overhaul gpx creation and usage
This commit is contained in:
@@ -3,7 +3,7 @@ import path from 'node:path';
|
||||
import { transaction } from '../database.js';
|
||||
import { config } from '../config.js';
|
||||
import { HttpError } from '../middleware/errors.js';
|
||||
import { parseGpx } from './gpx.js';
|
||||
import { parseGpx, gpxWithWaypoints } from './gpx.js';
|
||||
import { distanceMeters, routeMetrics } from './geo.js';
|
||||
import {
|
||||
ensureRouteDirectories,
|
||||
@@ -203,6 +203,7 @@ export function createRoute(db, fields, gpxFile) {
|
||||
return id;
|
||||
});
|
||||
ensureRouteDirectories(routeId);
|
||||
fs.writeFileSync(gpxFile.path, parsed.normalizedXml);
|
||||
const gpxPath = moveUploadedFile(gpxFile, path.join(routeDirectory(routeId), 'route.gpx'));
|
||||
db.prepare('UPDATE routes SET gpx_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
|
||||
.run(gpxPath, routeId);
|
||||
@@ -231,7 +232,11 @@ export function updateRoute(db, id, fields, gpxFile) {
|
||||
updateMetrics(db, id, parsed.metrics);
|
||||
});
|
||||
|
||||
if (gpxFile) moveUploadedFile(gpxFile, path.join(routeDirectory(id), 'route.gpx'));
|
||||
if (gpxFile) {
|
||||
fs.writeFileSync(gpxFile.path, parsed.normalizedXml);
|
||||
moveUploadedFile(gpxFile, path.join(routeDirectory(id), 'route.gpx'));
|
||||
syncRouteWaypoints(db, id);
|
||||
}
|
||||
return getRoute(db, id);
|
||||
}
|
||||
|
||||
@@ -250,6 +255,9 @@ export function appendRoute(db, id, gpxFile) {
|
||||
});
|
||||
|
||||
removeUpload(gpxFile);
|
||||
const target = resolveStoredFile(db.prepare('SELECT gpx_path FROM routes WHERE id = ?').get(id).gpx_path);
|
||||
const original = fs.readFileSync(target, 'utf8');
|
||||
fs.writeFileSync(target, parseGpx(original, allPoints).normalizedXml);
|
||||
return getRoute(db, id);
|
||||
}
|
||||
|
||||
@@ -283,15 +291,28 @@ export function getPoi(db, routeId, poiId) {
|
||||
return listPois(db, routeId).find(poi => poi.id === Number(poiId));
|
||||
}
|
||||
|
||||
function syncRouteWaypoints(db, routeId) {
|
||||
const row = db.prepare('SELECT gpx_path FROM routes WHERE id = ?').get(routeId);
|
||||
const filename = resolveStoredFile(row.gpx_path);
|
||||
const xml = fs.readFileSync(filename, 'utf8');
|
||||
fs.writeFileSync(filename, gpxWithWaypoints(xml, listPois(db, routeId)));
|
||||
}
|
||||
|
||||
function poiCoordinates(fields, existing = {}) {
|
||||
const values = ['lat', 'lon'].map(key => Object.hasOwn(fields, key) ? fields[key] : existing[key]);
|
||||
const [lat, lon] = values.map(value => value == null || value === '' ? null : Number.parseFloat(value));
|
||||
const absent = lat === null && lon === null;
|
||||
if (!absent && (!Number.isFinite(lat) || !Number.isFinite(lon) || Math.abs(lat) > 90 || Math.abs(lon) > 180)) {
|
||||
throw new HttpError(400, 'Gültige POI-Koordinaten sind erforderlich.');
|
||||
}
|
||||
return { lat, lon };
|
||||
}
|
||||
|
||||
export function createPoi(db, routeId, fields) {
|
||||
const route = db.prepare("SELECT id FROM routes WHERE id = ? AND status = 'active'").get(routeId);
|
||||
if (!route) throw new HttpError(404, 'Strecke nicht gefunden.');
|
||||
|
||||
const lat = Number.parseFloat(fields.lat);
|
||||
const lon = Number.parseFloat(fields.lon);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
|
||||
throw new HttpError(400, 'Gültige POI-Koordinaten sind erforderlich.');
|
||||
}
|
||||
const { lat, lon } = poiCoordinates(fields);
|
||||
const result = db.prepare(`INSERT INTO pois
|
||||
(route_id, title, description, lat, lon, trigger_radius_m, sequence) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(
|
||||
routeId,
|
||||
@@ -302,6 +323,7 @@ export function createPoi(db, routeId, fields) {
|
||||
Number.parseFloat(fields.triggerRadiusM || config.defaultPoiTriggerMeters),
|
||||
Number.parseInt(fields.sequence || '0', 10)
|
||||
);
|
||||
syncRouteWaypoints(db, routeId);
|
||||
return listPois(db, routeId).find(poi => poi.id === Number(result.lastInsertRowid));
|
||||
}
|
||||
|
||||
@@ -309,13 +331,12 @@ export function updatePoi(db, routeId, poiId, fields) {
|
||||
const existing = db.prepare(`SELECT p.*, r.status FROM pois p JOIN routes r ON r.id = p.route_id
|
||||
WHERE p.id = ? AND p.route_id = ?`).get(poiId, routeId);
|
||||
if (existing?.status !== 'active') throw new HttpError(404, 'POI nicht gefunden.');
|
||||
const lat = fields.lat == null ? existing.lat : Number.parseFloat(fields.lat);
|
||||
const lon = fields.lon == null ? existing.lon : Number.parseFloat(fields.lon);
|
||||
const { lat, lon } = poiCoordinates(fields, existing);
|
||||
const radius = fields.triggerRadiusM == null
|
||||
? existing.trigger_radius_m
|
||||
: Number.parseFloat(fields.triggerRadiusM);
|
||||
const sequence = fields.sequence == null ? existing.sequence : Number.parseInt(fields.sequence, 10);
|
||||
if (![lat, lon, radius].every(Number.isFinite) || !Number.isInteger(sequence) || sequence < 0) {
|
||||
if (!Number.isFinite(radius) || !Number.isInteger(sequence) || sequence < 0) {
|
||||
throw new HttpError(400, 'POI-Koordinaten, Auslöseradius oder Reihenfolge sind ungültig.');
|
||||
}
|
||||
db.prepare(`UPDATE pois SET title=?, description=?, lat=?, lon=?, trigger_radius_m=?, sequence=?,
|
||||
@@ -328,6 +349,7 @@ export function updatePoi(db, routeId, poiId, fields) {
|
||||
sequence,
|
||||
poiId
|
||||
);
|
||||
syncRouteWaypoints(db, existing.route_id);
|
||||
return listPois(db, existing.route_id).find(poi => poi.id === Number(poiId));
|
||||
}
|
||||
|
||||
@@ -341,6 +363,7 @@ export function deletePoi(db, routeId, poiId) {
|
||||
db.prepare('DELETE FROM pois WHERE id = ? AND route_id = ?').run(poiId, routeId);
|
||||
});
|
||||
|
||||
syncRouteWaypoints(db, routeId);
|
||||
images.forEach(image => removeStoredFile(image.path));
|
||||
removeStoredFile(existing.audio_path);
|
||||
return { id: poiId, routeId, deleted: true };
|
||||
|
||||
Reference in New Issue
Block a user