mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 00:32:33 +00:00
add overpass poi to file
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
export * from './gpx';
|
export * from './gpx';
|
||||||
export { Coordinates, LineStyleExtension } from './types';
|
export { Coordinates, LineStyleExtension, WaypointType } from './types';
|
||||||
export { parseGPX, buildGPX } from './io';
|
export { parseGPX, buildGPX } from './io';
|
||||||
export * from './simplify';
|
export * from './simplify';
|
||||||
|
|
||||||
|
@@ -561,7 +561,7 @@ export const overpassTree: LayerTreeType = {
|
|||||||
tourism: {
|
tourism: {
|
||||||
attraction: true,
|
attraction: true,
|
||||||
viewpoint: true,
|
viewpoint: true,
|
||||||
sleep: true,
|
accommodation: true,
|
||||||
summit: true,
|
summit: true,
|
||||||
pass: true,
|
pass: true,
|
||||||
climbing: true,
|
climbing: true,
|
||||||
@@ -645,7 +645,7 @@ export const defaultOverpassQueries: LayerTreeType = {
|
|||||||
tourism: {
|
tourism: {
|
||||||
attraction: false,
|
attraction: false,
|
||||||
viewpoint: false,
|
viewpoint: false,
|
||||||
sleep: false,
|
accommodation: false,
|
||||||
summit: false,
|
summit: false,
|
||||||
pass: false,
|
pass: false,
|
||||||
climbing: false
|
climbing: false
|
||||||
@@ -770,30 +770,30 @@ export const defaultOverpassTree: LayerTreeType = {
|
|||||||
amenities: {
|
amenities: {
|
||||||
toilets: true,
|
toilets: true,
|
||||||
"water": true,
|
"water": true,
|
||||||
"water-spring": true,
|
"water-spring": false,
|
||||||
shower: true,
|
shower: false,
|
||||||
"fuel-station": false,
|
"fuel-station": false,
|
||||||
parking: false,
|
parking: false,
|
||||||
barrier: false
|
barrier: false
|
||||||
},
|
},
|
||||||
tourism: {
|
tourism: {
|
||||||
attraction: true,
|
attraction: false,
|
||||||
viewpoint: true,
|
viewpoint: false,
|
||||||
sleep: true,
|
accommodation: true,
|
||||||
summit: true,
|
summit: true,
|
||||||
pass: true,
|
pass: true,
|
||||||
climbing: false
|
climbing: false
|
||||||
},
|
},
|
||||||
bicycle: {
|
bicycle: {
|
||||||
"bicycle-parking": true,
|
"bicycle-parking": false,
|
||||||
"bicycle-rental": true,
|
"bicycle-rental": false,
|
||||||
"bicycle-shop": true
|
"bicycle-shop": true
|
||||||
},
|
},
|
||||||
"public-transport": {
|
"public-transport": {
|
||||||
"railway-station": true,
|
"railway-station": true,
|
||||||
"tram-stop": true,
|
"tram-stop": true,
|
||||||
"bus-stop": true,
|
"bus-stop": true,
|
||||||
ferry: true
|
ferry: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -926,10 +926,10 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
|
|||||||
tourism: "viewpoint"
|
tourism: "viewpoint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sleep: {
|
accommodation: {
|
||||||
icon: {
|
icon: {
|
||||||
svg: Bed,
|
svg: Bed,
|
||||||
color: "Green",
|
color: "#e6c100",
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
tourism: ["hotel", "hostel", "guest_house", "motel", "camp_site", "alpine_hut", "wilderness_hut"]
|
tourism: ["hotel", "hostel", "guest_house", "motel", "camp_site", "alpine_hut", "wilderness_hut"]
|
||||||
|
@@ -34,24 +34,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $currentPopupWaypoint[0].desc}
|
{#if $currentPopupWaypoint[0].desc}
|
||||||
<span>{$currentPopupWaypoint[0].desc}</span>
|
<span class="whitespace-pre-wrap">{$currentPopupWaypoint[0].desc}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $currentPopupWaypoint[0].cmt && $currentPopupWaypoint[0].cmt !== $currentPopupWaypoint[0].desc}
|
{#if $currentPopupWaypoint[0].cmt && $currentPopupWaypoint[0].cmt !== $currentPopupWaypoint[0].desc}
|
||||||
<span>{$currentPopupWaypoint[0].cmt}</span>
|
<span class="whitespace-pre-wrap">{$currentPopupWaypoint[0].cmt}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $currentTool === Tool.WAYPOINT}
|
{#if $currentTool === Tool.WAYPOINT}
|
||||||
<div class="mt-2">
|
<Button
|
||||||
<Button
|
class="mt-2 w-full px-2 py-1 h-8 justify-start"
|
||||||
class="w-full px-2 py-1 h-6 justify-start"
|
variant="outline"
|
||||||
variant="ghost"
|
on:click={() =>
|
||||||
on:click={() =>
|
deleteWaypoint($currentPopupWaypoint[1], $currentPopupWaypoint[0]._data.index)}
|
||||||
deleteWaypoint($currentPopupWaypoint[1], $currentPopupWaypoint[0]._data.index)}
|
>
|
||||||
>
|
<Trash2 size="16" class="mr-1" />
|
||||||
<Trash2 size="16" class="mr-1" />
|
{$_('menu.delete')}
|
||||||
{$_('menu.delete')}
|
<Shortcut key="" shift={true} click={true} />
|
||||||
<Shortcut key="" shift={true} click={true} />
|
</Button>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
@@ -209,10 +209,6 @@
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<Accordion.Item value="pois" class="hidden">
|
|
||||||
<Accordion.Trigger>{$_('layers.pois')}</Accordion.Trigger>
|
|
||||||
<Accordion.Content></Accordion.Content>
|
|
||||||
</Accordion.Item>
|
|
||||||
<Accordion.Item value="heatmap-color" class="hidden">
|
<Accordion.Item value="heatmap-color" class="hidden">
|
||||||
<Accordion.Trigger>{$_('layers.heatmap')}</Accordion.Trigger>
|
<Accordion.Trigger>{$_('layers.heatmap')}</Accordion.Trigger>
|
||||||
<Accordion.Content class="overflow-visible">
|
<Accordion.Content class="overflow-visible">
|
||||||
|
@@ -90,6 +90,7 @@ export class OverpassLayer {
|
|||||||
layout: {
|
layout: {
|
||||||
'icon-image': ['get', 'icon'],
|
'icon-image': ['get', 'icon'],
|
||||||
'icon-size': 0.25,
|
'icon-size': 0.25,
|
||||||
|
'icon-padding': 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,14 +174,13 @@ export class OverpassLayer {
|
|||||||
fetch(`${this.overpassUrl}?data=${getQueryForBounds(bounds, queries)}`)
|
fetch(`${this.overpassUrl}?data=${getQueryForBounds(bounds, queries)}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
try {
|
return response.json();
|
||||||
return response.json();
|
|
||||||
} catch (e) { }
|
|
||||||
}
|
}
|
||||||
this.currentQueries.delete(`${x},${y}`);
|
this.currentQueries.delete(`${x},${y}`);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}, () => (this.currentQueries.delete(`${x},${y}`)))
|
}, () => (this.currentQueries.delete(`${x},${y}`)))
|
||||||
.then((data) => this.storeOverpassData(x, y, queries, data));
|
.then((data) => this.storeOverpassData(x, y, queries, data))
|
||||||
|
.catch(() => this.currentQueries.delete(`${x},${y}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
storeOverpassData(x: number, y: number, queries: string[], data: any) {
|
storeOverpassData(x: number, y: number, queries: string[], data: any) {
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { overpassPopup, overpassPopupPOI } from './OverpassLayer';
|
import { overpassPopup, overpassPopupPOI } from './OverpassLayer';
|
||||||
import { PencilLine } from 'lucide-svelte';
|
import { selection } from '$lib/components/file-list/Selection';
|
||||||
|
import { PencilLine, MapPin } from 'lucide-svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
import { dbUtils } from '$lib/db';
|
||||||
|
|
||||||
let popupElement: HTMLDivElement;
|
let popupElement: HTMLDivElement;
|
||||||
|
|
||||||
@@ -47,23 +49,45 @@
|
|||||||
<img src={tags.image ?? tags['image:0']} />
|
<img src={tags.image ?? tags['image:0']} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Card.Content
|
<Card.Content class="flex flex-col p-0 text-sm mt-1 whitespace-normal break-all">
|
||||||
class="grid grid-cols-[auto_auto] gap-x-3 p-0 text-sm mt-1 whitespace-normal break-all"
|
<div class="grid grid-cols-[auto_auto] gap-x-3">
|
||||||
>
|
{#each Object.entries(tags) as [key, value]}
|
||||||
{#each Object.entries(tags) as [key, value]}
|
{#if key !== 'name' && !key.includes('image')}
|
||||||
{#if key !== 'name' && !key.includes('image')}
|
<span class="font-mono">{key}</span>
|
||||||
<span class="font-mono">{key}</span>
|
{#if key === 'website' || key === 'contact:website' || key === 'contact:facebook' || key === 'contact:instagram' || key === 'contact:twitter'}
|
||||||
{#if key === 'website' || key === 'contact:website' || key === 'contact:facebook' || key === 'contact:instagram' || key === 'contact:twitter'}
|
<a href={value} target="_blank" class="text-blue-500 underline">{value}</a>
|
||||||
<a href={value} target="_blank" class="text-blue-500 underline">{value}</a>
|
{:else if key === 'phone' || key === 'contact:phone'}
|
||||||
{:else if key === 'phone' || key === 'contact:phone'}
|
<a href={'tel:' + value} class="text-blue-500 underline">{value}</a>
|
||||||
<a href={'tel:' + value} class="text-blue-500 underline">{value}</a>
|
{:else if key === 'email' || key === 'contact:email'}
|
||||||
{:else if key === 'email' || key === 'contact:email'}
|
<a href={'mailto:' + value} class="text-blue-500 underline">{value}</a>
|
||||||
<a href={'mailto:' + value} class="text-blue-500 underline">{value}</a>
|
{:else}
|
||||||
{:else}
|
<span>{value}</span>
|
||||||
<span>{value}</span>
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
|
<Button
|
||||||
|
class="mt-2"
|
||||||
|
variant="outline"
|
||||||
|
disabled={$selection.size === 0}
|
||||||
|
on:click={() => {
|
||||||
|
let desc = Object.entries(tags)
|
||||||
|
.map(([key, value]) => `${key}: ${value}`)
|
||||||
|
.join('\n');
|
||||||
|
dbUtils.addOrUpdateWaypoint({
|
||||||
|
attributes: {
|
||||||
|
lat: $overpassPopupPOI.lat,
|
||||||
|
lon: $overpassPopupPOI.lon
|
||||||
|
},
|
||||||
|
name: tags.name ?? '',
|
||||||
|
desc: desc,
|
||||||
|
cmt: desc
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MapPin size="16" class="mr-1" />
|
||||||
|
{$_('toolbar.waypoint.add')}
|
||||||
|
</Button>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
{/if}
|
{/if}
|
||||||
|
@@ -102,39 +102,22 @@
|
|||||||
}
|
}
|
||||||
latitude = parseFloat(latitude.toFixed(6));
|
latitude = parseFloat(latitude.toFixed(6));
|
||||||
longitude = parseFloat(longitude.toFixed(6));
|
longitude = parseFloat(longitude.toFixed(6));
|
||||||
if ($selectedWaypoint) {
|
|
||||||
dbUtils.applyToFile($selectedWaypoint[1], (file) => {
|
dbUtils.addOrUpdateWaypoint(
|
||||||
let wpt = file.wpt[$selectedWaypoint[0]._data.index];
|
{
|
||||||
wpt.name = name;
|
|
||||||
wpt.desc = description;
|
|
||||||
wpt.cmt = description;
|
|
||||||
wpt.setCoordinates({
|
|
||||||
lat: latitude,
|
|
||||||
lon: longitude
|
|
||||||
});
|
|
||||||
wpt.ele =
|
|
||||||
get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let fileIds = new Set<string>();
|
|
||||||
$selection.getSelected().forEach((item) => {
|
|
||||||
fileIds.add(item.getFileId());
|
|
||||||
});
|
|
||||||
let waypoint = new Waypoint({
|
|
||||||
name,
|
|
||||||
desc: description,
|
|
||||||
cmt: description,
|
|
||||||
attributes: {
|
attributes: {
|
||||||
lat: latitude,
|
lat: latitude,
|
||||||
lon: longitude
|
lon: longitude
|
||||||
}
|
},
|
||||||
});
|
name,
|
||||||
waypoint.ele =
|
desc: description,
|
||||||
get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0;
|
cmt: description
|
||||||
dbUtils.applyToFiles(Array.from(fileIds), (file) =>
|
},
|
||||||
file.replaceWaypoints(file.wpt.length, file.wpt.length, [waypoint])
|
$selectedWaypoint
|
||||||
);
|
? new ListWaypointItem($selectedWaypoint[1], $selectedWaypoint[0]._data.index)
|
||||||
}
|
: undefined
|
||||||
|
);
|
||||||
|
|
||||||
selectedWaypoint.set(undefined);
|
selectedWaypoint.set(undefined);
|
||||||
resetWaypointData();
|
resetWaypointData();
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import Dexie, { liveQuery } from 'dexie';
|
import Dexie, { liveQuery } from 'dexie';
|
||||||
import { GPXFile, GPXStatistics, Track, TrackSegment, Waypoint, TrackPoint, type Coordinates, distance, type LineStyleExtension } from 'gpx';
|
import { GPXFile, GPXStatistics, Track, TrackSegment, Waypoint, TrackPoint, type Coordinates, distance, type LineStyleExtension, type WaypointType } from 'gpx';
|
||||||
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, freeze, produceWithPatches } from 'immer';
|
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, freeze, produceWithPatches } from 'immer';
|
||||||
import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
|
import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
|
||||||
import { gpxStatistics, initTargetMapBounds, splitAs, updateAllHidden, updateTargetMapBounds } from './stores';
|
import { gpxStatistics, initTargetMapBounds, map, splitAs, updateAllHidden, updateTargetMapBounds } from './stores';
|
||||||
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer, defaultOpacities, defaultOverpassQueries, defaultOverpassTree } from './assets/layers';
|
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer, defaultOpacities, defaultOverpassQueries, defaultOverpassTree } from './assets/layers';
|
||||||
import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
|
import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
|
||||||
import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList';
|
import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList';
|
||||||
@@ -893,6 +893,29 @@ export const dbUtils = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addOrUpdateWaypoint: (waypoint: WaypointType, item?: ListWaypointItem) => {
|
||||||
|
let ele = get(map)?.queryTerrainElevation([waypoint.attributes.lon, waypoint.attributes.lat], { exaggerated: false }) ?? 0;
|
||||||
|
if (item) {
|
||||||
|
dbUtils.applyToFile(item.getFileId(), (file) => {
|
||||||
|
let wpt = file.wpt[item.getWaypointIndex()];
|
||||||
|
wpt.name = waypoint.name;
|
||||||
|
wpt.desc = waypoint.desc;
|
||||||
|
wpt.cmt = waypoint.cmt;
|
||||||
|
wpt.setCoordinates(waypoint.attributes);
|
||||||
|
wpt.ele = ele;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let fileIds = new Set<string>();
|
||||||
|
get(selection).getSelected().forEach((item) => {
|
||||||
|
fileIds.add(item.getFileId());
|
||||||
|
});
|
||||||
|
let wpt = new Waypoint(waypoint);
|
||||||
|
wpt.ele = ele;
|
||||||
|
dbUtils.applyToFiles(Array.from(fileIds), (file) =>
|
||||||
|
file.replaceWaypoints(file.wpt.length, file.wpt.length, [wpt])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
setStyleToSelection: (style: LineStyleExtension) => {
|
setStyleToSelection: (style: LineStyleExtension) => {
|
||||||
if (get(selection).size === 0) {
|
if (get(selection).size === 0) {
|
||||||
return;
|
return;
|
||||||
|
@@ -182,6 +182,7 @@
|
|||||||
"longitude": "Longitude",
|
"longitude": "Longitude",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"create": "Create point of interest",
|
"create": "Create point of interest",
|
||||||
|
"add": "Add point of interest to file",
|
||||||
"help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.",
|
"help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.",
|
||||||
"help_no_selection": "Select a file item to create or edit points of interest."
|
"help_no_selection": "Select a file item to create or edit points of interest."
|
||||||
},
|
},
|
||||||
@@ -223,7 +224,6 @@
|
|||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
"heatmap": "Strava Heatmap",
|
"heatmap": "Strava Heatmap",
|
||||||
"pois": "Points of interest",
|
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -302,7 +302,7 @@
|
|||||||
"tourism": "Tourism",
|
"tourism": "Tourism",
|
||||||
"attraction": "Attraction",
|
"attraction": "Attraction",
|
||||||
"viewpoint": "Viewpoint",
|
"viewpoint": "Viewpoint",
|
||||||
"sleep": "Sleep",
|
"accommodation": "Accommodation",
|
||||||
"summit": "Summit",
|
"summit": "Summit",
|
||||||
"pass": "Pass",
|
"pass": "Pass",
|
||||||
"climbing": "Climbing",
|
"climbing": "Climbing",
|
||||||
|
Reference in New Issue
Block a user