dyomedea/src/db/gpx.ts

422 lines
11 KiB
TypeScript
Raw Normal View History

2022-12-12 19:57:25 +00:00
import { cloneDeep } from 'lodash';
import { PureComponent } from 'react';
import { $DEVCOMP } from 'solid-js';
import { Point, Rectangle } from '../components/map/types';
import { lat2tile, lon2tile, rectanglesIntersect } from '../lib/geo';
import { findStartTime } from '../lib/gpx';
import getUri, { intToGpxId, intToTrkptId } from '../lib/ids';
import { get, getDocsByType, getFamily, put, putAll } from './lib';
2022-12-12 19:57:25 +00:00
export const emptyGpx: Gpx = {
$: {
version: '1.1',
creator: 'dyomedea version 0.000002',
xmlns: 'http://www.topografix.com/GPX/1/1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation':
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd',
'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3',
'xmlns:wptx1': 'http://www.garmin.com/xmlschemas/WaypointExtension/v1',
'xmlns:gpxtpx': 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1',
'xmlns:dyo': 'http://xmlns.dyomedea.com/',
},
metadata: {
name: undefined,
desc: undefined,
author: undefined,
copyright: undefined,
link: undefined,
time: undefined,
keywords: undefined,
bounds: undefined,
extensions: undefined,
},
wpt: undefined,
rte: undefined,
trk: undefined,
extensions: undefined,
};
export const putNewGpx = async (
id: IdGpx = { gpx: intToGpxId(Date.now()) }
) => {
const uri = getUri('gpx', id);
await put(
uri,
'gpx',
(gpx) => {
(gpx.metadata ??= {}).time = new Date(Date.now()).toISOString();
return gpx;
},
emptyGpx
);
return id;
};
export const existsGpx = async (id: IdGpx) => {
const uri = getUri('gpx', id);
try {
await get(uri);
return true;
} catch {
return false;
}
};
const prune = (
id: any,
object: any,
previousGpx: Gpx | null,
extensions: any,
docs: any[]
) => {
if (typeof object === 'object') {
for (const key in object) {
if (
key === 'wpt' ||
key === 'rte' ||
key === 'rtept' ||
key === 'trk' ||
key === 'trkseg' ||
key === 'trkpt'
) {
const subObjects = object[key];
2022-12-10 20:25:15 +00:00
let previousId = 0;
let nbPreviousObjects = 0;
if (previousGpx !== null && key in previousGpx) {
nbPreviousObjects = previousGpx[key].length;
}
for (const index in subObjects) {
const subId = { ...id };
2022-12-10 20:25:15 +00:00
if (key === 'trkpt') {
// fix buggy times in GPX tracks
const normalId = intToTrkptId(
new Date(object[key][index].time).valueOf()
);
const id = normalId > previousId ? normalId : previousId + 1;
subId[key] = id;
previousId = id;
} else {
subId[key] = index + nbPreviousObjects;
2022-12-10 20:25:15 +00:00
}
// console.log({
// caller: 'prune',
// id,
// subId,
// key,
// object: object[key][index],
// time: object[key][index].time,
// });
subObjects[index].extensions = {
...extensions,
...subObjects[index].extensions,
};
docs.push({
_id: getUri(key, subId),
type: key,
doc: subObjects[index],
});
prune(subId, subObjects[index], previousGpx, extensions, docs);
}
object[key] = undefined;
} else prune(id, object[key], previousGpx, extensions, docs);
}
}
};
// const extensionsFromObject = (
// object: any,
// extensions = {
// viewport: { topLeft: <Point>{}, bottomRight: <Point>{} },
// bbox: {
// minLon: <number | undefined>undefined,
// minLat: <number | undefined>undefined,
// maxLon: <number | undefined>undefined,
// maxLat: <number | undefined>undefined,
// },
// }
// ) => {
// if (typeof object === 'object') {
// if ('$' in object) {
// const attributes = object.$;
// if ('lat' in attributes) {
// const lat = +attributes.lat;
// if (
// extensions.bbox.minLat === undefined ||
// lat < extensions.bbox.minLat
// ) {
// extensions.bbox.minLat = lat;
// }
// if (
// extensions.bbox.maxLat === undefined ||
// lat > extensions.bbox.maxLat
// ) {
// extensions.bbox.maxLat = lat;
// }
// }
// if ('lon' in attributes) {
// const lon = +attributes.lon;
// if (
// extensions.bbox.minLon === undefined ||
// lon < extensions.bbox.minLon
// ) {
// extensions.bbox.minLon = lon;
// }
// if (
// extensions.bbox.maxLon === undefined ||
// lon > extensions.bbox.maxLon
// ) {
// extensions.bbox.maxLon = lon;
// }
// }
// }
// for (const key in object) {
// extensionsFromObject(object[key], extensions);
// }
// }
// return extensions;
// };
// const extensionsFromGpx = (gpx: Gpx) => {
// const extensions = { ...gpx.extensions, ...extensionsFromObject(gpx) };
// gpx.extensions = undefined;
// if (
// extensions.bbox.maxLat !== undefined &&
// extensions.bbox.minLon !== undefined
// ) {
// extensions.viewport.topLeft = {
// x: lon2tile(extensions.bbox.minLon, 0),
// y: lat2tile(extensions.bbox.maxLat, 0),
// };
// }
// if (
// extensions.bbox.minLat !== undefined &&
// extensions.bbox.maxLon !== undefined
// ) {
// extensions.viewport.bottomRight = {
// x: lon2tile(extensions.bbox.maxLon, 0),
// y: lat2tile(extensions.bbox.minLat, 0),
// };
// }
// return extensions;
// };
const hasMissingTimestamps = (trk: Trk) => {
for (const trkseg of trk.trkseg!) {
for (const trkpt of trkseg.trkpt!) {
if (trkpt.time === undefined) {
return true;
}
}
}
return false;
};
const convertTrkToRteWhenNeeded = (gpx: Gpx) => {
if (gpx.trk === undefined) {
return;
}
const newTrks: Trk[] = [];
for (const trk of gpx.trk) {
if (hasMissingTimestamps(trk)) {
const rte = { ...trk, trkseg: undefined, rtept: [] };
for (const trkseg of trk.trkseg!) {
rte.rtept = rte.rtept.concat(trkseg.trkpt);
}
if (gpx.rte === undefined) {
gpx.rte = [];
}
gpx.rte.push(rte);
} else {
newTrks.push(trk);
}
}
gpx.trk = newTrks;
};
export const pruneAndSaveImportedGpx = async (params: any) => {
console.log({ caller: 'pruneAndSaveImportedGpx', params });
const { id, gpx, tech } = params;
let gpxId: IdGpx;
let docs: any[] = [];
const extensions = {
...tech,
...gpx.extensions,
gpx: {
creator: gpx?.$?.creator,
metadata: gpx.metadata,
extensions: gpx.extensions,
},
};
convertTrkToRteWhenNeeded(gpx);
let previousGpx: Gpx | null = null;
if (id === 'new') {
const currentDateTime = new Date().toISOString();
const startTime = new Date(findStartTime(gpx, currentDateTime));
if (gpx.metadata === undefined) {
gpx.metadata = {};
}
if (gpx.metadata.time === undefined) {
gpx.metadata.time = startTime.toISOString();
}
gpxId = { gpx: intToGpxId(startTime.valueOf()) };
docs = [
{ _id: getUri('gpx', gpxId), type: 'gpx', doc: gpx },
{
_id: getUri('extensions', gpxId),
type: 'extensions',
doc: {},
},
];
} else {
gpxId = getUri('gpx', id);
previousGpx = (await getGpx(id)) ?? null;
}
prune(gpxId, gpx, previousGpx, extensions, docs);
console.log({ caller: 'pruneAndSaveImportedGpx / pruned', docs });
try {
const result = await putAll(docs);
console.log(JSON.stringify(result));
if (id !== 'new') {
put(id, 'gpx', (doc) => doc, {});
}
} catch (err) {
console.error(`error: ${err}`);
}
};
2022-11-27 13:32:21 +00:00
export const getAllGpxes = async () => {
try {
return (await get('gpx')).doc;
} catch {
return [];
}
};
export const getAllGpxesWithSummary = async () => {
const allGpxes = await getAllGpxes();
const result = await Promise.all(
allGpxes.map(async (id: string) => {
const gpxDoc = await get(id);
2022-12-12 17:55:14 +00:00
// console.log({ caller: 'getAllGpxesWithSummary', gpx: gpxDoc });
return {
id,
name: gpxDoc.doc.metadata?.name,
creator: gpxDoc.doc.$?.creator,
};
})
);
console.log({ caller: 'getAllGpxesWithSummary', result });
return result;
};
2022-11-27 15:09:08 +00:00
export const appendToArray = (target: any, key: string, value: any) => {
if (!(key in target)) {
target[key] = <any>[];
}
target[key].push(value);
};
export const getFullGpx = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
let target: any[];
let gpx: Gpx | undefined = undefined;
docs.rows.forEach((row: any) => {
// level 0
if (row.doc.type === 'gpx') {
target = [row.doc.doc];
gpx = row.doc.doc;
}
//level 1
if (
row.doc.type === 'wpt' ||
row.doc.type === 'rte' ||
row.doc.type === 'trk' ||
row.doc.type === 'extensions'
) {
target.splice(1);
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
target.push(row.doc.doc);
}
// level 2
if (row.doc.type === 'rtept' || row.doc.type === 'trkseg') {
target.splice(2);
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
target.push(row.doc.doc);
}
// level 3
if (row.doc.type === 'trkpt') {
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
}
});
return gpx;
};
export const getGpx = async (params: any) => {
const { id } = params;
2022-12-12 19:57:25 +00:00
if (id === 'new') {
const newGpx = cloneDeep(emptyGpx);
newGpx.metadata!.time = new Date().toISOString();
return newGpx;
}
const docs = await getFamily(id, { include_docs: true });
2022-11-27 14:50:08 +00:00
let target: any[];
let gpx: Gpx | undefined = undefined;
docs.rows.forEach((row: any) => {
// level 0
if (row.doc.type === 'gpx') {
target = [row.doc.doc];
gpx = row.doc.doc;
}
//level 1 (extensions)
if (row.doc.type === 'extensions') {
target.splice(1);
appendToArray(target.at(-1), row.doc.type, row.doc.doc);
target.push(row.doc.doc);
}
//level 1 (others)
if (
row.doc.type === 'wpt' ||
row.doc.type === 'rte' ||
2022-11-27 14:50:08 +00:00
row.doc.type === 'trk'
) {
target.splice(1);
appendToArray(target.at(-1), row.doc.type, row.doc._id);
target.push(row.doc.doc);
}
});
return gpx;
};
2022-12-12 17:55:14 +00:00
export const putGpx = async (params: any) => {
2022-12-12 19:57:25 +00:00
let { id, gpx } = params;
if (id === 'new') {
id = getUri('gpx', {
gpx: intToGpxId(new Date(gpx.metadata.time).valueOf()),
});
}
2022-12-12 17:55:14 +00:00
const extensions = gpx?.extensions;
gpx.extensions = undefined;
gpx.wpt = undefined;
gpx.trk = undefined;
gpx.rte = undefined;
await put(id, 'gpx', (doc) => gpx, gpx);
if (extensions !== undefined) {
await put(
`${id}/4extensions`,
'extensions',
(doc) => extensions,
extensions
);
}
return id;
};