diff --git a/package-lock.json b/package-lock.json
index 49e028734..3aac1a975 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -69,6 +69,7 @@
"katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4",
+ "leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
"marked": "15.0.6",
"mermaid": "11.4.1",
@@ -132,6 +133,7 @@
"@types/jasmine": "5.1.5",
"@types/jquery": "3.5.32",
"@types/jsdom": "21.1.7",
+ "@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.12.0",
@@ -3839,6 +3841,16 @@
"@types/geojson": "*"
}
},
+ "node_modules/@types/leaflet-gpx": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@types/leaflet-gpx/-/leaflet-gpx-1.3.7.tgz",
+ "integrity": "sha512-IDshIOLZ7dUUjRiCE3DuJcAGavgUCw0xQ93dc/3YvsA6jrFc+nx8eXr0tqZIf2SaWMgqiDj/n7N24WWNh/898g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/leaflet": "*"
+ }
+ },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -11431,6 +11443,12 @@
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
+ "node_modules/leaflet-gpx": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/leaflet-gpx/-/leaflet-gpx-2.1.2.tgz",
+ "integrity": "sha512-lKoEPlAWel9KXn9keg6Dmyt7gmj5IYyD8CKuxivN+77GpZr2bpKliwFvZJxLUHmNu4fICmCySyxhm5qjZuvvQg==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
diff --git a/package.json b/package.json
index 4216a3d26..fa88fd4fd 100644
--- a/package.json
+++ b/package.json
@@ -114,6 +114,7 @@
"katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4",
+ "leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
"marked": "15.0.6",
"mermaid": "11.4.1",
@@ -174,6 +175,7 @@
"@types/jasmine": "5.1.5",
"@types/jquery": "3.5.32",
"@types/jsdom": "21.1.7",
+ "@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.12.0",
diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts
index 60166cd41..5bb060ce9 100644
--- a/src/public/app/widgets/type_widgets/geo_map.ts
+++ b/src/public/app/widgets/type_widgets/geo_map.ts
@@ -104,6 +104,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
private _state: State;
private L!: Leaflet;
private currentMarkerData: MarkerData;
+ private gpxLoaded?: boolean;
static getType() {
return "geoMap";
@@ -159,9 +160,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
}
async #reloadMarkers() {
- const map = this.geoMapWidget.map;
-
- if (!this.note || !map) {
+ if (!this.note) {
return;
}
@@ -173,48 +172,80 @@ export default class GeoMapTypeWidget extends TypeWidget {
// Add the new markers.
this.currentMarkerData = {};
const childNotes = await this.note.getChildNotes();
- const L = this.L;
for (const childNote of childNotes) {
- const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
- if (!latLng) {
+ if (childNote.mime === "application/gpx+xml") {
+ this.#processNoteWithGpxTrack(childNote);
continue;
}
- const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
- const icon = L.divIcon({
- html: `\
-
-
-
- ${childNote.title}`,
- iconSize: [ 25, 41 ],
- iconAnchor: [ 12, 41 ]
- })
+ const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
+ if (latLng) {
+ this.#processNoteWithMarker(childNote, latLng);
+ }
+ }
+ }
- const marker = L.marker(L.latLng(lat, lng), {
- icon,
- draggable: true,
- autoPan: true,
- autoPanSpeed: 5,
- })
- .addTo(map)
- .on("moveend", e => {
- this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng());
- });
+ async #processNoteWithGpxTrack(note: FNote) {
+ if (!this.L || !this.geoMapWidget.map) {
+ return;
+ }
- marker.on("contextmenu", (e) => {
- openContextMenu(childNote.noteId, e.originalEvent);
+ if (!this.gpxLoaded) {
+ await import("leaflet-gpx");
+ this.gpxLoaded = true;
+ }
+
+ // TODO: This is not very efficient as it's probably a string response that is parsed and then converted back to string and parsed again.
+ const xmlResponse = await server.get(`notes/${note.noteId}/open`);
+ const stringResponse = new XMLSerializer().serializeToString(xmlResponse);
+
+ const track = new this.L.GPX(stringResponse, {
+
+ });
+ track.addTo(this.geoMapWidget.map);
+ }
+
+ #processNoteWithMarker(note: FNote, latLng: string) {
+ const map = this.geoMapWidget.map;
+ if (!map) {
+ return;
+ }
+
+ const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
+ const L = this.L;
+ const icon = L.divIcon({
+ html: `\
+
+
+
+ ${note.title}`,
+ iconSize: [ 25, 41 ],
+ iconAnchor: [ 12, 41 ]
+ })
+
+ const marker = L.marker(L.latLng(lat, lng), {
+ icon,
+ draggable: true,
+ autoPan: true,
+ autoPanSpeed: 5,
+ })
+ .addTo(map)
+ .on("moveend", e => {
+ this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
});
- const el = marker.getElement();
- if (el) {
- const $el = $(el);
- $el.attr("data-href", `#${childNote.noteId}`);
- note_tooltip.setupElementTooltip($($el));
- }
+ marker.on("contextmenu", (e) => {
+ openContextMenu(note.noteId, e.originalEvent);
+ });
- this.currentMarkerData[childNote.noteId] = marker;
+ const el = marker.getElement();
+ if (el) {
+ const $el = $(el);
+ $el.attr("data-href", `#${note.noteId}`);
+ note_tooltip.setupElementTooltip($($el));
}
+
+ this.currentMarkerData[note.noteId] = marker;
}
#changeState(newState: State) {
@@ -299,6 +330,14 @@ export default class GeoMapTypeWidget extends TypeWidget {
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
+ // If any of the children branches are altered.
+ if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.noteId)) {
+ this.#reloadMarkers();
+ return;
+ }
+
+ // If any of note has its location attribute changed.
+ // TODO: Should probably filter by parent here as well.
const attributeRows = loadResults.getAttributeRows();
if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) {
this.#reloadMarkers();