adds waypoints from photos

This commit is contained in:
Christian Beutel
2025-04-15 15:49:21 +02:00
parent 5db80db06f
commit 8fc10baaf1
14 changed files with 93 additions and 2 deletions

View File

@@ -63,6 +63,9 @@
for (let i = 0; i < dropdownMenu.children.length; i++) { for (let i = 0; i < dropdownMenu.children.length; i++) {
const li = dropdownMenu.children[i]; const li = dropdownMenu.children[i];
const textNode = li.getElementsByTagName("p")[0]; const textNode = li.getElementsByTagName("p")[0];
if(!textNode) {
return
}
textNode.innerHTML = items[i].text; textNode.innerHTML = items[i].text;
const text = textNode.innerText.replace( const text = textNode.innerText.replace(

View File

@@ -14,6 +14,7 @@
class="fa fa-{t.icon} p-3 mr-2 rounded-lg" class="fa fa-{t.icon} p-3 mr-2 rounded-lg"
class:success-toast={t.type == "success"} class:success-toast={t.type == "success"}
class:error-toast={t.type == "error"} class:error-toast={t.type == "error"}
class:warning-toast={t.type == "warning"}
></i> ></i>
<p class="mr-4"> <p class="mr-4">
{t.text} {t.text}
@@ -38,4 +39,8 @@
color: #ef4444; color: #ef4444;
background-color: #fecaca; background-color: #fecaca;
} }
.warning-toast {
color: #ffb900;
background-color: #fef3c6;
}
</style> </style>

View File

@@ -88,7 +88,7 @@
setFields("lon", convertDMSToDD(lon, lonDir)); setFields("lon", convertDMSToDD(lon, lonDir));
} else { } else {
show_toast({ show_toast({
text: "No GPS data in image", text: $_('no-gps-data-in-image'),
icon: "close", icon: "close",
type: "error", type: "error",
}); });

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Passwort vergessen?", "forgot-your-password": "Passwort vergessen?",
"french": "Französisch", "french": "Französisch",
"from-file": "Aus einer Datei", "from-file": "Aus einer Datei",
"from-photos": "",
"from-url": "Aus einer URL", "from-url": "Aus einer URL",
"german": "Deutsch", "german": "Deutsch",
"get-position-from-exif": "Koordinaten aus EXIF Daten", "get-position-from-exif": "Koordinaten aus EXIF Daten",
@@ -212,6 +213,7 @@
"no-comments-so-far": "Bisher keine Kommentare", "no-comments-so-far": "Bisher keine Kommentare",
"no-data": "Keine Daten", "no-data": "Keine Daten",
"no-description-for-now": "Noch keine Beschreibung", "no-description-for-now": "Noch keine Beschreibung",
"no-gps-data-in-image": "No GPS data in image",
"no-grid": "Kein Gitter", "no-grid": "Kein Gitter",
"no-notifications": "Keine Benachrichtigungen", "no-notifications": "Keine Benachrichtigungen",
"no-photos-here": "Hier sind noch keine Fotos", "no-photos-here": "Hier sind noch keine Fotos",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Forgot your password?", "forgot-your-password": "Forgot your password?",
"french": "French", "french": "French",
"from-file": "From file", "from-file": "From file",
"from-photos": "From Photos",
"from-url": "From URL", "from-url": "From URL",
"german": "German", "german": "German",
"get-position-from-exif": "Get coordinates from EXIF data", "get-position-from-exif": "Get coordinates from EXIF data",
@@ -212,6 +213,7 @@
"no-comments-so-far": "No comments so far", "no-comments-so-far": "No comments so far",
"no-data": "No data", "no-data": "No data",
"no-description-for-now": "No description for now", "no-description-for-now": "No description for now",
"no-gps-data-in-image": "",
"no-grid": "No Grid", "no-grid": "No Grid",
"no-notifications": "No notifications", "no-notifications": "No notifications",
"no-photos-here": "No photos or videos here", "no-photos-here": "No photos or videos here",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "¿Contraseña olvidada?", "forgot-your-password": "¿Contraseña olvidada?",
"french": "Francés", "french": "Francés",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "Alemán", "german": "Alemán",
"get-position-from-exif": "Obtener las coordenadas de los datos EXIF", "get-position-from-exif": "Obtener las coordenadas de los datos EXIF",
@@ -212,6 +213,7 @@
"no-comments-so-far": "Ningún comentario todavía", "no-comments-so-far": "Ningún comentario todavía",
"no-data": "No datos", "no-data": "No datos",
"no-description-for-now": "Ninguna descripción de momento", "no-description-for-now": "Ninguna descripción de momento",
"no-gps-data-in-image": "",
"no-grid": "Ninguna cuadrícula", "no-grid": "Ninguna cuadrícula",
"no-notifications": "No notificaciones", "no-notifications": "No notificaciones",
"no-photos-here": "No fotos aquí", "no-photos-here": "No fotos aquí",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Mot de passe oublié ?", "forgot-your-password": "Mot de passe oublié ?",
"french": "Français", "french": "Français",
"from-file": "Depuis un fichier", "from-file": "Depuis un fichier",
"from-photos": "",
"from-url": "Depuis une URL", "from-url": "Depuis une URL",
"german": "Allemand", "german": "Allemand",
"get-position-from-exif": "Obtenir les coordonnées à partir des données EXIF", "get-position-from-exif": "Obtenir les coordonnées à partir des données EXIF",
@@ -212,6 +213,7 @@
"no-comments-so-far": "Aucun commentaire pour l'instant", "no-comments-so-far": "Aucun commentaire pour l'instant",
"no-data": "Pas de données", "no-data": "Pas de données",
"no-description-for-now": "Pas de description pour le moment", "no-description-for-now": "Pas de description pour le moment",
"no-gps-data-in-image": "",
"no-grid": "Aucune grille", "no-grid": "Aucune grille",
"no-notifications": "Pas de notifications", "no-notifications": "Pas de notifications",
"no-photos-here": "Aucune photo ici", "no-photos-here": "Aucune photo ici",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Forgot your password?", "forgot-your-password": "Forgot your password?",
"french": "Francia", "french": "Francia",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "Német", "german": "Német",
"get-position-from-exif": "Get coordinates from EXIF data", "get-position-from-exif": "Get coordinates from EXIF data",
@@ -212,6 +213,7 @@
"no-comments-so-far": "No comments so far", "no-comments-so-far": "No comments so far",
"no-data": "No data", "no-data": "No data",
"no-description-for-now": "No description for now", "no-description-for-now": "No description for now",
"no-gps-data-in-image": "",
"no-grid": "No Grid", "no-grid": "No Grid",
"no-notifications": "No notifications", "no-notifications": "No notifications",
"no-photos-here": "No photos here", "no-photos-here": "No photos here",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Password dimenticata?", "forgot-your-password": "Password dimenticata?",
"french": "Francese", "french": "Francese",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "Tedesco", "german": "Tedesco",
"get-position-from-exif": "Ottieni posizione da dati EXIF", "get-position-from-exif": "Ottieni posizione da dati EXIF",
@@ -212,6 +213,7 @@
"no-comments-so-far": "Nessun commento per il momento", "no-comments-so-far": "Nessun commento per il momento",
"no-data": "Nessun dato", "no-data": "Nessun dato",
"no-description-for-now": "Nessuna descrizione per il momento", "no-description-for-now": "Nessuna descrizione per il momento",
"no-gps-data-in-image": "",
"no-grid": "Nessuna griglia", "no-grid": "Nessuna griglia",
"no-notifications": "Nessuna notifica", "no-notifications": "Nessuna notifica",
"no-photos-here": "Nessuna foto qui", "no-photos-here": "Nessuna foto qui",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Forgot your password?", "forgot-your-password": "Forgot your password?",
"french": "Frans", "french": "Frans",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "Duits", "german": "Duits",
"get-position-from-exif": "Get coordinates from EXIF data", "get-position-from-exif": "Get coordinates from EXIF data",
@@ -212,6 +213,7 @@
"no-comments-so-far": "No comments so far", "no-comments-so-far": "No comments so far",
"no-data": "No data", "no-data": "No data",
"no-description-for-now": "No description for now", "no-description-for-now": "No description for now",
"no-gps-data-in-image": "",
"no-grid": "No Grid", "no-grid": "No Grid",
"no-notifications": "No notifications", "no-notifications": "No notifications",
"no-photos-here": "No photos here", "no-photos-here": "No photos here",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Zapomniałeś hasła?", "forgot-your-password": "Zapomniałeś hasła?",
"french": "Francuski", "french": "Francuski",
"from-file": "Z pliku", "from-file": "Z pliku",
"from-photos": "",
"from-url": "Z URL", "from-url": "Z URL",
"german": "Niemiecki", "german": "Niemiecki",
"get-position-from-exif": "Odczytaj współrzędne z danych EXIF", "get-position-from-exif": "Odczytaj współrzędne z danych EXIF",
@@ -212,6 +213,7 @@
"no-comments-so-far": "Nie ma jeszcze komentarzy", "no-comments-so-far": "Nie ma jeszcze komentarzy",
"no-data": "Brak danych", "no-data": "Brak danych",
"no-description-for-now": "Nie ma jeszcze opisu", "no-description-for-now": "Nie ma jeszcze opisu",
"no-gps-data-in-image": "",
"no-grid": "Brak Siatki", "no-grid": "Brak Siatki",
"no-notifications": "Brak powiadomień", "no-notifications": "Brak powiadomień",
"no-photos-here": "Nie ma tu zdjęć", "no-photos-here": "Nie ma tu zdjęć",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "Forgot your password?", "forgot-your-password": "Forgot your password?",
"french": "Francês", "french": "Francês",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "Alemão", "german": "Alemão",
"get-position-from-exif": "Obter coordenadas dos dados EXIF", "get-position-from-exif": "Obter coordenadas dos dados EXIF",
@@ -212,6 +213,7 @@
"no-comments-so-far": "No comments so far", "no-comments-so-far": "No comments so far",
"no-data": "Sem dados", "no-data": "Sem dados",
"no-description-for-now": "No description for now", "no-description-for-now": "No description for now",
"no-gps-data-in-image": "",
"no-grid": "No Grid", "no-grid": "No Grid",
"no-notifications": "No notifications", "no-notifications": "No notifications",
"no-photos-here": "No photos here", "no-photos-here": "No photos here",

View File

@@ -139,6 +139,7 @@
"forgot-your-password": "忘记密码?", "forgot-your-password": "忘记密码?",
"french": "法语", "french": "法语",
"from-file": "From file", "from-file": "From file",
"from-photos": "",
"from-url": "From URL", "from-url": "From URL",
"german": "德语", "german": "德语",
"get-position-from-exif": "从EXIF数据获取坐标", "get-position-from-exif": "从EXIF数据获取坐标",
@@ -212,6 +213,7 @@
"no-comments-so-far": "No comments so far", "no-comments-so-far": "No comments so far",
"no-data": "无数据", "no-data": "无数据",
"no-description-for-now": "No description for now", "no-description-for-now": "No description for now",
"no-gps-data-in-image": "",
"no-grid": "No Grid", "no-grid": "No Grid",
"no-notifications": "No notifications", "no-notifications": "No notifications",
"no-photos-here": "No photos here", "no-photos-here": "No photos here",

View File

@@ -45,7 +45,7 @@
setRoute, setRoute,
} from "$lib/stores/valhalla_store"; } from "$lib/stores/valhalla_store";
import { waypoint } from "$lib/stores/waypoint_store"; import { waypoint } from "$lib/stores/waypoint_store";
import { getFileURL } from "$lib/util/file_util"; import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
import { import {
formatDistance, formatDistance,
formatElevation, formatElevation,
@@ -65,6 +65,7 @@
} from "$lib/components/base/search.svelte"; } from "$lib/components/base/search.svelte";
import RoutingOptionsPopup from "$lib/components/trail/routing_options_popup.svelte"; import RoutingOptionsPopup from "$lib/components/trail/routing_options_popup.svelte";
import { TagCreateSchema } from "$lib/models/api/tag_schema.js"; import { TagCreateSchema } from "$lib/models/api/tag_schema.js";
import { convertDMSToDD } from "$lib/models/gpx/utils.js";
import { Tag } from "$lib/models/tag.js"; import { Tag } from "$lib/models/tag.js";
import { import {
searchLocationReverse, searchLocationReverse,
@@ -77,6 +78,7 @@
createAnchorMarker, createAnchorMarker,
createEditTrailMapPopup, createEditTrailMapPopup,
} from "$lib/util/maplibre_util"; } from "$lib/util/maplibre_util";
import EXIF from "$lib/vendor/exif-js/exif.js";
import { validator } from "@felte/validator-zod"; import { validator } from "@felte/validator-zod";
import cryptoRandomString from "crypto-random-string"; import cryptoRandomString from "crypto-random-string";
import { createForm } from "felte"; import { createForm } from "felte";
@@ -899,6 +901,53 @@
const result = await tags_index(q); const result = await tags_index(q);
tagItems = result.items.map((t) => ({ text: t.name, value: t })); tagItems = result.items.map((t) => ({ text: t.name, value: t }));
} }
function openPhotoBrowser() {
document.getElementById("waypoint-photo-input")!.click();
}
async function handleWaypointPhotoSelection() {
const files = (
document.getElementById("waypoint-photo-input") as HTMLInputElement
).files;
if (!files) {
return;
}
for (const file of files) {
const coords = await new Promise<number[]>((resolve) => {
EXIF.getData(file, function (p) {
const lat = EXIF.getTag(p, "GPSLatitude");
const latDir = EXIF.getTag(p, "GPSLatitudeRef");
const lon = EXIF.getTag(p, "GPSLongitude");
const lonDir = EXIF.getTag(p, "GPSLongitudeRef");
if (lat && lon) {
resolve([
convertDMSToDD(lat, latDir),
convertDMSToDD(lon, lonDir),
]);
} else {
resolve([]);
}
});
});
if (coords.length) {
const wp: Waypoint = new Waypoint(coords[0], coords[1], {
icon: "image",
});
wp._photos = [file];
saveWaypoint(wp);
} else {
show_toast({
type: "warning",
icon: "warning",
text: `${file.name}: ${$_("no-gps-data-in-image")}`,
}, 10000);
}
}
}
</script> </script>
<svelte:head> <svelte:head>
@@ -1113,6 +1162,20 @@
onclick={() => beforeWaypointModalOpen()} onclick={() => beforeWaypointModalOpen()}
><i class="fa fa-plus mr-2"></i>{$_("add-waypoint")}</button ><i class="fa fa-plus mr-2"></i>{$_("add-waypoint")}</button
> >
<button
class="btn-secondary"
type="button"
onclick={() => openPhotoBrowser()}
><i class="fa fa-image mr-2"></i>{$_("from-photos")}</button
>
<input
type="file"
id="waypoint-photo-input"
accept="image/*"
multiple={true}
style="display: none;"
onchange={() => handleWaypointPhotoSelection()}
/>
<hr class="border-separator" /> <hr class="border-separator" />
<h3 class="text-xl font-semibold">{$_("photos")}</h3> <h3 class="text-xl font-semibold">{$_("photos")}</h3>
<PhotoPicker <PhotoPicker