├── Readme.md
├── komoot_regions.png
├── render_unlocked_komoot_regions.user.js
└── view.js
/Readme.md:
--------------------------------------------------------------------------------
1 | # Komoot unlocked regions viewer
2 |
3 | A small utility to display all unlocked regions of your komoot account.
4 |
5 | 
6 |
7 | ## Usage
8 |
9 | 1. Sign into your kommot account and navigate to https://www.komoot.com/product/regions
10 | 2. Open the developer console (`F12`)
11 | 3. Paste the content of `view.js` into the console and execute it
12 |
13 | Alternatively you can use the provided userscript.
14 |
--------------------------------------------------------------------------------
/komoot_regions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahz/komoot-unlocked-regions/4b6c47a49485caa9a6afff8f12321965ebf2fb86/komoot_regions.png
--------------------------------------------------------------------------------
/render_unlocked_komoot_regions.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Render Unlocked Komoot Regions
3 | // @namespace http://tampermonkey.net/
4 | // @version 0.2
5 | // @include /^https?://www\.komoot\.(.*)/product/regions(.*)
6 | // @grant none
7 | // ==/UserScript==
8 |
9 |
10 | function GM_main () {
11 | window.onload = function () {
12 |
13 | function loadScriptFromGithub() {
14 | const http = new XMLHttpRequest();
15 | http.open("GET", "https://raw.githubusercontent.com/cahz/komoot-unlocked-regions/master/view.js", true);
16 | http.onreadystatechange = function() {
17 | console.log(http.status);
18 | if(http.readyState == 4 && http.status == 200) {
19 | const d = document;
20 | const scriptNode = d.createElement ('script');
21 | scriptNode.type = "text/javascript";
22 | scriptNode.textContent = http.responseText;
23 |
24 | const eHead = d.getElementsByTagName('head')[0] || d.body || d.documentElement;
25 | eHead.appendChild (scriptNode);
26 | }
27 | }
28 | http.send();
29 | }
30 |
31 | // run loadScriptFromGithub after 1sec
32 | setTimeout(loadScriptFromGithub, 1000);
33 | }
34 | }
35 |
36 | const d = document;
37 | const scriptNode = d.createElement ('script');
38 | scriptNode.type = "text/javascript";
39 | scriptNode.textContent = '(' + GM_main.toString() + ')()';
40 |
41 | const eHead = d.getElementsByTagName('head')[0] || d.body || d.documentElement;
42 | eHead.appendChild (scriptNode);
43 |
--------------------------------------------------------------------------------
/view.js:
--------------------------------------------------------------------------------
1 | // get list of unlocked regions from komoot internal API
2 | var regions = kmtBoot.getProps().packages.models.map((p) => {
3 | if (p.attributes.region === undefined) {return 9999;}
4 | return p.attributes.region.id
5 | });
6 |
7 | function loadScripts(srcs, callback={}){
8 | if(srcs.length == 0){
9 | callback();
10 | return;
11 | }
12 | let script = document.createElement('script');
13 | script.src = srcs.shift();
14 | document.getElementsByTagName('head')[0].appendChild(script);
15 | script.onload = () => {loadScripts(srcs, callback)};
16 | }
17 |
18 | // Load Maplibre style
19 | let style = document.createElement('link');
20 | style.rel = "stylesheet";
21 | style.href = "https://unpkg.com/maplibre-gl/dist/maplibre-gl.css";
22 | document.getElementsByTagName('head')[0].appendChild(style);
23 |
24 | // load scripts: Maplibre
25 | loadScripts(["https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"], () => {
26 | let mapelem = document.createElement('div');
27 | mapelem.id = 'mapid';
28 | mapelem.style.height = document.getElementsByClassName("c-inline-map__container")[0].getBoundingClientRect().height + "px"
29 | document.getElementsByClassName("c-inline-map__container")[0].parentNode.parentNode.appendChild(mapelem);
30 |
31 | let map = new maplibregl.Map({
32 | container: 'mapid',
33 | style: 'https://tiles-api.maps.komoot.net/v1/style.json?optimize=true',
34 | attributionControl: false,
35 | });
36 |
37 | // Colors as used by komoot for region bundle (red) and single region (blue)
38 | let regionColor = [ "case", [ "boolean", [ "get", "region" ] ], [ "rgba", 16, 134, 232, 1 ], [ "rgba", 245, 82, 94, 1 ] ];
39 |
40 | map.once("load", () => {
41 | for(let key in map.style?.sourceCaches){ // Remove komoot's wrong osm attribution
42 | if(map.style.sourceCaches[key]?._source?.attribution){
43 | map.style.sourceCaches[key]._source.attribution = "";
44 | }
45 | }
46 |
47 | map.addControl(new maplibregl.AttributionControl({
48 | customAttribution: ['Maplibre',
49 | '© komoot',
50 | 'Map data © OpenStreetMap contributors']
51 | }))
52 | map.addControl(new maplibregl.NavigationControl());
53 | map.addControl(new maplibregl.GeolocateControl());
54 |
55 | map.setLayoutProperty("komoot-region", 'visibility', 'visible'); // Enable the region layer
56 | map.addLayer({
57 | id: 'custom-region-text',
58 | source: 'komoot_region',
59 | type: 'symbol',
60 | minzoom: 6,
61 | layout: {
62 | 'text-field': ['get', 'name'],
63 | 'text-font': ['Noto Sans Bold'],
64 | },
65 | paint: {
66 | 'text-color': regionColor,
67 | }
68 | });
69 | map.addLayer({
70 | id: 'custom-region-polygon',
71 | source: 'komoot_region',
72 | type: 'fill',
73 | paint: {
74 | 'fill-color': regionColor,
75 | 'fill-opacity': 0.3,
76 | }
77 | },"custom-region-text"); // Draw polygons *under* text layer
78 |
79 | // Prepare for initial zoom
80 | let allbounds = new maplibregl.LngLatBounds();
81 |
82 | // iterate over unlocked regions and draw the result
83 | regions.forEach(async (id) => {
84 | if (id == 9999) {return}
85 |
86 | // load regions and add them as geojson features
87 | fetch("?region="+id, {headers: {'onlyprops': 'true'}})
88 | .then(res => res.json())
89 | .then(json => {
90 | let counter = 0;
91 | json.regions[0].geometry.forEach(p => {
92 | counter++;
93 | let region = [];
94 | p.forEach((i) => {
95 | region.push([i.lng, i.lat]);
96 | allbounds.extend([i.lng, i.lat]);
97 | });
98 |
99 | map.getSource('komoot_region').updateData({
100 | add: [{
101 | type: "Feature",
102 | id: id+"p"+counter,
103 | geometry: {
104 | type: "Polygon",
105 | coordinates: [region],
106 | },
107 | properties: {
108 | region: json.regions[0].groupId==1,
109 | name: json.regions[0].name,
110 | },
111 | }]
112 | });
113 | });
114 | map.fitBounds(allbounds, {padding: 20}); // Zoom in
115 | });
116 | });
117 | });
118 |
119 | map.on('click', 'custom-region-polygon', (e) => {
120 | const coordinates = e.features[0].geometry.coordinates[0];
121 | const bounds = coordinates.reduce((bounds, coord) => {
122 | return bounds.extend(coord);
123 | }, new maplibregl.LngLatBounds(coordinates[0], coordinates[0]));
124 |
125 | map.fitBounds(bounds, {
126 | padding: 20
127 | });
128 | });
129 |
130 | // Change the cursor to a pointer when the mouse is over the region layer.
131 | map.on('mouseenter', 'custom-region-polygon', () => { map.getCanvas().style.cursor = 'pointer'; });
132 | map.on('mouseleave', 'custom-region-polygon', () => { map.getCanvas().style.cursor = ''; });
133 |
134 | });
135 |
136 |
137 | availableRegions = kmtBoot.getProps().freeProducts.length;
138 | if(availableRegions>0){
139 | let elem = document.createElement('span');
140 | elem.innerHTML = "You have "+availableRegions+" free region"+(availableRegions!=1?"s":"")+" available!";
141 | document.getElementsByClassName("c-inline-map__container")[0].parentNode.parentNode.parentNode.appendChild(elem);
142 | }
143 |
144 | console.log("You have %d free region(s) available!", kmtBoot.getProps().freeProducts.length);
145 |
--------------------------------------------------------------------------------