Reintroducing WebWorkers (pfeeew, Vite makes it tough !)

This commit is contained in:
Eric van der Vlist 2022-11-26 19:36:55 +01:00
parent 02f02984e5
commit 5de30283b1
52 changed files with 3362 additions and 47 deletions

710
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,10 +27,16 @@
"@capacitor/android": "^3.4.3",
"@capacitor/core": "^3.4.3",
"@capacitor/ios": "^3.4.3",
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
"@solidjs/router": "^0.5.1",
"@suid/icons-material": "^0.5.1",
"@suid/material": "^0.8.0",
"lodash": "^4.17.21",
"ol": "^7.1.0",
"pouchdb": "^7.3.1",
"pouchdb-browser": "^7.3.1",
"pouchdb-find": "^7.3.1",
"solid-js": "^1.3.12"
}
}

86
src/db/gpx.test.ts Normal file
View File

@ -0,0 +1,86 @@
import { initDb } from '.';
import { existsGpx, putNewGpx } from './gpx';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The gpx module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new Gpx when required', async () => {
await putNewGpx({ gpx: 0 });
expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000000',
_rev: undefined,
doc: {
$: {
creator: 'dyomedea version 0.000002',
version: '1.1',
xmlns: 'http://www.topografix.com/GPX/1/1',
'xmlns:dyo': 'http://xmlns.dyomedea.com/',
'xmlns:gpxtpx':
'http://www.garmin.com/xmlschemas/TrackPointExtension/v1',
'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3',
'xmlns:wptx1':
'http://www.garmin.com/xmlschemas/WaypointExtension/v1',
'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',
},
extensions: undefined,
metadata: {
author: undefined,
bounds: undefined,
copyright: undefined,
desc: undefined,
extensions: undefined,
keywords: undefined,
link: undefined,
name: undefined,
time: '1970-01-01T00:00:00.000Z',
},
rte: undefined,
trk: undefined,
wpt: undefined,
},
type: 'gpx',
});
});
test('db.put() generates an id if needed', async () => {
const id = await putNewGpx();
expect(id).toEqual({ gpx: 4320000000000000 });
});
});
describe('The gpx module with a real db', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test("existsGpx returns false if the GPX doesn't exist", async () => {
const exists = await existsGpx({ gpx: 1 });
expect(exists).toBeFalsy();
});
test('existsGpx returns false if the GPX exists', async () => {
const id = { gpx: 1 };
await putNewGpx(id);
const exists = await existsGpx(id);
expect(exists).toBeTruthy();
});
});

269
src/db/gpx.ts Normal file
View File

@ -0,0 +1,269 @@
import { PureComponent } from 'react';
import { Point, Rectangle } from '../components/map/types';
import { lat2tile, lon2tile, rectanglesIntersect } from '../lib/geo';
import getUri, { intToGpxId } 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];
for (const index in subObjects) {
const subId = { ...id };
subId[key] = index;
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: <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;
};
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(JSON.stringify(docs));
try {
const result = await putAll(docs);
console.log(JSON.stringify(result));
} catch (err) {
console.error(`error: ${err}`);
}
};
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))
);
};
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;
const docs = await getFamily(id, { include_docs: true });
console.log(`getGpx, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows.filter(
(row: any) =>
row.doc.type === 'gpx' ||
row.doc.type === 'wpt' ||
row.doc.type === 'rte' ||
row.doc.type === 'trk' ||
row.doc.type === 'extensions'
);
};

117
src/db/index.ts Normal file
View File

@ -0,0 +1,117 @@
import _ from 'lodash';
import PouchDB from 'pouchdb';
import PouchDBFind from 'pouchdb-find';
import uri from '../lib/ids';
PouchDB.plugin(PouchDBFind);
const dbDefinitionId = uri('dbdef', {});
const currentDbDefinition = {
_id: dbDefinitionId,
type: dbDefinitionId,
def: { version: '0.000001' },
};
declare global {
var db: any;
var dbReady: boolean;
}
export const initDb = async (params: any) => {
if (globalThis.db === undefined) {
globalThis.db = new PouchDB('dyomedea', {
auto_compaction: true,
revs_limit: 10,
});
}
const db = globalThis.db;
var previousDbDefinition = {
_id: dbDefinitionId,
type: dbDefinitionId,
def: { version: '0' },
};
try {
previousDbDefinition = await db.get(dbDefinitionId);
} catch (error: any) {
if (error.status !== 404) {
console.log(
`Unexpected error fetching db definition: ${JSON.stringify(error)}`
);
return;
}
}
if (previousDbDefinition.def.version < currentDbDefinition.def.version) {
previousDbDefinition.def = currentDbDefinition.def;
db.put(previousDbDefinition);
// TODO: support migrations
}
await await db.compact();
await db.viewCleanup();
// WARNING: defs must use the canonical form and be identical to what will be returned by db.getIndexes
const requiredIndexes: any = [
{
name: 'type',
def: {
fields: [{ type: 'asc' }],
},
},
];
const existingIndexes = (await db.getIndexes()).indexes;
const pruneIndex = ({ name, def }: any) => ({ name, def });
const isSameIndex = (idx1: any, idx2: any) => {
return _.isEqual(pruneIndex(idx1), pruneIndex(idx2));
};
const findIndex = (targetIndexes: any, index: any) =>
targetIndexes.find((targetIndex: any) => {
return isSameIndex(targetIndex, index);
});
for (var index of existingIndexes) {
if (index.type === 'json') {
// Non system indexes
// console.log(`Checking existing index :${JSON.stringify(index)}`);
if (!findIndex(requiredIndexes, index)) {
// console.log(`db.deleteIndex(${JSON.stringify(index)})`);
await db.deleteIndex(index);
}
}
}
for (index of requiredIndexes) {
if (!findIndex(existingIndexes, index)) {
// console.log(`db.createIndex(${JSON.stringify(index)})`);
await db.createIndex({ name: index.name, ...index.def });
}
}
globalThis.dbReady = true;
/* const indexes = await db.getIndexes();
console.log(`indexes: ${JSON.stringify(indexes)}`);
const explain1 = await db.explain({
selector: {
type: 'trkpt',
gpx: 'xxxx',
},
// sort: ['trkpt.time'],
// use_index: 'type-trkpt-gpx-time',
});
console.log(`explain1: ${JSON.stringify(explain1)}`);
const explain2 = await db.explain({
selector: {
type: 'gpx',
},
// sort: ['trkpt.time'],
// use_index: 'type-trkpt-gpx-time',
});
console.log(`explain2: ${JSON.stringify(explain2)}`);
*/
};

299
src/db/lib.test.ts Normal file
View File

@ -0,0 +1,299 @@
import { initDb } from '.';
import uri from '../lib/ids';
import { getDocsByType, getFamily } from './lib';
import { putNewRte } from './rte';
import { putNewRtept } from './rtept';
import { putNewTrk } from './trk';
import { putNewTrkpt } from './trkpt';
import { putNewTrkseg } from './trkseg';
import { putNewWpt } from './wpt';
declare global {
var db: any;
}
const originalDateNow = globalThis.Date.now;
describe('getFamily', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test('returns two rows after a gpx and a track have been inserted.', async () => {
await putNewTrk();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
],
"total_rows": 4,
}
`);
});
test('also returns the docs if required.', async () => {
await putNewTrk();
const allDocs: any = await getFamily('gpx/', {
include_docs: true,
});
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"doc": Object {
"_id": "gpx/4320000000000000",
"_rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
"doc": Object {
"$": Object {
"creator": "dyomedea version 0.000002",
"version": "1.1",
"xmlns": "http://www.topografix.com/GPX/1/1",
"xmlns:dyo": "http://xmlns.dyomedea.com/",
"xmlns:gpxtpx": "http://www.garmin.com/xmlschemas/TrackPointExtension/v1",
"xmlns:gpxx": "http://www.garmin.com/xmlschemas/GpxExtensions/v3",
"xmlns:wptx1": "http://www.garmin.com/xmlschemas/WaypointExtension/v1",
"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",
},
"metadata": Object {
"time": "1970-01-01T00:00:00.000Z",
},
},
"type": "gpx",
},
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"doc": Object {
"_id": "gpx/4320000000000000/3trk/000000",
"_rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
"doc": Object {
"number": 0,
},
"type": "trk",
},
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
],
"total_rows": 4,
}
`);
});
test('returns three rows after a gpx and a track segment have been inserted.', async () => {
await putNewTrkseg();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000",
"value": Object {
"rev": "1-68d7de0569de570229ea9f9e1a0b13cb",
},
},
],
"total_rows": 5,
}
`);
});
test('returns four rows after a gpx and a track point have been inserted.', async () => {
await putNewTrkpt();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000",
"key": "gpx/4320000000000000/3trk/000000",
"value": Object {
"rev": "1-4c114f3ae0073151e4082ff1d220c2a4",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000",
"value": Object {
"rev": "1-68d7de0569de570229ea9f9e1a0b13cb",
},
},
Object {
"id": "gpx/4320000000000000/3trk/000000/000000/000000",
"key": "gpx/4320000000000000/3trk/000000/000000/000000",
"value": Object {
"rev": "1-7d917d1f3505fe0e3092161694904b53",
},
},
],
"total_rows": 6,
}
`);
});
test('returns two rows after a gpx and a waypoint have been inserted.', async () => {
await putNewWpt();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/1wpt/000000",
"key": "gpx/4320000000000000/1wpt/000000",
"value": Object {
"rev": "1-c6793365fd0dd56236ab8734a41c7ae7",
},
},
],
"total_rows": 4,
}
`);
});
test('returns two rows after a gpx and a route have been inserted.', async () => {
await putNewRte();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000",
"key": "gpx/4320000000000000/2rte/000000",
"value": Object {
"rev": "1-2ca14f512a9c83f5a239389e580befce",
},
},
],
"total_rows": 4,
}
`);
});
test('returns three rows after a gpx and a route point have been inserted.', async () => {
await putNewRtept();
const allDocs: any = await getFamily('gpx/');
expect(allDocs).toMatchInlineSnapshot(`
Object {
"offset": 0,
"rows": Array [
Object {
"id": "gpx/4320000000000000",
"key": "gpx/4320000000000000",
"value": Object {
"rev": "1-49baa096ec0c89962f2cafd3ff50b80b",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000",
"key": "gpx/4320000000000000/2rte/000000",
"value": Object {
"rev": "1-2ca14f512a9c83f5a239389e580befce",
},
},
Object {
"id": "gpx/4320000000000000/2rte/000000/000000",
"key": "gpx/4320000000000000/2rte/000000/000000",
"value": Object {
"rev": "1-0f4064d20f6bfac3888a7758851fbac5",
},
},
],
"total_rows": 5,
}
`);
});
});
describe('getDocsByType', () => {
beforeEach(async () => {
await initDb({});
globalThis.Date.now = () => 0;
});
afterEach(async () => {
await db.destroy();
db = undefined;
globalThis.Date.now = originalDateNow;
});
test('gets the rte amongst other docs', async () => {
await putNewRtept();
const rtes = await getDocsByType('rte');
expect(rtes).toMatchInlineSnapshot(`
Array [
Object {
"_id": "gpx/4320000000000000/2rte/000000",
"_rev": "1-2ca14f512a9c83f5a239389e580befce",
"doc": Object {
"number": 0,
},
"type": "rte",
},
]
`);
});
});

52
src/db/lib.ts Normal file
View File

@ -0,0 +1,52 @@
import { cloneDeep } from 'lodash';
declare global {
var db: any;
}
export const put = async (
_id: string,
type: string,
update: (doc: any) => any,
defaultDoc: any
) => {
var current;
try {
current = await db.get(_id);
} catch {
current = { _rev: undefined, doc: cloneDeep(defaultDoc) };
}
try {
db.put({ _id, _rev: current._rev, type, doc: update(current.doc) });
} catch (error: any) {
if (error.name === 'conflict') {
await put(_id, type, update, defaultDoc);
} else {
console.error(
`put(${_id}, ${JSON.stringify(
update(current.doc)
)}), error: ${JSON.stringify(error)}`
);
}
}
};
export const getFamily = async (key: string, options: any = {}) => {
return await db.allDocs({
startkey: key,
endkey: key + '\ufff0',
...options,
});
};
export const get = async (id: string) => {
await db.get(id);
};
export const putAll = async (docs: any[]) => {
return await db.bulkDocs(docs);
};
export const getDocsByType = async (type: string) => {
return (await db.find({ selector: { type: type } })).docs;
};

47
src/db/rte.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { putNewRte } from './rte';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The rte module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new rte when required', async () => {
putNewRte({ gpx: 4320000000000000, rte: 25 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/4320000000000000/2rte/000025',
_rev: undefined,
doc: {
cmt: undefined,
desc: undefined,
extensions: undefined,
link: undefined,
name: undefined,
number: 0,
rtept: undefined,
src: undefined,
type: undefined,
},
type: 'rte',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewRte({ gpx: 0 });
expect(id).toEqual({ gpx: 0, rte: 0 });
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewRte();
expect(id).toEqual({ gpx: 4320000000000000, rte: 0 });
});
});

33
src/db/rte.ts Normal file
View File

@ -0,0 +1,33 @@
import getUri from '../lib/ids';
import { putNewGpx } from './gpx';
import { put } from './lib';
export const emptyRte: Rte = {
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
number: 0,
type: undefined,
extensions: undefined,
rtept: undefined,
};
export const putNewRte = async (id?: IdRte | IdGpx) => {
let finalId = { ...id };
if (!('rte' in finalId)) {
const gpxId = await putNewGpx(id);
finalId = { ...gpxId, rte: 0 };
}
const uri = getUri('rte', finalId);
await put(
uri,
'rte',
(rte) => {
return rte;
},
emptyRte
);
return finalId as IdRte;
};

58
src/db/rtept.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { putNewRtept } from './rtept';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The rtept module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new rtept when required', async () => {
putNewRtept({ gpx: 0, rte: 0, rtept: 0 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000000/2rte/000000/000000',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: undefined,
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,
sat: undefined,
src: undefined,
sym: undefined,
time: undefined,
type: undefined,
vdop: undefined,
},
type: 'rtept',
});
});
test('db.put() generates an id for the rtept if needed', async () => {
const id = await putNewRtept({ gpx: 0 });
expect(id).toEqual({ gpx: 0, rte: 0, rtept: 0 });
});
test('db.put() generates ids for both gpx and rte if needed', async () => {
const id = await putNewRtept();
expect(id).toEqual({ gpx: 4320000000000000, rte: 0, rtept: 0 });
});
});

44
src/db/rtept.ts Normal file
View File

@ -0,0 +1,44 @@
import getUri from '../lib/ids';
import { put } from './lib';
import { putNewRte } from './rte';
export const emptyRtept: Wpt = {
$: { lat: 0, lon: 0 },
ele: undefined,
time: undefined,
magvar: undefined,
geoidheight: undefined,
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
sym: undefined,
type: undefined,
fix: undefined,
sat: undefined,
hdop: undefined,
vdop: undefined,
pdop: undefined,
ageofdgpsdata: undefined,
dgpsid: undefined,
extensions: undefined,
};
export const putNewRtept = async (id?: IdGpx | IdRte | IdRtept) => {
let finalId = { ...id };
if (!('rtept' in finalId)) {
const rteId = await putNewRte(id);
finalId = { ...rteId, rtept: 0 };
}
const uri = getUri('rtept', finalId);
await put(
uri,
'rtept',
(rtept) => {
return rtept;
},
emptyRtept
);
return finalId as IdRtept;
};

47
src/db/trk.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { putNewTrk } from './trk';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trk module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trk when required', async () => {
putNewTrk({ gpx: 1, trk: 2 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/3trk/000002',
_rev: undefined,
doc: {
cmt: undefined,
desc: undefined,
extensions: undefined,
link: undefined,
name: undefined,
number: 0,
src: undefined,
trkseg: undefined,
type: undefined,
},
type: 'trk',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrk({ gpx: 2 });
expect(id).toEqual({ gpx: 2, trk: 0});
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrk();
expect(id).toEqual({ gpx: 4320000000000000, trk: 0});
});
});

42
src/db/trk.ts Normal file
View File

@ -0,0 +1,42 @@
import getUri from '../lib/ids';
import { putNewGpx } from './gpx';
import { getFamily, put } from './lib';
export const emptyTrk: Trk = {
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
number: 0,
type: undefined,
extensions: undefined,
trkseg: undefined,
};
export const putNewTrk = async (id?: IdTrk | IdGpx) => {
let finalId = { ...id };
if (!('trk' in finalId)) {
const gpxId = await putNewGpx(id);
finalId = { ...gpxId, trk: 0 };
}
const uri = getUri('trk', finalId);
await put(
uri,
'trk',
(trk) => {
return trk;
},
emptyTrk
);
return finalId as IdTrk;
};
export const getTrk = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
console.log(`getTrk, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows.filter(
(row: any) => row.doc.type === 'trk' || row.doc.type === 'trkseg'
);
};

79
src/db/trkpt.test.ts Normal file
View File

@ -0,0 +1,79 @@
import { putNewTrkpt } from './trkpt';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trkpt module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trkpt when required', async () => {
putNewTrkpt({
gpx: 1,
trk: 2,
trkseg: 3,
trkpt: 4,
});
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/3trk/000002/000003/000004',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: {
'dyo:accuracy': undefined,
'dyo:batterylevel': undefined,
'dyo:course': undefined,
'dyo:speed': undefined,
'dyo:useragent': undefined,
},
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,
sat: undefined,
src: undefined,
sym: undefined,
time: undefined,
type: undefined,
vdop: undefined,
},
type: 'trkpt',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkpt({ gpx: 5 });
expect(id).toEqual({
gpx: 5,
trk: 0,
trkseg: 0,
trkpt: 0,
});
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkpt();
expect(id).toEqual({
gpx: 4320000000000000,
trk: 0,
trkseg: 0,
trkpt: 0,
});
});
});

50
src/db/trkpt.ts Normal file
View File

@ -0,0 +1,50 @@
import getUri from '../lib/ids';
import { put } from './lib';
import { putNewTrkseg } from './trkseg';
const emptyTrkpt: Wpt = {
$: { lat: 0, lon: 0 },
ele: undefined,
time: undefined,
magvar: undefined,
geoidheight: undefined,
name: undefined,
cmt: undefined,
desc: undefined,
src: undefined,
link: undefined,
sym: undefined,
type: undefined,
fix: undefined,
sat: undefined,
hdop: undefined,
vdop: undefined,
pdop: undefined,
ageofdgpsdata: undefined,
dgpsid: undefined,
extensions: {
'dyo:speed': undefined,
'dyo:course': undefined,
'dyo:accuracy': undefined,
'dyo:batterylevel': undefined,
'dyo:useragent': undefined,
},
};
export const putNewTrkpt = async (id?: IdTrk | IdGpx | IdTrkseg | IdTrkpt) => {
let finalId = { ...id };
if (!('trkpt' in finalId)) {
const trksegId = await putNewTrkseg(id);
finalId = { ...trksegId, trkpt: 0 };
}
const uri = getUri('trkpt', finalId);
await put(
uri,
'trkpt',
(trkpt) => {
return trkpt;
},
emptyTrkpt
);
return finalId;
};

40
src/db/trkseg.test.ts Normal file
View File

@ -0,0 +1,40 @@
import { putNewTrkseg } from './trkseg';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The trkseg module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new trk when required', async () => {
putNewTrkseg({ gpx: 1234567890123456, trk: 123456, trkseg: 5 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/1234567890123456/3trk/123456/000005',
_rev: undefined,
doc: {
trkpt: undefined,
extensions: undefined,
},
type: 'trkseg',
});
});
test('db.put() generates an id for the trk if needed', async () => {
const id = await putNewTrkseg({ gpx: 1 });
expect(id).toEqual({ gpx: 1, trk: 0, trkseg: 0 });
});
test('db.put() generates ids for both gpx and trk if needed', async () => {
const id = await putNewTrkseg();
expect(id).toEqual({ gpx: 4320000000000000, trk: 0, trkseg: 0 });
});
});

33
src/db/trkseg.ts Normal file
View File

@ -0,0 +1,33 @@
import getUri from '../lib/ids';
import { getFamily, put } from './lib';
import { putNewTrk } from './trk';
const emptyTrkseg: Trkseg = {
trkpt: undefined,
extensions: undefined,
};
export const putNewTrkseg = async (id?: IdTrk | IdGpx | IdTrkseg) => {
let finalId = { ...id };
if (!('trkseg' in finalId)) {
const trkId = await putNewTrk(id);
finalId = { ...trkId, trkseg: 0 };
}
const uri = getUri('trkseg', finalId);
await put(
uri,
'trkseg',
(trkseg) => {
return trkseg;
},
emptyTrkseg
);
return finalId as IdTrkseg;
};
export const getTrkseg = async (params: any) => {
const { id } = params;
const docs = await getFamily(id, { include_docs: true });
console.log(`getTrkseg, uri: ${id} docs: ${JSON.stringify(docs)}`);
return docs.rows;
};

119
src/db/types.d.ts vendored Normal file
View File

@ -0,0 +1,119 @@
interface Gpx {
$: Gpx_;
metadata?: Metadata;
wpt?: Wpt[];
rte?: Rte[];
trk?: Trk[];
extensions?: Extensions;
}
interface Gpx_ {
version: '1.1';
creator: string;
xmlns: 'http://www.topografix.com/GPX/1/1';
'xmlns:xsi'?: 'http://www.w3.org/2001/XMLSchema-instance';
'xsi:schemaLocation'?: string;
'xmlns:gpxx'?: string;
'xmlns:wptx1'?: string;
'xmlns:gpxtpx'?: string;
'xmlns:dyo'?: 'http://xmlns.dyomedea.com/';
}
interface Metadata {
name?: string;
desc?: string;
author?: string;
copyright?: string;
link?: Link[];
time?: string;
keywords?: string;
bounds?: Bounds;
extensions?: Extensions;
}
interface Bounds {
$: Bounds_;
}
interface Bounds_ {
minlat: number;
minlon: number;
maxlat: number;
maxlon: number;
}
interface Extensions {
'dyo:speed'?: number;
'dyo:course'?: number;
'dyo:accuracy'?: number;
'dyo:batterylevel'?: number;
'dyo:useragent'?: string;
}
interface Trk {
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link[];
number?: number;
type?: string;
extensions?: Extensions;
trkseg?: Trkseg[];
}
interface Link {
$: Link_;
text?: string;
type?: string;
}
interface Link_ {
href: string;
}
interface Trkseg {
trkpt?: Wpt[];
extensions?: Extensions;
}
interface Wpt {
$: Wpt_;
ele?: number;
time?: string;
magvar?: number;
geoidheight?: number;
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link;
sym?: string;
type?: string;
fix?: string;
sat?: number;
hdop?: number;
vdop?: number;
pdop?: number;
ageofdgpsdata?: number;
dgpsid?: number;
extensions?: Extensions;
}
interface Wpt_ {
lat: number;
lon: number;
}
interface Rte {
name?: string;
cmt?: string;
desc?: string;
src?: string;
link?: Link[];
number?: number;
type?: string;
extensions?: Extensions;
rtept?: Wpt[];
}

58
src/db/wpt.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { putNewWpt } from './wpt';
declare global {
var db: any;
var dbReady: boolean;
}
const originalDb = globalThis.db;
const originalDateNow = globalThis.Date.now;
describe('The wpt module', () => {
beforeEach(() => {
globalThis.db = { put: jest.fn() };
globalThis.Date.now = () => 0;
});
afterEach(() => {
globalThis.db = originalDb;
globalThis.Date.now = originalDateNow;
});
test('db.put() a new wpt when required', async () => {
putNewWpt({ gpx: 1, wpt: 2 });
await expect(globalThis.db.put).toBeCalledWith({
_id: 'gpx/0000000000000001/1wpt/000002',
_rev: undefined,
doc: {
$: { lat: 0, lon: 0 },
ageofdgpsdata: undefined,
cmt: undefined,
desc: undefined,
dgpsid: undefined,
ele: undefined,
extensions: undefined,
fix: undefined,
geoidheight: undefined,
hdop: undefined,
link: undefined,
magvar: undefined,
name: undefined,
pdop: undefined,