mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-15 07:07:07 +00:00
show hovered point on elevation profile
This commit is contained in:
1 parent
b80fa7d942
commit
21e27a4416
15 files changed
+260
-106
No files matched your search
@@ -5,6 +5,7 @@
|
||||
import { StartEndMarkers } from '$lib/components/map/gpx-layer/start-end-markers';
|
||||
import { createPopups, removePopups } from '$lib/components/map/gpx-layer/gpx-layer-popup';
|
||||
import { map } from '$lib/components/map/map';
|
||||
import { hoveredTrackpoint } from '$lib/state/selection-statistics';
|
||||
|
||||
let distanceMarkers: DistanceMarkers;
|
||||
let startEndMarkers: StartEndMarkers;
|
||||
@@ -14,6 +15,11 @@
|
||||
startEndMarkers = new StartEndMarkers();
|
||||
distanceMarkers = new DistanceMarkers();
|
||||
createPopups(map_);
|
||||
// the layers do not see the pointer when it leaves the map or when it drags something
|
||||
const stopHovering = () => hoveredTrackpoint.set(null);
|
||||
map_.on('mouseout', stopHovering);
|
||||
map_.on('mousedown', stopHovering);
|
||||
map_.on('movestart', stopHovering);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import CopyCoordinates from '$lib/components/map/gpx-layer/CopyCoordinates.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { Compass, Earth, Mountain, Timer } from '@lucide/svelte';
|
||||
import { Compass, Earth } from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import type { PopupItem } from '$lib/components/map/map-popup';
|
||||
import type { TrackpointDetails } from '$lib/engine';
|
||||
import { map } from '$lib/components/map/map';
|
||||
|
||||
let {
|
||||
trackpoint,
|
||||
}: {
|
||||
trackpoint: PopupItem<Pick<TrackpointDetails, 'lng' | 'lat'> & Partial<TrackpointDetails>>;
|
||||
trackpoint: PopupItem<{ lng: number; lat: number }>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
@@ -22,33 +20,19 @@
|
||||
<Compass size="14" />
|
||||
{trackpoint.item.lat.toFixed(6)}° {trackpoint.item.lng.toFixed(6)}°
|
||||
</div>
|
||||
{#if trackpoint.item.ele !== undefined}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Mountain size="14" />
|
||||
<WithUnits value={trackpoint.item.ele} type="elevation" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if trackpoint.item.time !== undefined}
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<Timer size="14" />
|
||||
{i18n.df.format(new Date(trackpoint.item.time))}
|
||||
</div>
|
||||
{/if}
|
||||
<CopyCoordinates
|
||||
coordinates={trackpoint.item}
|
||||
onCopy={() => trackpoint.hide?.()}
|
||||
class="mt-0.5"
|
||||
/>
|
||||
{#if trackpoint.fileId === undefined}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="justify-start"
|
||||
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.lat.toFixed(5)}/${trackpoint.item.lng.toFixed(5)}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Earth size="14" />
|
||||
{i18n._('menu.edit_osm')}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="justify-start"
|
||||
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.lat.toFixed(5)}/${trackpoint.item.lng.toFixed(5)}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Earth size="14" />
|
||||
{i18n._('menu.edit_osm')}
|
||||
</Button>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
Point,
|
||||
} from 'maplibre-gl';
|
||||
import { map } from '$lib/components/map/map';
|
||||
import { waypointPopup, trackpointPopup } from './gpx-layer-popup';
|
||||
import { closestPointIndex } from '$lib/geo/closest-point';
|
||||
import { waypointPopup } from './gpx-layer-popup';
|
||||
import { closestPointIndexIn } from '$lib/geo/closest-point';
|
||||
import { getElevation, loadSVGIcon } from '$lib/utils';
|
||||
import { selectedWaypoint } from '$lib/components/toolbar/tools/waypoint/waypoint';
|
||||
import { MapPin, Square } from 'lucide-static';
|
||||
@@ -21,6 +21,7 @@ import { currentTool, Tool } from '$lib/components/toolbar/tools';
|
||||
import { splitAtPoint } from '$lib/components/toolbar/tools/scissors/scissors';
|
||||
import { mapCursor, MapCursorState } from '$lib/state/map-cursor';
|
||||
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
|
||||
import { hoveredTrackpoint } from '$lib/state/selection-statistics';
|
||||
|
||||
export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) {
|
||||
const symbolSvg = symbol ? symbols[symbol]?.iconSvg : undefined;
|
||||
@@ -63,6 +64,7 @@ export class GPXLayer {
|
||||
currentWaypointData: GeoJSON.FeatureCollection | null = null;
|
||||
draggedWaypointIndex: number | null = null;
|
||||
draggingStartingPosition: Point = new Point(0, 0);
|
||||
hoversTrackpoint: boolean = false;
|
||||
unsubscribe: (() => void)[] = [];
|
||||
|
||||
updateBinded: () => void = this.update.bind(this);
|
||||
@@ -400,25 +402,50 @@ export class GPXLayer {
|
||||
layerOnMouseLeave() {
|
||||
mapCursor.notify(MapCursorState.SCISSORS, false);
|
||||
mapCursor.notify(MapCursorState.LAYER_HOVER, false);
|
||||
this.stopHoveringTrackpoint();
|
||||
}
|
||||
|
||||
async layerOnMouseMove(e: MapLayerMouseEvent) {
|
||||
if (e.originalEvent.shiftKey) {
|
||||
const { segmentId } = e.features![0].properties;
|
||||
const lngLat = { lng: e.lngLat.lng, lat: e.lngLat.lat };
|
||||
const index = closestPointIndex(await engine.segmentCoordinates(segmentId), lngLat);
|
||||
const trackpoint =
|
||||
index === undefined
|
||||
? undefined
|
||||
: await engine.trackpoint(this.fileId, segmentId, index);
|
||||
if (trackpoint) {
|
||||
trackpointPopup?.setItem({
|
||||
item: trackpoint,
|
||||
kind: 'trackpoint',
|
||||
fileId: this.fileId,
|
||||
});
|
||||
layerOnMouseMove(e: MapLayerMouseEvent) {
|
||||
const index = this.selectedTrackpointAt(e);
|
||||
if (index === undefined) {
|
||||
this.stopHoveringTrackpoint();
|
||||
} else {
|
||||
this.hoversTrackpoint = true;
|
||||
hoveredTrackpoint.set(index);
|
||||
}
|
||||
}
|
||||
|
||||
stopHoveringTrackpoint() {
|
||||
if (this.hoversTrackpoint) {
|
||||
this.hoversTrackpoint = false;
|
||||
hoveredTrackpoint.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The trackpoint of the statistics of the selection that is the closest to the pointer, in the
|
||||
* first segment under it that is part of the selection.
|
||||
*/
|
||||
selectedTrackpointAt(e: MapLayerMouseEvent): number | undefined {
|
||||
const statistics = get(engine.statistics);
|
||||
if (!statistics.segments) {
|
||||
return undefined;
|
||||
}
|
||||
const { starts, ids } = statistics.segments;
|
||||
for (const { properties } of e.features!) {
|
||||
const segment = ids.indexOf(properties.segmentId);
|
||||
if (segment >= 0) {
|
||||
const end = segment + 1 < starts.length ? starts[segment + 1] : statistics.length;
|
||||
return closestPointIndexIn(
|
||||
statistics.lng,
|
||||
statistics.lat,
|
||||
starts[segment],
|
||||
end,
|
||||
e.lngLat
|
||||
);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
layerOnClick(e: MapLayerMouseEvent) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileStateCollectionObserver } from '$lib/engine';
|
||||
import { engine, FileStateCollectionObserver } from '$lib/engine';
|
||||
import { writable } from 'svelte/store';
|
||||
import { GPXLayer } from './gpx-layer';
|
||||
|
||||
@@ -14,6 +14,7 @@ export class GPXLayerCollection {
|
||||
if (this._fileStateCollectionObserver) {
|
||||
return;
|
||||
}
|
||||
engine.requestStatistics().set(['segments']);
|
||||
this._fileStateCollectionObserver = new FileStateCollectionObserver(
|
||||
(newFiles) => {
|
||||
newFiles.forEach((fileStore, fileId) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import MapPopupComponent from '$lib/components/map/MapPopup.svelte';
|
||||
export type PopupItem<T = any> = {
|
||||
/** Has a position (`lng` and `lat`). */
|
||||
item: T;
|
||||
/** What the item is: a `WaypointDetails`, a `TrackpointDetails` or an Overpass element. */
|
||||
/** What the item is: a `WaypointDetails`, the coordinates of a point or an Overpass element. */
|
||||
kind: 'waypoint' | 'trackpoint' | 'overpass';
|
||||
fileId?: string;
|
||||
hide?: () => void;
|
||||
|
||||
Reference in new issue
Block a user