import { PureComponent } from 'react'; import { Point, Rectangle } from '../components/map/types'; import { lat2tile, lon2tile, rectanglesIntersect } from '../lib/geo'; import getUri, { intToGpxId, intToTrkptId } from '../lib/ids'; import { get, getDocsByType, getFamily, put, putAll } from './lib'; 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, 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]; let previousId = 0; for (const index in subObjects) { const subId = { ...id }; 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; } // console.log({ // caller: 'prune', // id, // subId, // key, // object: object[key][index], // time: object[key][index].time, // }); docs.push({ _id: getUri(key, subId), type: key, doc: subObjects[index], }); prune(subId, subObjects[index], docs); } object[key] = undefined; } else prune(id, object[key], docs); } } }; const extensionsFromObject = ( object: any, extensions = { viewport: { topLeft: {}, bottomRight: {} }, bbox: { minLon: undefined, minLat: undefined, maxLon: undefined, maxLat: 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; }; export const pruneAndSaveImportedGpx = async (params: any) => { const { id, gpx, extensions } = params; let docs: any[] = [ { _id: getUri('gpx', id), type: 'gpx', doc: gpx }, { _id: getUri('extensions', id), type: 'extensions', doc: { ...extensions, ...extensionsFromGpx(gpx) }, }, ]; prune(id, gpx, docs); console.log({ caller: 'pruneAndSaveImportedGpx / pruned', docs }); try { const result = await putAll(docs); console.log(JSON.stringify(result)); } catch (err) { console.error(`error: ${err}`); } }; export const getAllGpxes = async () => { return (await getDocsByType('gpx')).map((doc: any) => doc._id); }; export const getGpxesForViewport = async (params: any) => { const { viewport, zoomLevel } = params; const zoomedViewport: Rectangle = { topLeft: { x: viewport.topLeft.x / 2 ** zoomLevel, y: viewport.topLeft.y / 2 ** zoomLevel, }, bottomRight: { x: (viewport.bottomRight.x + 1) / 2 ** zoomLevel, y: (viewport.bottomRight.y + 1) / 2 ** zoomLevel, }, }; const allExtensions = await getDocsByType('extensions'); console.log( `getGpxesForViewport, allExtensions: ${JSON.stringify(allExtensions)}` ); return allExtensions .filter((extensions: any) => { return rectanglesIntersect(zoomedViewport, extensions.doc.viewport); }) .map((extensions: any) => getUri('gpx', getUri('extensions', extensions._id)) ); }; export const appendToArray = (target: any, key: string, value: any) => { if (!(key in target)) { target[key] = []; } 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; 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 (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' || row.doc.type === 'trk' ) { target.splice(1); appendToArray(target.at(-1), row.doc.type, row.doc._id); target.push(row.doc.doc); } }); return gpx; };