server_photo_s85js/api_v1/scanner/parsers/parseGpx.js
2026-08-11 08:45:04 +02:00

204 lines
4.8 KiB
JavaScript

// api_v1/scanner/parsers/parseGpx.js
const fsp = require('fs/promises');
const xml2js = require('xml2js'); // npm install xml2js
function toMillis(date) {
if (!date) return null;
const d = new Date(date);
return isNaN(d.getTime()) ? null : d.getTime();
}
function buildTrackPreviewFromGpxPoints(points, maxPoints = 300) {
if (!Array.isArray(points)) return [];
const clean = [];
for (const p of points) {
const lat = p.$?.lat != null ? Number(p.$.lat) : null;
const lng = p.$?.lon != null ? Number(p.$.lon) : null;
const alt = p.ele != null ? Number(p.ele) : null;
if (!Number.isFinite(lat) || !Number.isFinite(lng)) continue;
const point = { lat, lng };
if (Number.isFinite(alt)) {
point.alt = alt;
}
clean.push(point);
}
if (clean.length <= maxPoints) return clean;
const step = Math.ceil(clean.length / maxPoints);
const preview = [];
for (let i = 0; i < clean.length; i += step) {
preview.push(clean[i]);
}
const last = clean[clean.length - 1];
const currentLast = preview[preview.length - 1];
if (
!currentLast ||
currentLast.lat !== last.lat ||
currentLast.lng !== last.lng
) {
preview.push(last);
}
return preview;
}
async function parseGpx(absPath) {
const xml = await fsp.readFile(absPath, 'utf8');
const parser = new xml2js.Parser({ explicitArray: false });
const gpx = await parser.parseStringPromise(xml);
const root = gpx.gpx || gpx;
const trk = Array.isArray(root.trk) ? root.trk[0] : root.trk;
const name = trk?.name || null;
const segments = trk?.trkseg
? (Array.isArray(trk.trkseg) ? trk.trkseg : [trk.trkseg])
: [];
const points = [];
for (const seg of segments) {
const segPts = seg.trkpt
? (Array.isArray(seg.trkpt) ? seg.trkpt : [seg.trkpt])
: [];
points.push(...segPts);
}
let minLat = null;
let maxLat = null;
let minLon = null;
let maxLon = null;
let minElev = null;
let maxElev = null;
let startTime = null;
let endTime = null;
let distance = null;
for (const p of points) {
const lat = p.$?.lat != null ? Number(p.$.lat) : null;
const lon = p.$?.lon != null ? Number(p.$.lon) : null;
const ele = p.ele != null ? Number(p.ele) : null;
const time = p.time || null;
if (Number.isFinite(lat) && Number.isFinite(lon)) {
minLat = minLat == null ? lat : Math.min(minLat, lat);
maxLat = maxLat == null ? lat : Math.max(maxLat, lat);
minLon = minLon == null ? lon : Math.min(minLon, lon);
maxLon = maxLon == null ? lon : Math.max(maxLon, lon);
}
if (Number.isFinite(ele)) {
minElev = minElev == null ? ele : Math.min(minElev, ele);
maxElev = maxElev == null ? ele : Math.max(maxElev, ele);
}
if (time) {
if (!startTime) startTime = time;
endTime = time;
}
}
const start_time_millis = toMillis(startTime);
const end_time_millis = toMillis(endTime);
const duration_millis =
start_time_millis && end_time_millis
? end_time_millis - start_time_millis
: null;
const centerLat =
minLat != null && maxLat != null
? (minLat + maxLat) / 2
: null;
const centerLon =
minLon != null && maxLon != null
? (minLon + maxLon) / 2
: null;
const trackPreview = buildTrackPreviewFromGpxPoints(points, 300);
return {
format: 'GPX',
display_name: name,
route_name: name,
title: name,
description: root.metadata?.desc || null,
notes: null,
sport: root.metadata?.type || null,
activity_type: null,
device_manufacturer: root.metadata?.author?.name || null,
device_model: null,
source_app: root.metadata?.author?.name || null,
start_time_millis,
end_time_millis,
duration_millis,
distance_meters: distance,
elevation_gain_meters: null,
elevation_loss_meters: null,
min_elevation_meters: minElev,
max_elevation_meters: maxElev,
file_distance_meters: distance,
file_elevation_gain_meters: null,
computed_distance_meters: distance,
computed_elevation_gain_meters: null,
average_speed_mps: null,
max_speed_mps: null,
average_pace_sec_per_km: null,
average_heart_rate: null,
max_heart_rate: null,
average_cadence: null,
max_cadence: null,
calories: null,
power_avg: null,
power_max: null,
point_count: points.length,
segment_count: segments.length,
lap_count: 0,
min_latitude: minLat,
max_latitude: maxLat,
min_longitude: minLon,
max_longitude: maxLon,
center_latitude: centerLat,
center_longitude: centerLon,
// Preview ridotta del percorso per i thumb della gallery
track_preview: trackPreview,
tags: [],
diagnostics: {
source: 'GPX',
segments: segments.length,
points: points.length,
track_preview_points: trackPreview.length
}
};
}
module.exports = parseGpx;