From 16bdf9fdbe7b9ec6df36ebae6747000afe0c29ab Mon Sep 17 00:00:00 2001 From: vcoppe Date: Thu, 6 Jun 2024 18:11:03 +0200 Subject: [PATCH] strava heatmap layers (nakarte proxy) --- website/src/lib/assets/layers.ts | 98 +++++++++++++++++++ .../layer-control/LayerControlSettings.svelte | 78 ++++++++++++++- .../src/lib/components/layer-control/utils.ts | 2 +- website/src/lib/db.ts | 1 + website/src/lib/stores.ts | 43 +++++++- website/src/locales/en.json | 22 ++++- 6 files changed, 238 insertions(+), 6 deletions(-) diff --git a/website/src/lib/assets/layers.ts b/website/src/lib/assets/layers.ts index 8df3c0f9..7476bdb2 100644 --- a/website/src/lib/assets/layers.ts +++ b/website/src/lib/assets/layers.ts @@ -386,6 +386,62 @@ export const overlays: { [key: string]: AnySourceData; } = { maxzoom: 18, attribution: '© Waymarked Trails' }, + stravaHeatmapRun: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapTrailRun: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapHike: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapRide: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapGravel: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapMTB: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapWater: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, + stravaHeatmapWinter: { + type: 'raster', + tiles: [], + tileSize: 1024, + maxzoom: 15, + attribution: '© Strava' + }, }; export const opacities: { [key: string]: number; } = { @@ -452,6 +508,16 @@ export const overlayTree: LayerTreeType = { cyclOSM: { cyclOSMlite: true, }, + strava: { + stravaHeatmapRun: true, + stravaHeatmapTrailRun: true, + stravaHeatmapHike: true, + stravaHeatmapRide: true, + stravaHeatmapGravel: true, + stravaHeatmapMTB: true, + stravaHeatmapWater: true, + stravaHeatmapWinter: true, + }, waymarked_trails: { waymarkedTrailsHiking: true, waymarkedTrailsCycling: true, @@ -487,6 +553,16 @@ export const defaultOverlays = { cyclOSM: { cyclOSMlite: false, }, + strava: { + stravaHeatmapRun: false, + stravaHeatmapTrailRun: false, + stravaHeatmapHike: false, + stravaHeatmapRide: false, + stravaHeatmapGravel: false, + stravaHeatmapMTB: false, + stravaHeatmapWater: false, + stravaHeatmapWinter: false, + }, waymarked_trails: { waymarkedTrailsHiking: false, waymarkedTrailsCycling: false, @@ -568,6 +644,16 @@ export const defaultOverlayTree: LayerTreeType = { cyclOSM: { cyclOSMlite: true, }, + strava: { + stravaHeatmapRun: true, + stravaHeatmapTrailRun: true, + stravaHeatmapHike: true, + stravaHeatmapRide: true, + stravaHeatmapGravel: true, + stravaHeatmapMTB: true, + stravaHeatmapWater: true, + stravaHeatmapWinter: true, + }, waymarked_trails: { waymarkedTrailsHiking: true, waymarkedTrailsCycling: true, @@ -591,4 +677,16 @@ export const defaultOverlayTree: LayerTreeType = { } }, } +} + +export const stravaHeatmapServers = ['https://proxy.nakarte.me/https/heatmap-external-a.strava.com/tiles-auth']; //['https://heatmap-external-a.strava.com/tiles-auth', 'https://heatmap-external-b.strava.com/tiles-auth', 'https://heatmap-external-c.strava.com/tiles-auth']; +export const stravaHeatmapActivityIds: { [key: string]: string } = { + stravaHeatmapRun: 'sport_Run', + stravaHeatmapTrailRun: 'sport_TrailRun', + stravaHeatmapHike: 'sport_Hike', + stravaHeatmapRide: 'sport_Ride', + stravaHeatmapGravel: 'sport_GravelRide', + stravaHeatmapMTB: 'sport_MountainBikeRide', + stravaHeatmapWater: 'water', + stravaHeatmapWinter: 'winter', } \ No newline at end of file diff --git a/website/src/lib/components/layer-control/LayerControlSettings.svelte b/website/src/lib/components/layer-control/LayerControlSettings.svelte index 04454102..ee0d48a4 100644 --- a/website/src/lib/components/layer-control/LayerControlSettings.svelte +++ b/website/src/lib/components/layer-control/LayerControlSettings.svelte @@ -5,15 +5,75 @@ import { ScrollArea } from '$lib/components/ui/scroll-area/index.js'; import * as Sheet from '$lib/components/ui/sheet'; import * as Accordion from '$lib/components/ui/accordion'; + import { Label } from '$lib/components/ui/label'; + import * as Select from '$lib/components/ui/select'; import { basemapTree, overlayTree } from '$lib/assets/layers'; import { settings } from '$lib/db'; import { _ } from 'svelte-i18n'; + import { writable, get } from 'svelte/store'; + import { map, setStravaHeatmapURLs } from '$lib/stores'; - const { selectedBasemapTree, selectedOverlayTree } = settings; + const { selectedBasemapTree, selectedOverlayTree, stravaHeatmapColor, currentOverlays } = + settings; export let open: boolean; + + const heatmapColors = [ + { value: '', label: '' }, + { value: 'blue', label: $_('layers.color.blue') }, + { value: 'bluered', label: $_('layers.color.bluered') }, + { value: 'gray', label: $_('layers.color.gray') }, + { value: 'hot', label: $_('layers.color.hot') }, + { value: 'orange', label: $_('layers.color.orange') }, + { value: 'purple', label: $_('layers.color.purple') } + ]; + + let selectedHeatmapColor = writable(heatmapColors[0]); + + $: if ($selectedHeatmapColor !== heatmapColors[0]) { + stravaHeatmapColor.set($selectedHeatmapColor.value); + + // remove and add the heatmap layers + let m = get(map); + if (m) { + let currentStravaLayers = []; + for (let layer of Object.keys(overlayTree.overlays.world.strava)) { + if (m.getLayer(layer)) { + m.removeLayer(layer); + currentStravaLayers.push(layer); + } + if (m.getSource(layer)) { + m.removeSource(layer); + } + } + if (currentStravaLayers.length > 0) { + currentOverlays.update(($currentOverlays) => { + for (let layer of currentStravaLayers) { + $currentOverlays.overlays.world.strava[layer] = false; + } + return $currentOverlays; + }); + currentOverlays.update(($currentOverlays) => { + for (let layer of currentStravaLayers) { + $currentOverlays.overlays.world.strava[layer] = true; + } + return $currentOverlays; + }); + } + } + } + + $: if ($stravaHeatmapColor) { + setStravaHeatmapURLs(); + if ($stravaHeatmapColor !== get(selectedHeatmapColor).value) { + let toSelect = heatmapColors.find(({ value }) => value === $stravaHeatmapColor); + if (toSelect) { + selectedHeatmapColor.set(toSelect); + } + } + } @@ -55,7 +115,21 @@ {$_('layers.heatmap')} - + +
+ + + + + + + {#each heatmapColors as { value, label }} + {label} + {/each} + + +
+
{$_('layers.pois')} diff --git a/website/src/lib/components/layer-control/utils.ts b/website/src/lib/components/layer-control/utils.ts index 52dd531e..37a8a07e 100644 --- a/website/src/lib/components/layer-control/utils.ts +++ b/website/src/lib/components/layer-control/utils.ts @@ -16,7 +16,7 @@ export function anySelectedLayer(node: LayerTreeType) { } export function getLayers(node: LayerTreeType, layers: { [key: string]: boolean } = {}): { [key: string]: boolean } { - Object.keys(node).find((id) => { + Object.keys(node).forEach((id) => { if (typeof node[id] == "boolean") { layers[id] = node[id]; } else { diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index dec679ca..379d9b9a 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -97,6 +97,7 @@ export const settings = { selectedOverlayTree: dexieSettingStore('selectedOverlayTree', defaultOverlayTree), directionMarkers: dexieSettingStore('directionMarkers', false), distanceMarkers: dexieSettingStore('distanceMarkers', false), + stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'), fileOrder: dexieSettingStore('fileOrder', []), }; diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts index d15c155e..0d488648 100644 --- a/website/src/lib/stores.ts +++ b/website/src/lib/stores.ts @@ -5,10 +5,11 @@ import { GPXFile, buildGPX, parseGPX, GPXStatistics, type Coordinates } from 'gp import { tick } from 'svelte'; import { _ } from 'svelte-i18n'; import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer'; -import { dbUtils, fileObservers } from './db'; +import { dbUtils, fileObservers, settings } from './db'; import { applyToOrderedSelectedItemsFromFile, selectFile, selection } from '$lib/components/file-list/Selection'; import { ListFileItem, ListWaypointItem, ListWaypointsItem } from '$lib/components/file-list/FileList'; import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls'; +import { overlayTree, overlays, stravaHeatmapActivityIds, stravaHeatmapServers } from '$lib/assets/layers'; export const map = writable(null); export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); @@ -237,4 +238,44 @@ export function exportFile(file: GPXFile) { a.download = file.metadata.name + '.gpx'; a.click(); URL.revokeObjectURL(url); +} + +let stravaCookies: any = null; +function refreshStravaCookies() { + if (stravaCookies === null) { + return fetch('https://s.gpx.studio') + .then(response => { + if (response.ok) { + return response.json(); + } else { + throw new Error('Failed to fetch Strava cookies'); + } + }) + .then(data => { + stravaCookies = data; + console.log('Strava cookies:', stravaCookies); + }); + } else { + return Promise.resolve(); + } +} + +export function setStravaHeatmapURLs() { + refreshStravaCookies().then(() => { + overlays.stravaHeatmapRun.tiles = []; + overlays.stravaHeatmapTrailRun.tiles = []; + overlays.stravaHeatmapHike.tiles = []; + overlays.stravaHeatmapRide.tiles = []; + overlays.stravaHeatmapGravel.tiles = []; + overlays.stravaHeatmapMTB.tiles = []; + overlays.stravaHeatmapWater.tiles = []; + overlays.stravaHeatmapWinter.tiles = []; + + for (let activity of Object.keys(overlayTree.overlays.world.strava)) { + overlays[activity].tiles = []; + for (let server of stravaHeatmapServers) { + overlays[activity].tiles.push(`${server}/${stravaHeatmapActivityIds[activity]}/${get(settings.stravaHeatmapColor)}/{z}/{x}/{y}@2x.png?Signature=${stravaCookies['CloudFront-Signature']}&Key-Pair-Id=${stravaCookies['CloudFront-Key-Pair-Id']}&Policy=${stravaCookies['CloudFront-Policy']}`); + } + } + }); } \ No newline at end of file diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 9b50dae8..fd9bb568 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -38,7 +38,8 @@ "donate": "Donate", "ctrl": "Ctrl", "click": "Click", - "drag": "Drag" + "drag": "Drag", + "color": "Color" }, "toolbar": { "routing": { @@ -116,7 +117,7 @@ "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.", "selection": "Layer selection", "custom_layers": "Custom layers", - "heatmap": "Heatmap", + "heatmap": "Strava Heatmap", "pois": "Points of interest", "label": { "basemaps": "Basemaps", @@ -160,6 +161,15 @@ "ignFrCadastre": "IGN Cadastre", "ignSlope": "IGN Slope", "ignSkiTouring": "IGN Ski Touring", + "strava": "Strava", + "stravaHeatmapRun": "Running", + "stravaHeatmapTrailRun": "Trail Running", + "stravaHeatmapHike": "Hiking", + "stravaHeatmapRide": "Cycling", + "stravaHeatmapGravel": "Gravel Cycling", + "stravaHeatmapMTB": "MTB", + "stravaHeatmapWater": "Water", + "stravaHeatmapWinter": "Winter", "waymarked_trails": "Waymarked Trails", "waymarkedTrailsHiking": "Hiking", "waymarkedTrailsCycling": "Cycling", @@ -167,6 +177,14 @@ "waymarkedTrailsSkating": "Skating", "waymarkedTrailsHorseRiding": "Horse Riding", "waymarkedTrailsWinter": "Winter" + }, + "color": { + "blue": "Blue", + "bluered": "Blue Red", + "gray": "Gray", + "hot": "Hot", + "purple": "Purple", + "orange": "Orange" } }, "chart": {