Compare commits

..

No commits in common. "1fdd4e71b509a9a0cd93f35adbf6e4c1740fe260" and "1a780c6f53d84d7598df11e140b49f4604bd08e8" have entirely different histories.

4 changed files with 117 additions and 175 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
*~ *~
.DS_Store .DS_Store
*.swp *.swp
web-ext-artifacts

View File

@ -1,4 +1,5 @@
# SAC Route Portal GPX Downloader # SAC Route Portal GPX Downloader
The [Swiss Alpine Club](https://www.sac-cas.ch/en/) has a great The [Swiss Alpine Club](https://www.sac-cas.ch/en/) has a great
[route portal](https://www.sac-cas.ch/en/huts-and-tours/sac-route-portal/) [route portal](https://www.sac-cas.ch/en/huts-and-tours/sac-route-portal/)
for finding interesting hiking routes. for finding interesting hiking routes.
@ -18,6 +19,3 @@ Heed their warning and do not blindly follow the gps track! Use your brain and
plan your route beforehand to be aware of dangerous parts and alternatives. plan your route beforehand to be aware of dangerous parts and alternatives.
Take a look at their [safety instructions](https://www.sac-cas.ch/en/training-and-safety/safety/). Take a look at their [safety instructions](https://www.sac-cas.ch/en/training-and-safety/safety/).
## Contributors
- wizche

View File

@ -1,255 +1,196 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
let gpxTrack = null; let gpxTrack = null;
/** /**
* Convert the Swiss projection coordinates to WGS84 (lat/lon). * Convert the Swiss projection coordinates to WGS84 (lat/lon).
* *
* Calculations from "Approximate formulas for the transformation between Swiss * Calculations from "Approximate formulas for the transformation between Swiss
* projection coordinates and WGS84" by the Swiss Federal Office of Topography, * projection coordinates and WGS84" by the Swiss Federal Office of Topography,
* swisstopo * swisstopo
* (https://www.swisstopo.admin.ch/en/knowledge-facts/surveying-geodesy/reference-systems/map-projections.html). * (https://www.swisstopo.admin.ch/en/knowledge-facts/surveying-geodesy/reference-systems/map-projections.html).
* *
* @param {*} point Array of Swiss projection coordinates, position 0 is E and 1 is N.
* @returns Calculated lat and lon.
*/
function toWGS84(point) {
// convert LV95 into the civilian system
const y_aux = (point[0] - 2600000) / 1000000;
const x_aux = (point[1] - 1200000) / 1000000;
// calculate longitude and latitude in the unit 10000"
let lat = 16.9023892 +
3.238272 * x_aux -
0.270978 * Math.pow(y_aux, 2) -
0.002528 * Math.pow(x_aux, 2) -
0.0447 * Math.pow(y_aux, 2) * x_aux -
0.014 * Math.pow(x_aux, 3);
let lon = 2.6779094 +
4.728982 * y_aux +
0.791484 * y_aux * x_aux +
0.1306 * y_aux * Math.pow(x_aux, 2) -
0.0436 * Math.pow(y_aux, 3);
// unit 10000" to 1" and seconds to degrees (dec)
lat = (lat * 100) / 36;
lon = (lon * 100) / 36;
return { lat: lat, lon: lon };
}
/**
* Get the gpx trackpoint representation of a Swiss projection coordinate point.
*
* @param {*} point Array of Swiss projection coordinates, position 0 is E and 1 is N. * @param {*} point Array of Swiss projection coordinates, position 0 is E and 1 is N.
* @returns Track point xml node for gpx. * @returns Track point xml node for gpx.
*/ */
function toTrackPoint(point) { function toTrackPoint(point) {
const wgs84Point = toWGS84(point); // convert LV95 into the civilian system
return `<trkpt lat="${wgs84Point.lat}" lon="${wgs84Point.lon}"/>`; let y_aux = (point[0] - 2600000)/1000000;
} let x_aux = (point[1] - 1200000)/1000000;
/** // calculate longitude and latitude in the unit 10000"
* Get the gpx waypoint representation of a route portal point. let lat = 16.9023892 +
* 3.238272 * x_aux -
* @returns Way point xml node for gpx. 0.270978 * Math.pow(y_aux, 2) -
*/ 0.002528 * Math.pow(x_aux, 2) -
function toWayPoint(point) { 0.0447 * Math.pow(y_aux, 2) * x_aux -
const wgs84Point = toWGS84(point.geom.coordinates); 0.0140 * Math.pow(x_aux, 3);
return `
<wpt lat="${wgs84Point.lat}" lon="${wgs84Point.lon}"> let lng = 2.6779094 +
<ele>${point.altitude}</ele> 4.728982 * y_aux +
<name>${point.display_name}</name> 0.791484 * y_aux * x_aux +
</wpt>`; 0.1306 * y_aux * Math.pow(x_aux, 2) -
0.0436 * Math.pow(y_aux, 3);
// unit 10000" to 1" and seconds to degrees (dec)
lat = lat * 100 / 36;
lng = lng * 100 / 36;
return `<trkpt lat="${lat}" lon="${lng}"/>`;
} }
/** /**
* Create track title. * Create track title.
* *
* @param {*} geoJson * @param {*} geoJson
* @returns Combined route number, id and route title. * @returns Combined route number, id and route title.
*/ */
function trackTitle(geoJson) { function trackTitle(geoJson) {
const book = geoJson.book_route_number const route = geoJson.segments[0];
? `${geoJson.book_route_number} - ` return `${geoJson.book_route_number}-${route.route_id} ${route.title}`;
: "";
return `${book}${geoJson.title}`;
} }
/** /**
* Create a gpx representation from the sac GeoJSON data. * Create a gpx representation from the sac GeoJSON data.
* *
* @param {*} geoJson * @param {*} geoJson
* @returns Simple gpx string. * @returns Simple gpx string.
*/ */
function toGpx(geoJson) { function toGpx(geoJson) {
const trackSegments = geoJson.segments const route = geoJson.segments[0].geom;
.map((segment) => { const routeTitle = trackTitle(geoJson);
if (segment.geom == null) return "";
return `<trkseg>
${segment.geom.coordinates.map(toTrackPoint).join("")}
</trkseg>`;
})
.join("");
const departurePoint = geoJson.departure_point const xmlString = `<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
? toWayPoint(geoJson.departure_point)
: "";
const endPoint = geoJson.end_point ? toWayPoint(geoJson.end_point) : "";
const waypoints = geoJson.waypoints
? geoJson.waypoints
.map((wp) => {
return toWayPoint(wp.reference_poi);
})
.join("")
: "";
const routeTitle = trackTitle(geoJson);
const xmlString = `<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0" xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"
version="1.0" version="1.0"
creator="SAC-Tourenportal GPX Downloader"> creator="SAC-Tourenportal GPX Downloader">
${departurePoint}
${toWayPoint(geoJson.destination_poi)}
${waypoints}
${endPoint}
<trk> <trk>
<name>Track ${routeTitle}</name> <name>Track ${routeTitle}</name>
${trackSegments} <trkseg>
${route.coordinates.map(toTrackPoint).join("")}
</trkseg>
</trk> </trk>
</gpx> </gpx>
`; `;
const parser = new DOMParser(); const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "text/xml"); let xmlDoc = parser.parseFromString(xmlString, "text/xml");
return new XMLSerializer().serializeToString(xmlDoc.documentElement); return new XMLSerializer().serializeToString(xmlDoc.documentElement);
} }
/** /**
* Intercept the download of GeoJSON data and save it for the background script. * Intercept the download of GeoJSON data and save it for the background script.
*/ */
function listener(details) { function listener(details) {
const filter = browser.webRequest.filterResponseData(details.requestId); let filter = browser.webRequest.filterResponseData(details.requestId);
const decoder = new TextDecoder("utf-8"); let decoder = new TextDecoder("utf-8");
const encoder = new TextEncoder(); let encoder = new TextEncoder();
const data = []; let data = [];
filter.ondata = (event) => { filter.ondata = event => {
data.push(event.data); data.push(event.data);
}; };
filter.onstop = async (_event) => { filter.onstop = async event => {
const blob = new Blob(data, { type: "text/html" }); let blob = new Blob(data, {type: 'text/html'});
const buffer = await blob.arrayBuffer(); let buffer = await blob.arrayBuffer();
const str = decoder.decode(buffer); let str = decoder.decode(buffer);
updateActiveTab(browser.tabs); gpxTrack = JSON.parse(str);
updateActiveTab(browser.tabs);
filter.write(encoder.encode(str)); filter.write(encoder.encode(str));
filter.close(); filter.close();
};
const geoJson = JSON.parse(str); return {};
const routeTitle = trackTitle(geoJson);
gpxTrack = { title: routeTitle, data: toGpx(geoJson) };
};
return {};
} }
/** /**
* Check if a url should be intercepted. * Check if a url should be intercepted.
* *
* @param {*} tab * @param {*} tab
* @returns True if the active tab is an sac website, false otherwise. * @returns True if the active tab is an sac website, false otherwise.
*/ */
function checkTrack(tab) { function checkTrack(tab) {
if (!tab.url) { if (!tab.url) {
return false; return false;
} }
return tab.url.match("https://www.sac-cas.ch/.*") && gpxTrack; return tab.url.match("https://www.sac-cas.ch/.*") && gpxTrack;
} }
/** /**
* If a valid tack was selected, download it as a gpx file. * If a valid tack was selected, download it as a gpx file.
*/ */
function handleClick(tab) { function handleClick(tab) {
const hasTrack = checkTrack(tab); const hasTrack = checkTrack(tab);
if (!hasTrack) { if (!hasTrack) {
return; return;
} }
const blob = new Blob([gpxTrack.data], { type: "application/gpx+xml" }); let blob = new Blob([toGpx(gpxTrack)], {type: "application/gpx+xml"});
const objectURL = URL.createObjectURL(blob); let objectURL = URL.createObjectURL(blob);
const downloading = browser.downloads.download({ const routeTitle = trackTitle(gpxTrack);
url: objectURL,
filename: `${gpxTrack.title}.gpx`,
saveAs: true,
conflictAction: "uniquify",
});
downloading.then( let downloading = browser.downloads.download({
(id) => console.log(`Started downloading: ${id}`), url : objectURL,
(error) => console.log(`Download failed: ${error}`), filename : `track-${routeTitle}.gpx`,
); saveAs: true,
gpxTrack = null; conflictAction : 'uniquify'
});
downloading.then(
(id) => console.log(`Started downloading: ${id}`),
(error) => console.log(`Download failed: ${error}`));
gpxTrack = null;
} }
/** /**
* Update the download icon and text. * Update the download icon and text.
*/ */
function updateActiveTab(_tabs) { function updateActiveTab(tabs) {
function updateTab(tabs) { function updateTab(tabs) {
if (tabs[0]) { if (tabs[0]) {
updateIcon(tabs[0]); updateIcon(tabs[0]);
}
} }
}
let gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
const gettingActiveTab = browser.tabs.query({ gettingActiveTab.then(updateTab);
active: true,
currentWindow: true,
});
gettingActiveTab.then(updateTab);
} }
/** /**
* Update the download icon. * Update the download icon.
*/ */
function updateIcon(tab) { function updateIcon(tab) {
const hasTrack = checkTrack(tab); const hasTrack = checkTrack(tab);
browser.browserAction.setIcon({ browser.browserAction.setIcon({
path: hasTrack path: hasTrack ? {
? {
48: "icons/map.png", 48: "icons/map.png",
} } : {
: {
48: "icons/map-disabled.png", 48: "icons/map-disabled.png",
}, },
tabId: tab.id, tabId: tab.id
}); });
browser.browserAction.setTitle({ browser.browserAction.setTitle({
title: hasTrack title: hasTrack ? `Download track ${trackTitle(gpxTrack)}` : 'No track selected',
? `Download track "${gpxTrack.title}"` tabId: tab.id
: "No track selected", });
tabId: tab.id,
});
} }
browser.webRequest.onBeforeRequest.addListener( browser.webRequest.onBeforeRequest.addListener(
listener, listener,
{ urls: ["https://www.sac-cas.ch/*[routeId]*"] }, {urls: ["https://www.sac-cas.ch/*[routeId]*"]},
["blocking"], ["blocking"]
); );
browser.browserAction.onClicked.addListener(handleClick); browser.browserAction.onClicked.addListener(handleClick);
@ -257,5 +198,5 @@ browser.browserAction.onClicked.addListener(handleClick);
browser.tabs.onUpdated.addListener(updateActiveTab); browser.tabs.onUpdated.addListener(updateActiveTab);
browser.tabs.onActivated.addListener(updateActiveTab); browser.tabs.onActivated.addListener(updateActiveTab);
browser.windows.onFocusChanged.addListener(updateActiveTab); browser.windows.onFocusChanged.addListener(updateActiveTab);
updateActiveTab(); updateActiveTab();

View File

@ -1,15 +1,19 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "SAC Route Portal GPX Downloader", "name": "SAC Route Portal GPX",
"version": "0.8", "version": "0.1",
"developer": { "developer": {
"name": "Sebastian Hugentobler", "name": "Sebastian Hugentobler",
"url": "https://code.vanwa.ch/sebastian/sac-route-portal-gpx-fx" "url": "https://code.vanwa.ch/sebastian/sac-route-portal-gpx-fx"
}, },
"description": "Download gpx tracks from the sac route portal.", "description": "Download gpx tracks from the sac route portal.",
"icons": { "icons": {
"48": "icons/map.png" "48": "icons/map.png"
}, },
"permissions": [ "permissions": [
"activeTab", "activeTab",
"downloads", "downloads",
@ -17,13 +21,13 @@
"webRequestBlocking", "webRequestBlocking",
"https://www.sac-cas.ch/*" "https://www.sac-cas.ch/*"
], ],
"background": { "background": {
"scripts": [ "scripts": ["background.js"]
"background.js"
]
}, },
"browser_action": { "browser_action": {
"default_icon": "icons/map.png", "default_icon": "icons/map.png",
"default_title": "To GPX" "default_title": "To GPX"
} }
} }