adds map_with_elevation_multiple

This commit is contained in:
Christian Beutel
2024-09-10 00:06:21 +02:00
parent da658fdf27
commit 86c377225b
22 changed files with 559 additions and 247 deletions

View File

@@ -1,6 +1,11 @@
<script lang="ts">
import type { List } from "$lib/models/list";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import { _ } from "svelte-i18n";
export let list: List;
@@ -10,6 +15,21 @@
{ text: $_("edit"), value: "edit" },
{ text: $_("delete"), value: "delete" },
];
$: cumulativeDistance = list.expand?.trails.reduce(
(s, b) => s + b.distance!,
0,
);
$: cumulativeElevationGain = list.expand?.trails.reduce(
(s, b) => s + b.elevation_gain!,
0,
);
$: cumulativeDuration = list.expand?.trails.reduce(
(s, b) => s + b.duration!,
0,
);
</script>
<div
@@ -18,13 +38,13 @@
>
{#if list.avatar}
<img
class="w-16 md:w-24 aspect-square rounded-full object-cover"
class="w-16 md:w-20 aspect-square rounded-full object-cover"
src={getFileURL(list, list.avatar)}
alt="avatar"
/>
{:else}
<div
class="flex w-16 md:w-24 aspect-square shrink-0 items-center justify-center"
class="flex w-16 md:w-20 aspect-square shrink-0 items-center justify-center"
>
<i class="fa fa-table-list text-5xl"></i>
</div>
@@ -36,13 +56,36 @@
</h5>
<Dropdown items={dropdownItems} on:change></Dropdown>
</div>
<div class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap">
<span
><i class="fa fa-left-right mr-2"></i>{formatDistance(
cumulativeDistance,
)}</span
>
<span
><i class="fa fa-up-down mr-2"></i>{formatElevation(
cumulativeElevationGain,
)}</span
>
<span
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
cumulativeDuration,
)}</span
>
</div>
<p class="text-sm text-gray-500 mb-2"
>{list.expand?.trails.length ?? 0}
{$_("trail", {
values: { n: list.expand?.trails.length ?? 0 },
})}</p
>
<p
class="text-gray-500 text-sm mr-8 whitespace-pre-wrap {active
? ''
: 'max-h-24 overflow-hidden text-ellipsis'}"
>
{!active ? list.description?.substring(0, 100) : list.description}
{#if ((list.description?.length ?? 0) > 100) && !active}
{#if (list.description?.length ?? 0) > 100 && !active}
...
{/if}
</p>

View File

@@ -2,43 +2,41 @@
import { page } from "$app/stores";
import { Settings } from "$lib/models/settings";
import type { Trail } from "$lib/models/trail";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import { createMarkerFromWaypoint, endIcon, startIcon } from "$lib/util/leaflet_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
import type { Layer, Map, Marker, Polyline } from "leaflet";
import type { Map, Marker } from "leaflet";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
import Dropdown from "../base/dropdown.svelte";
export let trails: Trail[];
export let trail: Trail | null;
export let markers: Marker[] = [];
export let map: Map | null = null;
export let options: any = {};
export let graticule: AutoGraticule | null = null;
export let crosshair: boolean = false;
export let activeTrailIndex: number = 0;
const dispatch = createEventDispatcher();
let L: any;
let gpxGroup: any;
let controlElevation: any;
let selectedMetric: "altitude" | "slope" | "speed" | false = "altitude";
$: gpxData = trails.map((t) => t.expand.gpx_data);
$: if (gpxData && gpxGroup) {
gpxGroup._elevation.updateOptions({
$: gpxData = trail?.expand.gpx_data;
$: if (gpxData && controlElevation) {
controlElevation.updateOptions({
autofitBounds: options.autofitBounds ?? true,
});
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
controlElevation.clear();
controlElevation.load(gpxData);
}
$: if (options && gpxGroup) {
gpxGroup._elevation.updateOptions(options);
$: if (options) {
controlElevation?.updateOptions(options);
}
$: hotlineSwitcherItems = [
@@ -68,21 +66,14 @@
L = (await import("leaflet")).default;
await import("leaflet-gpx");
await import("leaflet.awesome-markers");
//@ts-ignore
await import("$lib/vendor/leaflet-elevation/src/index.js");
await import("$lib/vendor/leaflet-elevation/libs/leaflet-gpxgroup");
const AutoGraticule = (
await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")
).default;
map = L.map("map", {
preferCanvas: true,
plugins: ["/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js"],
}).setView(
[
trails.at(activeTrailIndex)?.lat ?? 0,
trails.at(activeTrailIndex)?.lon ?? 0,
],
map = L.map("map", { preferCanvas: true }).setView(
[trail?.lat ?? 0, trail?.lon ?? 0],
3,
);
map!.attributionControl.setPrefix(false);
@@ -112,9 +103,7 @@
const baseMaps: Record<string, L.TileLayer> = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
...($page.data.settings as Settings)?.tilesets?.reduce<
Record<string, string>
>((t, current) => {
...($page.data.settings as Settings)?.tilesets?.reduce< Record<string, string>>((t, current) => {
t[current.name] = L.tileLayer(current.url);
return t;
}, {}),
@@ -170,7 +159,7 @@
lazy: false,
distance: false,
direction: true,
offset: 1000,
offset: 500,
},
// Toggle "leaflet-edgescale" integration
edgeScale: false,
@@ -182,6 +171,14 @@
wptIcons: false,
wptLabels: false,
preferCanvas: true,
trkStart: {
interactive: false,
className: "hihi",
icon: startIcon()
},
trkEnd: {
icon: endIcon(),
},
graticule: false,
drawing: false,
};
@@ -191,34 +188,13 @@
options,
);
gpxGroup = L.gpxGroup(gpxData, {
points: [],
// points_options: opts.points,
elevation: true,
elevation_options: elevation_options,
flyToBounds: true,
distanceMarkers: true,
});
controlElevation = L.control.elevation(elevation_options).addTo(map);
const markerLayerGroup = L.layerGroup().addTo(map);
gpxGroup.on("selection_changed", ({ polyline }: { polyline: any }) => {
markerLayerGroup.clearLayers();
activeTrailIndex = polyline.options.index ?? 0;
if (polyline._selected) {
for (const waypoint of trails.at(activeTrailIndex)?.expand
.waypoints ?? []) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(markerLayerGroup!);
markers.push(marker);
}
}
});
gpxGroup.addTo(map);
// controlElevation = L.control.elevation(elevation_options).addTo(map);
for (const waypoint of trail?.expand.waypoints ?? []) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(map!);
markers.push(marker);
}
if (elevation_options.graticule) {
graticule = new AutoGraticule();
@@ -227,15 +203,14 @@
});
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
gpxGroup._elevation.updateOptions({
controlElevation.updateOptions({
hotline: metric,
autofitBounds: false,
});
selectedMetric = metric;
localStorage.setItem("gradient", metric.toString());
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
controlElevation.clear();
controlElevation.load(gpxData);
}
</script>
@@ -266,4 +241,4 @@
<slot />
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,321 @@
<script lang="ts">
import { page } from "$app/stores";
import { Settings } from "$lib/models/settings";
import type { Trail } from "$lib/models/trail";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
import Dropdown from "../base/dropdown.svelte";
export let trails: Trail[];
export let map: any | null = null;
export let options: any = {};
export let activeTrailIndex: number | null = 0;
const dispatch = createEventDispatcher();
let L: any;
let gpxGroup: any;
let selectedMetric: "altitude" | "slope" | "speed" | false = "altitude";
$: gpxData = trails.map((t) => t.expand.gpx_data);
$: if (
gpxData &&
gpxGroup &&
map &&
(gpxData.length != gpxGroup._tracks.length ||
gpxGroup?._tracks[0] !== gpxData[0])
) {
if (gpxData.length == 0) {
map?.setView([0, 0], 4);
}
gpxGroup._elevation.updateOptions({
autofitBounds: options.autofitBounds ?? true,
});
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
}
$: if (options && gpxGroup) {
gpxGroup._elevation.updateOptions(options);
}
$: hotlineSwitcherItems = [
{
text: $_("altitude"),
value: "altitude",
icon: selectedMetric == "altitude" ? "circle-dot" : "circle",
},
{
text: $_("slope"),
value: "slope",
icon: selectedMetric == "slope" ? "circle-dot" : "circle",
},
{
text: $_("speed"),
value: "speed",
icon: selectedMetric == "speed" ? "circle-dot" : "circle",
},
{
text: $_("off"),
value: false,
icon: selectedMetric == false ? "circle-dot" : "circle",
},
];
onMount(async () => {
L = (await import("leaflet")).default;
await import("leaflet-gpx");
await import("leaflet.awesome-markers");
await import("$lib/vendor/leaflet-elevation/src/index.js");
await import("$lib/vendor/leaflet-elevation/libs/leaflet-gpxgroup");
const AutoGraticule = (
await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")
).default;
map = L.map("map", {
preferCanvas: true,
plugins: ["/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js"],
}).setView(
[
trails.at(activeTrailIndex ?? 0)?.lat ?? 0,
trails.at(activeTrailIndex ?? 0)?.lon ?? 0,
],
3,
);
map!.attributionControl.setPrefix(false);
map!.on("zoomend", function () {
dispatch("zoomend", map);
});
map!.on("click", function (e: any) {
dispatch("click", e);
});
const baseLayer = L.tileLayer(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
{
attribution: "© OpenStreetMap contributors",
},
);
const topoLayer = L.tileLayer(
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
{
attribution: "© OpenStreetMap contributors",
},
);
const baseMaps: Record<string, L.TileLayer> = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
...($page.data.settings as Settings)?.tilesets?.reduce<
Record<string, string>
>((t, current) => {
t[current.name] = L.tileLayer(current.url);
return t;
}, {}),
};
L.control.layers(baseMaps).addTo(map);
const layerPreference = localStorage.getItem("layer");
if (
layerPreference &&
Object.keys(baseMaps).includes(layerPreference)
) {
baseMaps[layerPreference].addTo(map!);
} else {
baseLayer.addTo(map);
}
map!.on("baselayerchange", function (e: any) {
localStorage.setItem("layer", e.name);
});
const localMetric =
(localStorage.getItem("gradient") as any) ?? "altitude";
selectedMetric = localMetric === "false" ? false : localMetric;
const default_elevation_options = {
height: 200,
theme: "lightblue-theme",
detached: true,
elevationDiv: "#elevation",
closeBtn: false,
followMarker: true,
autofitBounds: true,
imperial: $page.data.settings?.unit == "imperial" ?? false,
reverseCoords: false,
acceleration: false,
slope: true,
speed: true,
altitude: true,
time: true,
distance: true,
// Summary track info style: "inline" || "multiline" || false
summary: false,
downloadLink: false,
ruler: false,
legend: true,
// Toggle "leaflet-almostover" integration
almostOver: false,
// Toggle "leaflet-distance-markers" integration
distanceMarkers: {
lazy: false,
distance: false,
direction: true,
offset: 1000,
},
// Toggle "leaflet-edgescale" integration
edgeScale: false,
// Toggle "leaflet-hotline" integration
hotline: selectedMetric,
// Display track datetimes: true || false
timestamps: false,
waypoints: false,
wptIcons: false,
wptLabels: false,
preferCanvas: true,
graticule: false,
drawing: false,
};
const elevation_options = Object.assign(
default_elevation_options,
options,
);
gpxGroup = L.gpxGroup(gpxData, {
points: [],
// points_options: opts.points,
elevation: true,
elevation_options: elevation_options,
flyToBounds: true,
distanceMarkers: true,
removeElevationOnDeselect: options.removeElevationOnDeselect,
});
const markerLayerGroup = L.layerGroup().addTo(map);
gpxGroup.on("selection_changed", ({ polyline }: { polyline: any }) => {
markerLayerGroup.clearLayers();
activeTrailIndex = null;
if (polyline._selected) {
activeTrailIndex = polyline.options.index;
for (const waypoint of trails.at(activeTrailIndex!)?.expand
.waypoints ?? []) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(markerLayerGroup!);
}
}
});
gpxGroup.on("clear", () => {
markerLayerGroup.clearLayers();
activeTrailIndex = null;
});
gpxGroup.on("route_loaded", ({ route }: { route: any }) => {
const trail = trails.find((t) => gpxGroup?._hashCode(t.expand.gpx_data) == route.options.hash)
if (!trail) {
return;
}
const thumbnail = trail.photos.length
? getFileURL(trail, trail.photos[trail.thumbnail])
: "/imgs/default_thumbnail.webp";
route.bindPopup(
`<a href="/trail/view/${trail.id}" data-sveltekit-preload-data="false">
<li class="flex items-center gap-4 cursor-pointer text-black max-w-80">
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${thumbnail}" alt="">
</div>
<div>
<h4 class="font-semibold text-lg">${trail.name}</h4>
<div class="flex gap-x-4">
${trail.location ? `<h5><i class="fa fa-location-dot mr-2"></i>${trail.location}</h5>` : ""}
<h5><i class="fa fa-gauge mr-2"></i>${$_(trail.difficulty as string)}</h5>
</div>
<div class="flex mt-2 gap-4 text-sm text-gray-500 flex-wrap"><span class="shrink-0"><i
class="fa fa-left-right mr-2"></i>${formatDistance(
trail.distance,
)}</span> <span class="shrink-0"><i class="fa fa-up-down mr-2"></i>${formatElevation(
trail.elevation_gain,
)}</span> <span class="shrink-0"><i class="fa fa-clock mr-2"></i>${formatTimeHHMM(
trail.duration,
)}</span></div>
</div>
</li>
</a>`,
);
});
gpxGroup.addTo(map);
});
export function selectTrail(index: number) {
gpxGroup.select(index);
}
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
if (metric === selectedMetric) {
return;
}
gpxGroup._elevation.updateOptions({
hotline: metric,
});
gpxGroup.options.flyToBounds = false;
selectedMetric = metric;
localStorage.setItem("gradient", metric.toString());
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
gpxGroup.options.flyToBounds = true;
}
</script>
<div id="map-container" class="flex flex-col h-full">
<div
id="map"
class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
style="position: relative; outline-style: none;"
>
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
<Dropdown
items={hotlineSwitcherItems}
on:change={(e) => switchHotline(e.detail.value)}
let:toggleMenu={openDropdown}
>
<button
class="rounded-md border-2 border-black border-opacity-30 bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
on:click={openDropdown}
>
<i class="fa fa-route text-lg"></i>
</button>
</Dropdown>
</div>
</div>
<div class="flex items-center justify-between">
<slot />
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
</div>
</div>

View File

@@ -106,6 +106,7 @@
"license": "Lizenz",
"link-copied": "Link kopiert",
"list": "{n, plural, =1 {Liste} other {Listen}}",
"list-saved-successfully": "",
"location": "Standort",
"login": "Login",
"login-details": "Login Details",

View File

@@ -106,6 +106,7 @@
"license": "License",
"link-copied": "Link copied!",
"list": "{n, plural, =1 {List} other {Lists}}",
"list-saved-successfully": "List saved successfully",
"location": "Location",
"login": "Login",
"login-details": "Login details",

View File

@@ -106,6 +106,7 @@
"license": "Licence",
"link-copied": "",
"list": "{n, plural, =1 {Liste} other {Listes}}",
"list-saved-successfully": "",
"location": "Localisation",
"login": "Connexion",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "License",
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listák}}",
"list-saved-successfully": "",
"location": "Helyszín",
"login": "Bejelentkezés",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "Licenza",
"link-copied": "Link copiato",
"list": "{n, plural, =1 {Lista} other {Liste}}",
"list-saved-successfully": "",
"location": "Posizione",
"login": "Login",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "Licentie",
"link-copied": "",
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
"list-saved-successfully": "",
"location": "Locatie",
"login": "Inloggen",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "Licencja",
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listy}}",
"list-saved-successfully": "",
"location": "Lokalizacja",
"login": "Zaloguj się",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "Licença",
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listas}}",
"list-saved-successfully": "",
"location": "Localização",
"login": "Login",
"login-details": "",

View File

@@ -106,6 +106,7 @@
"license": "开源协议",
"link-copied": "",
"list": "{n, plural, =1 {列表} other {列表}}",
"list-saved-successfully": "",
"location": "地点",
"login": "登录",
"login-details": "",

View File

@@ -4,6 +4,7 @@ import { pb } from "$lib/pocketbase";
import { getFileURL } from "$lib/util/file_util";
import { ClientResponseError } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { fetchGPX } from "./trail_store";
export const lists: Writable<List[]> = writable([])
export const list: Writable<List> = writable(new List("", []))
@@ -38,6 +39,12 @@ export async function lists_show(id: string, f: (url: RequestInfo | URL, config?
})
const response = await r.json()
for (const trail of response.expand.trails) {
const gpxData: string = await fetchGPX(trail);
trail.expand.gpx_data = gpxData;
}
if (!r.ok) {
throw new ClientResponseError(response)
}

View File

@@ -157,7 +157,7 @@ export async function trails_show(id: string, loadGPX?: boolean, f: (url: Reques
trail.set(response);
return response;
return response as Trail;
}
export async function trails_create(trail: Trail, photos: File[], gpx: File | Blob | null, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {

View File

@@ -1,17 +1,24 @@
import type { Waypoint } from "$lib/models/waypoint";
import type { Icon, LatLng, LeafletEvent, Map, Marker } from "leaflet";
import type { LeafletEvent, Map, Marker } from "leaflet";
export const startIcon = () => L.divIcon({
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-bullseye -translate-x-1/2"></i>',
className: 'start-icon'
});
export const endIcon = () => L.divIcon({
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-flag-checkered -translate-x-1/2"></i>',
className: 'end-icon'
});
export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?: (event: LeafletEvent) => void): Marker {
const fontAwesomeIcon = L.AwesomeMarkers.icon({
icon: waypoint.icon,
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon;
const icon = L.divIcon({
html: `<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-${waypoint.icon}"></i>`,
className: 'waypoint-icon'
});
const marker = L.marker([waypoint.lat, waypoint.lon], {
title: waypoint.name,
icon: fontAwesomeIcon,
icon: icon,
draggable: onDragEnd != null,
meta: {
waypointName: waypoint.name

View File

@@ -110,6 +110,8 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
this._hotline = L.featureGroup();
this._elevation = L.control.elevation(this.options.elevation_options);
this._routes = [];
this.options.points.forEach((poi) =>
L
.marker(poi.latlng, { icon: L.icon(this.options.points_options.icon) })
@@ -123,9 +125,9 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
},
addTo: function (map) {
this._hotline.addTo(map);
this._layers.addTo(map);
this._markers.addTo(map);
this._hotline.addTo(map);
this._map = map;
@@ -137,23 +139,37 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
this._tracks.forEach(this._addTrack, this);
},
select(index) {
if (index > this._routes.length - 1) {
return
}
this.setSelection(this._routes[index])
},
_addTrack: function (track) {
if (track instanceof Object) {
this._loadGeoJSON(track);
} else {
this._elevation._parseFromString(track)
.then(geojson => this._loadGeoJSON(geojson, track.split('/').pop().split('#')[0].split('?')[0]))
.then(geojson => this._loadGeoJSON(geojson, this._hashCode(track), track.split('/').pop().split('#')[0].split('?')[0]))
}
},
_hashCode: (s) => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0),
clear: function () {
this._elevation.clear()
this._elevation.remove();
this._clearLayers();
this._clearLayers(this._markers);
this._clearLayers(this._hotline)
this._count = 0;
this._loadedCount = 0;
this._tracks = []
this._routes = []
this.fire('clear')
},
_clearLayers(l) {
@@ -164,8 +180,9 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
}
},
_loadGeoJSON: function (geojson, fallbackName) {
if (geojson) {
_loadGeoJSON: function (geojson, hash, fallbackName) {
if (geojson) {
geojson.hash = hash
geojson.name = geojson.name || (geojson[0] && geojson[0].properties.name) || fallbackName;
this._loadRoute(geojson);
}
@@ -180,14 +197,14 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
weight: 5,
distanceMarkers: this.options.distanceMarkers_options,
};
var route = L.geoJson(data, {
name: data.name || '',
style: (feature) => line_style,
distanceMarkers: line_style.distanceMarkers,
originalStyle: line_style,
isGroupLayer: true,
index: this._count - 1,
hash: data.hash,
filter: feature => feature.geometry.type != "Point",
});
@@ -196,6 +213,8 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
route.addTo(this._layers);
route.eachLayer((layer) => this._onEachRouteLayer(route, layer));
this._onEachRouteLoaded(route);
});
@@ -204,6 +223,8 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
_onEachRouteLayer: function (route, layer) {
var polyline = layer;
this._routes.push(route)
route.on('selected', L.bind(this._onRouteSelected, this, route, polyline));
polyline.on('mouseover', L.bind(this._onRouteMouseOver, this, route, polyline));
@@ -211,15 +232,14 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
polyline.on('click', L.bind(this._onRouteClick, this, route, polyline));
const startIcon = L.divIcon({
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-bullseye"></i>',
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-bullseye -translate-x-1/2"></i>',
className: 'start-icon'
});
const endIcon = L.divIcon({
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-flag-checkered"></i>',
html: '<i class="px-2 py-2 text-white bg-gray-500 rounded-lg fa fa-flag-checkered -translate-x-1/2"></i>',
className: 'end-icon'
});
const latlngs = polyline.getLatLngs();
const latlngs = polyline.getLatLngs();
if (this._loadedCount == 0) {
L.marker(latlngs[0], { icon: startIcon }).addTo(this._markers)
@@ -280,26 +300,35 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
}
},
_onSelectionChanged: function (e) {
_onSelectionChanged: async function (e) {
var elevation = this._elevation;
var eleDiv = elevation.getContainer();
var route = this.getSelection();
var hotline = this._hotline;
elevation.clear();
hotline.clearLayers();
if (route && route.isSelected()) {
elevation.clear();
if (!eleDiv) {
elevation.addTo(this._map);
this._map.invalidateSize()
}
route.getLayers().forEach(function (layer) {
for (const layer of route.getLayers()) {
if (layer instanceof L.Polyline) {
elevation.addData(layer, false);
await elevation.addData(layer, false);
}
});
}
await elevation._initHotLine(route, this._hotline)
this._map.flyToBounds(route.getBounds(), { duration: 0.25, easeLinearity: 0.25, noMoveStart: true });
} else {
if (eleDiv) {
elevation.remove();
this._map.invalidateSize()
}
}
},

View File

@@ -38,18 +38,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
/*
* Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
*/
addData(d, layer) {
this.import(this.__D3)
.then(() => {
if (this._modulesLoaded) {
layer = layer ?? (d.on && d);
this._addData(d);
this._addLayer(layer);
this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
} else {
this.once('modules_loaded', () => this.addData(d, layer));
}
});
async addData(d, layer) {
await this.import(this.__D3)
if (this._modulesLoaded) {
layer = layer ?? (d.on && d);
this._addData(d);
this._addLayer(layer);
this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
} else {
this.once('modules_loaded', () => this.addData(d, layer));
}
},
/**
@@ -450,10 +449,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
let prop = typeof this.options.hotline == 'string' ? this.options.hotline : 'elevation';
return this.options.hotline ? this.import(/* @vite-ignore */this.__LHOTLINE)
.then(() => {
const map = this._map
map.createPane('hotline');
map.getPane('hotline').style.pointerEvents = 'none';
layer.eachLayer((trkseg) => {
if (trkseg.feature.geometry.type != "Point") {
let line = L.hotline(this._data.map(m => [m.x, m.y, m[prop] || 0]), {
renderer: L.Hotline.renderer(),
renderer: L.Hotline.renderer({pane: "hotline"}),
min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
palette: {
@@ -465,8 +468,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
outlineColor: '#000000',
outlineWidth: 1
}).addTo(target);
console.log(this._data);
let alpha = trkseg.options.style && trkseg.options.style.opacity || 1;
trkseg.on('add remove', ({ type }) => {
trkseg.setStyle({ opacity: (type == 'add' ? 0 : alpha) });