Compare commits

...

10 Commits

Author SHA1 Message Date
1fdd4e71b5
departure point can be null 2024-02-16 11:14:32 +01:00
7ea79b6f44
Don't crash if segments.geom is null.
Somtimes (for example https://www.sac-cas.ch/it/capanne-e-escursioni/portale-escursionistico-del-cas/albristhubel-6246/escursioni-con-le-racchette/von-matten-simmental-2785/)
segments.geom is null and the creation of the gpx xml subsequently fails.

If that is the case just ignore it and do not try to turn it into a
track segment.

Thanks to wizche for the report and fix!
2023-02-03 12:41:49 +01:00
553f764e69
unify formatting 2023-02-01 20:37:55 +01:00
6419d5defd
use correct title for track 2021-06-16 13:27:51 +02:00
85de91c668
add all waypoints and the endpoint 2021-06-16 12:59:13 +02:00
e18added04
version 0.4: add all route segments to gpx file 2021-06-16 12:42:39 +02:00
8eed835aa2
version 0.3 2021-06-16 12:02:26 +02:00
3fb2db279f
version 0.2 2021-06-16 10:29:41 +02:00
9c78a2e931
add wapoints to gpx file 2021-06-16 10:29:25 +02:00
3c9e6a2865
add web-ext-artifacts to gitignore 2021-06-15 11:38:10 +02:00
4 changed files with 175 additions and 117 deletions

1
.gitignore vendored
View File

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

View File

@ -1,5 +1,4 @@
# SAC Route Portal GPX Downloader
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/)
for finding interesting hiking routes.
@ -19,3 +18,6 @@ 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.
Take a look at their [safety instructions](https://www.sac-cas.ch/en/training-and-safety/safety/).
## Contributors
- wizche

View File

@ -1,196 +1,255 @@
/* 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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
let gpxTrack = null;
/**
* Convert the Swiss projection coordinates to WGS84 (lat/lon).
*
* Calculations from "Approximate formulas for the transformation between Swiss
* projection coordinates and WGS84" by the Swiss Federal Office of Topography,
* swisstopo
*
* Calculations from "Approximate formulas for the transformation between Swiss
* projection coordinates and WGS84" by the Swiss Federal Office of Topography,
* swisstopo
* (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.
* @returns Track point xml node for gpx.
*/
function toTrackPoint(point) {
// convert LV95 into the civilian system
let y_aux = (point[0] - 2600000)/1000000;
let x_aux = (point[1] - 1200000)/1000000;
const wgs84Point = toWGS84(point);
return `<trkpt lat="${wgs84Point.lat}" lon="${wgs84Point.lon}"/>`;
}
// 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.0140 * Math.pow(x_aux, 3);
let lng = 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;
lng = lng * 100 / 36;
return `<trkpt lat="${lat}" lon="${lng}"/>`;
/**
* Get the gpx waypoint representation of a route portal point.
*
* @returns Way point xml node for gpx.
*/
function toWayPoint(point) {
const wgs84Point = toWGS84(point.geom.coordinates);
return `
<wpt lat="${wgs84Point.lat}" lon="${wgs84Point.lon}">
<ele>${point.altitude}</ele>
<name>${point.display_name}</name>
</wpt>`;
}
/**
* Create track title.
*
* @param {*} geoJson
*
* @param {*} geoJson
* @returns Combined route number, id and route title.
*/
function trackTitle(geoJson) {
const route = geoJson.segments[0];
return `${geoJson.book_route_number}-${route.route_id} ${route.title}`;
const book = geoJson.book_route_number
? `${geoJson.book_route_number} - `
: "";
return `${book}${geoJson.title}`;
}
/**
* Create a gpx representation from the sac GeoJSON data.
*
* @param {*} geoJson
*
* @param {*} geoJson
* @returns Simple gpx string.
*/
function toGpx(geoJson) {
const route = geoJson.segments[0].geom;
const routeTitle = trackTitle(geoJson);
const trackSegments = geoJson.segments
.map((segment) => {
if (segment.geom == null) return "";
return `<trkseg>
${segment.geom.coordinates.map(toTrackPoint).join("")}
</trkseg>`;
})
.join("");
const xmlString = `<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
const departurePoint = geoJson.departure_point
? 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"
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"
version="1.0"
creator="SAC-Tourenportal GPX Downloader">
${departurePoint}
${toWayPoint(geoJson.destination_poi)}
${waypoints}
${endPoint}
<trk>
<name>Track ${routeTitle}</name>
<trkseg>
${route.coordinates.map(toTrackPoint).join("")}
</trkseg>
${trackSegments}
</trk>
</gpx>
`;
const parser = new DOMParser();
let xmlDoc = parser.parseFromString(xmlString, "text/xml");
const parser = new DOMParser();
const 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.
*/
function listener(details) {
let filter = browser.webRequest.filterResponseData(details.requestId);
let decoder = new TextDecoder("utf-8");
let encoder = new TextEncoder();
const filter = browser.webRequest.filterResponseData(details.requestId);
const decoder = new TextDecoder("utf-8");
const encoder = new TextEncoder();
let data = [];
filter.ondata = event => {
data.push(event.data);
};
const data = [];
filter.ondata = (event) => {
data.push(event.data);
};
filter.onstop = async event => {
let blob = new Blob(data, {type: 'text/html'});
let buffer = await blob.arrayBuffer();
let str = decoder.decode(buffer);
filter.onstop = async (_event) => {
const blob = new Blob(data, { type: "text/html" });
const buffer = await blob.arrayBuffer();
const str = decoder.decode(buffer);
gpxTrack = JSON.parse(str);
updateActiveTab(browser.tabs);
updateActiveTab(browser.tabs);
filter.write(encoder.encode(str));
filter.close();
};
filter.write(encoder.encode(str));
filter.close();
return {};
const geoJson = JSON.parse(str);
const routeTitle = trackTitle(geoJson);
gpxTrack = { title: routeTitle, data: toGpx(geoJson) };
};
return {};
}
/**
* Check if a url should be intercepted.
*
* @param {*} tab
*
* @param {*} tab
* @returns True if the active tab is an sac website, false otherwise.
*/
function checkTrack(tab) {
if (!tab.url) {
return false;
}
if (!tab.url) {
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.
*/
function handleClick(tab) {
const hasTrack = checkTrack(tab);
if (!hasTrack) {
return;
}
const hasTrack = checkTrack(tab);
if (!hasTrack) {
return;
}
let blob = new Blob([toGpx(gpxTrack)], {type: "application/gpx+xml"});
let objectURL = URL.createObjectURL(blob);
const blob = new Blob([gpxTrack.data], { type: "application/gpx+xml" });
const objectURL = URL.createObjectURL(blob);
const routeTitle = trackTitle(gpxTrack);
const downloading = browser.downloads.download({
url: objectURL,
filename: `${gpxTrack.title}.gpx`,
saveAs: true,
conflictAction: "uniquify",
});
let downloading = browser.downloads.download({
url : objectURL,
filename : `track-${routeTitle}.gpx`,
saveAs: true,
conflictAction : 'uniquify'
});
downloading.then(
(id) => console.log(`Started downloading: ${id}`),
(error) => console.log(`Download failed: ${error}`));
gpxTrack = null;
downloading.then(
(id) => console.log(`Started downloading: ${id}`),
(error) => console.log(`Download failed: ${error}`),
);
gpxTrack = null;
}
/**
* Update the download icon and text.
*/
function updateActiveTab(tabs) {
function updateTab(tabs) {
if (tabs[0]) {
updateIcon(tabs[0]);
}
function updateActiveTab(_tabs) {
function updateTab(tabs) {
if (tabs[0]) {
updateIcon(tabs[0]);
}
let gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then(updateTab);
}
const gettingActiveTab = browser.tabs.query({
active: true,
currentWindow: true,
});
gettingActiveTab.then(updateTab);
}
/**
* Update the download icon.
*/
function updateIcon(tab) {
const hasTrack = checkTrack(tab);
const hasTrack = checkTrack(tab);
browser.browserAction.setIcon({
path: hasTrack ? {
browser.browserAction.setIcon({
path: hasTrack
? {
48: "icons/map.png",
} : {
}
: {
48: "icons/map-disabled.png",
},
tabId: tab.id
});
browser.browserAction.setTitle({
title: hasTrack ? `Download track ${trackTitle(gpxTrack)}` : 'No track selected',
tabId: tab.id
});
tabId: tab.id,
});
browser.browserAction.setTitle({
title: hasTrack
? `Download track "${gpxTrack.title}"`
: "No track selected",
tabId: tab.id,
});
}
browser.webRequest.onBeforeRequest.addListener(
listener,
{urls: ["https://www.sac-cas.ch/*[routeId]*"]},
["blocking"]
listener,
{ urls: ["https://www.sac-cas.ch/*[routeId]*"] },
["blocking"],
);
browser.browserAction.onClicked.addListener(handleClick);
@ -198,5 +257,5 @@ browser.browserAction.onClicked.addListener(handleClick);
browser.tabs.onUpdated.addListener(updateActiveTab);
browser.tabs.onActivated.addListener(updateActiveTab);
browser.windows.onFocusChanged.addListener(updateActiveTab);
updateActiveTab();

View File

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