mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
add overpass poi to file
This commit is contained in:
@@ -561,7 +561,7 @@ export const overpassTree: LayerTreeType = {
|
||||
tourism: {
|
||||
attraction: true,
|
||||
viewpoint: true,
|
||||
sleep: true,
|
||||
accommodation: true,
|
||||
summit: true,
|
||||
pass: true,
|
||||
climbing: true,
|
||||
@@ -645,7 +645,7 @@ export const defaultOverpassQueries: LayerTreeType = {
|
||||
tourism: {
|
||||
attraction: false,
|
||||
viewpoint: false,
|
||||
sleep: false,
|
||||
accommodation: false,
|
||||
summit: false,
|
||||
pass: false,
|
||||
climbing: false
|
||||
@@ -770,30 +770,30 @@ export const defaultOverpassTree: LayerTreeType = {
|
||||
amenities: {
|
||||
toilets: true,
|
||||
"water": true,
|
||||
"water-spring": true,
|
||||
shower: true,
|
||||
"water-spring": false,
|
||||
shower: false,
|
||||
"fuel-station": false,
|
||||
parking: false,
|
||||
barrier: false
|
||||
},
|
||||
tourism: {
|
||||
attraction: true,
|
||||
viewpoint: true,
|
||||
sleep: true,
|
||||
attraction: false,
|
||||
viewpoint: false,
|
||||
accommodation: true,
|
||||
summit: true,
|
||||
pass: true,
|
||||
climbing: false
|
||||
},
|
||||
bicycle: {
|
||||
"bicycle-parking": true,
|
||||
"bicycle-rental": true,
|
||||
"bicycle-parking": false,
|
||||
"bicycle-rental": false,
|
||||
"bicycle-shop": true
|
||||
},
|
||||
"public-transport": {
|
||||
"railway-station": true,
|
||||
"tram-stop": true,
|
||||
"bus-stop": true,
|
||||
ferry: true
|
||||
ferry: false
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -926,10 +926,10 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
|
||||
tourism: "viewpoint"
|
||||
}
|
||||
},
|
||||
sleep: {
|
||||
accommodation: {
|
||||
icon: {
|
||||
svg: Bed,
|
||||
color: "Green",
|
||||
color: "#e6c100",
|
||||
},
|
||||
tags: {
|
||||
tourism: ["hotel", "hostel", "guest_house", "motel", "camp_site", "alpine_hut", "wilderness_hut"]
|
||||
|
@@ -34,24 +34,22 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if $currentPopupWaypoint[0].desc}
|
||||
<span>{$currentPopupWaypoint[0].desc}</span>
|
||||
<span class="whitespace-pre-wrap">{$currentPopupWaypoint[0].desc}</span>
|
||||
{/if}
|
||||
{#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 $currentTool === Tool.WAYPOINT}
|
||||
<div class="mt-2">
|
||||
<Button
|
||||
class="w-full px-2 py-1 h-6 justify-start"
|
||||
variant="ghost"
|
||||
on:click={() =>
|
||||
deleteWaypoint($currentPopupWaypoint[1], $currentPopupWaypoint[0]._data.index)}
|
||||
>
|
||||
<Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut key="" shift={true} click={true} />
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
class="mt-2 w-full px-2 py-1 h-8 justify-start"
|
||||
variant="outline"
|
||||
on:click={() =>
|
||||
deleteWaypoint($currentPopupWaypoint[1], $currentPopupWaypoint[0]._data.index)}
|
||||
>
|
||||
<Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut key="" shift={true} click={true} />
|
||||
</Button>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
@@ -209,10 +209,6 @@
|
||||
</ScrollArea>
|
||||
</Accordion.Content>
|
||||
</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.Trigger>{$_('layers.heatmap')}</Accordion.Trigger>
|
||||
<Accordion.Content class="overflow-visible">
|
||||
|
@@ -90,6 +90,7 @@ export class OverpassLayer {
|
||||
layout: {
|
||||
'icon-image': ['get', 'icon'],
|
||||
'icon-size': 0.25,
|
||||
'icon-padding': 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -173,14 +174,13 @@ export class OverpassLayer {
|
||||
fetch(`${this.overpassUrl}?data=${getQueryForBounds(bounds, queries)}`)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
try {
|
||||
return response.json();
|
||||
} catch (e) { }
|
||||
return response.json();
|
||||
}
|
||||
this.currentQueries.delete(`${x},${y}`);
|
||||
return Promise.reject();
|
||||
}, () => (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) {
|
||||
|
@@ -2,9 +2,11 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
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 { _ } from 'svelte-i18n';
|
||||
import { dbUtils } from '$lib/db';
|
||||
|
||||
let popupElement: HTMLDivElement;
|
||||
|
||||
@@ -47,23 +49,45 @@
|
||||
<img src={tags.image ?? tags['image:0']} />
|
||||
</div>
|
||||
{/if}
|
||||
<Card.Content
|
||||
class="grid grid-cols-[auto_auto] gap-x-3 p-0 text-sm mt-1 whitespace-normal break-all"
|
||||
>
|
||||
{#each Object.entries(tags) as [key, value]}
|
||||
{#if key !== 'name' && !key.includes('image')}
|
||||
<span class="font-mono">{key}</span>
|
||||
{#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>
|
||||
{:else if key === 'phone' || key === 'contact:phone'}
|
||||
<a href={'tel:' + value} class="text-blue-500 underline">{value}</a>
|
||||
{:else if key === 'email' || key === 'contact:email'}
|
||||
<a href={'mailto:' + value} class="text-blue-500 underline">{value}</a>
|
||||
{:else}
|
||||
<span>{value}</span>
|
||||
<Card.Content class="flex flex-col 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]}
|
||||
{#if key !== 'name' && !key.includes('image')}
|
||||
<span class="font-mono">{key}</span>
|
||||
{#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>
|
||||
{:else if key === 'phone' || key === 'contact:phone'}
|
||||
<a href={'tel:' + value} class="text-blue-500 underline">{value}</a>
|
||||
{:else if key === 'email' || key === 'contact:email'}
|
||||
<a href={'mailto:' + value} class="text-blue-500 underline">{value}</a>
|
||||
{:else}
|
||||
<span>{value}</span>
|
||||
{/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.Root>
|
||||
{/if}
|
||||
|
@@ -102,39 +102,22 @@
|
||||
}
|
||||
latitude = parseFloat(latitude.toFixed(6));
|
||||
longitude = parseFloat(longitude.toFixed(6));
|
||||
if ($selectedWaypoint) {
|
||||
dbUtils.applyToFile($selectedWaypoint[1], (file) => {
|
||||
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,
|
||||
|
||||
dbUtils.addOrUpdateWaypoint(
|
||||
{
|
||||
attributes: {
|
||||
lat: latitude,
|
||||
lon: longitude
|
||||
}
|
||||
});
|
||||
waypoint.ele =
|
||||
get(map)?.queryTerrainElevation([longitude, latitude], { exaggerated: false }) ?? 0;
|
||||
dbUtils.applyToFiles(Array.from(fileIds), (file) =>
|
||||
file.replaceWaypoints(file.wpt.length, file.wpt.length, [waypoint])
|
||||
);
|
||||
}
|
||||
},
|
||||
name,
|
||||
desc: description,
|
||||
cmt: description
|
||||
},
|
||||
$selectedWaypoint
|
||||
? new ListWaypointItem($selectedWaypoint[1], $selectedWaypoint[0]._data.index)
|
||||
: undefined
|
||||
);
|
||||
|
||||
selectedWaypoint.set(undefined);
|
||||
resetWaypointData();
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
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 { 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 { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
|
||||
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) => {
|
||||
if (get(selection).size === 0) {
|
||||
return;
|
||||
|
@@ -182,6 +182,7 @@
|
||||
"longitude": "Longitude",
|
||||
"latitude": "Latitude",
|
||||
"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_no_selection": "Select a file item to create or edit points of interest."
|
||||
},
|
||||
@@ -223,7 +224,6 @@
|
||||
},
|
||||
"opacity": "Overlay opacity",
|
||||
"heatmap": "Strava Heatmap",
|
||||
"pois": "Points of interest",
|
||||
"label": {
|
||||
"basemaps": "Basemaps",
|
||||
"overlays": "Overlays",
|
||||
@@ -302,7 +302,7 @@
|
||||
"tourism": "Tourism",
|
||||
"attraction": "Attraction",
|
||||
"viewpoint": "Viewpoint",
|
||||
"sleep": "Sleep",
|
||||
"accommodation": "Accommodation",
|
||||
"summit": "Summit",
|
||||
"pass": "Pass",
|
||||
"climbing": "Climbing",
|
||||
|
Reference in New Issue
Block a user