(Re)Adding background geolocation.

This commit is contained in:
Eric van der Vlist 2022-12-13 20:59:39 +01:00
parent 4dbdcecadd
commit acc1d7cc1e
9 changed files with 249 additions and 8 deletions

9
package-lock.json generated
View File

@ -11,6 +11,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@awesome-cordova-plugins/geolocation": "^6.2.0", "@awesome-cordova-plugins/geolocation": "^6.2.0",
"@capacitor-community/background-geolocation": "^1.2.6",
"@capacitor/android": "^3.4.3", "@capacitor/android": "^3.4.3",
"@capacitor/core": "^3.4.3", "@capacitor/core": "^3.4.3",
"@capacitor/ios": "^3.4.3", "@capacitor/ios": "^3.4.3",
@ -550,6 +551,14 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@capacitor-community/background-geolocation": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@capacitor-community/background-geolocation/-/background-geolocation-1.2.6.tgz",
"integrity": "sha512-zYe2gUf+CXoE7tbBzd6CNv3yW1SRCVAxVKg5t63zHbMQR2JfurVlfIEnXjLyc+strAMPavGwbIXEjiGv5jJvdA==",
"peerDependencies": {
"@capacitor/core": ">=3.0.0"
}
},
"node_modules/@capacitor/android": { "node_modules/@capacitor/android": {
"version": "3.9.0", "version": "3.9.0",
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-3.9.0.tgz", "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-3.9.0.tgz",

View File

@ -27,6 +27,7 @@
}, },
"dependencies": { "dependencies": {
"@awesome-cordova-plugins/geolocation": "^6.2.0", "@awesome-cordova-plugins/geolocation": "^6.2.0",
"@capacitor-community/background-geolocation": "^1.2.6",
"@capacitor/android": "^3.4.3", "@capacitor/android": "^3.4.3",
"@capacitor/core": "^3.4.3", "@capacitor/core": "^3.4.3",
"@capacitor/ios": "^3.4.3", "@capacitor/ios": "^3.4.3",

View File

@ -12,13 +12,54 @@ import Dialog from '../dialog';
import { useI18n } from '@solid-primitives/i18n'; import { useI18n } from '@solid-primitives/i18n';
import GpxChooser from '../gpx-chooser'; import GpxChooser from '../gpx-chooser';
import { currentGpxId } from '../gpx-dialog'; import { currentGpxId } from '../gpx-dialog';
import {
startBackgroundGeolocation,
stopBackgroundGeolocation,
} from '../../lib/background-geolocation';
import dispatch from '../../workers/dispatcher-main';
import { emptyTrk } from '../../db/trk';
import getUri from '../../lib/ids';
declare global {
var $lastValidLocationTime: number;
}
const GpxRecord: Component<{}> = (props) => { const GpxRecord: Component<{}> = (props) => {
const [t] = useI18n(); const [t] = useI18n();
const [state, setState] = createSignal('stopped'); const [state, setState] = createSignal('stopped');
const [open, setOpen] = createSignal(false); const [open, setOpen] = createSignal(false);
const [watcherId, setWatcherId] = createSignal();
const [gpxId, setGpxId] = createSignal<string>(''); const [gpxId, setGpxId] = createSignal<string>('');
const [trkId, setTrkId] = createSignal<string>(''); const [trksegId, setTrksegId] = createSignal<IdTrkseg>();
const newLocationHandler = async (location: any) => {
console.log(
`Location filtering, elapsed time: ${
location.time - globalThis.$lastValidLocationTime
}`
);
if (location.time - globalThis.$lastValidLocationTime > 15000) {
globalThis.$lastValidLocationTime = location.time;
await dispatch({
action: 'appendTrkpt',
params: {
trksegIdObj: trksegId(),
trkpt: {
$: {
lat: location.latitude,
lon: location.longitude,
},
ele: location.altitude,
time: new Date(location.time).toISOString(),
extensions: {
speed: location.speed,
accuracy: location.accuracy,
},
},
},
});
}
};
const handleClickOpen = () => { const handleClickOpen = () => {
if (state() === 'stopped') { if (state() === 'stopped') {
@ -29,22 +70,37 @@ const GpxRecord: Component<{}> = (props) => {
const handleClose = () => { const handleClose = () => {
setOpen(false); setOpen(false);
}; };
const startRecordingHandler = () => { const startRecordingHandler = async () => {
const trkId = await dispatch({
action: 'appendTrk',
params: { gpxId: gpxId(), trk: emptyTrk },
});
const trkIdObj = getUri('trk', trkId);
setTrksegId({ ...trkIdObj, trkseg: 0 });
globalThis.$lastValidLocationTime = 0;
const id = await startBackgroundGeolocation(newLocationHandler, 20);
setWatcherId(id);
setState('recording'); setState('recording');
setOpen(false); setOpen(false);
}; };
const pauseRecordingHandler = () => { const pauseRecordingHandler = () => {
stopBackgroundGeolocation(watcherId());
setState('paused'); setState('paused');
setOpen(false); setOpen(false);
}; };
const resumeRecordingHandler = () => { const resumeRecordingHandler = async () => {
setTrksegId({ ...trksegId()!, trkseg: trksegId()!.trkseg + 1 });
const id = await startBackgroundGeolocation(newLocationHandler, 20);
setWatcherId(id);
setState('recording'); setState('recording');
setOpen(false); setOpen(false);
}; };
const stopRecordingHandler = () => { const stopRecordingHandler = () => {
stopBackgroundGeolocation(watcherId());
setState('stopped'); setState('stopped');
setOpen(false); setOpen(false);
}; };
return ( return (
<> <>
<div class={style.control}> <div class={style.control}>
@ -81,6 +137,7 @@ const GpxRecord: Component<{}> = (props) => {
color='primary' color='primary'
variant='contained' variant='contained'
onClick={startRecordingHandler} onClick={startRecordingHandler}
disabled={gpxId() === 'new'}
sx={{ sx={{
width: '100%', width: '100%',
margin: '10px', margin: '10px',

View File

@ -1,3 +1,4 @@
import { slice } from 'lodash';
import { getWpt } from '../db/wpt'; import { getWpt } from '../db/wpt';
import { returnAgain } from '../workers/dispatcher-worker'; import { returnAgain } from '../workers/dispatcher-worker';
import { getAllGpxes, getGpx } from './gpx'; import { getAllGpxes, getGpx } from './gpx';
@ -31,14 +32,24 @@ const sendUpdate = async (params: any) => {
}; };
const changeHandler = async (change: any) => { const changeHandler = async (change: any) => {
console.log({ caller: 'ChangeHandler', change }); console.log({ caller: 'ChangeHandler', change, watches: globalThis.watches });
const { id } = change; const { id } = change;
if (!globalThis.watches) { if (!globalThis.watches) {
globalThis.watches = new Map(); globalThis.watches = new Map();
} }
sendUpdate(globalThis.watches.get(id)); sendUpdate(globalThis.watches.get(id));
const parentId = id.substring(0, id.lastIndexOf('/')); const tokens = id.split('/').slice(0, -1);
if (['1wpt', '2rte', '3trk'].includes(tokens.at(-1))) {
tokens.splice(-1);
}
const parentId = tokens.join('/');
console.log({
caller: 'ChangeHandler',
change,
watches: globalThis.watches,
parentId,
});
if (parentId === 'gpx') { if (parentId === 'gpx') {
const gpxes = await getAllGpxes(); const gpxes = await getAllGpxes();
@ -64,12 +75,23 @@ const changeHandler = async (change: any) => {
export default changeHandler; export default changeHandler;
export const getAndWatch = async (params: any) => { export const getAndWatch = async (params: any) => {
console.log({
caller: 'ChangeHandler / getAndWatch',
params,
watches: globalThis.watches,
});
const { method, _dispatchId, id, ...otherParams } = params; const { method, _dispatchId, id, ...otherParams } = params;
if (!globalThis.watches) { if (!globalThis.watches) {
globalThis.watches = new Map(); globalThis.watches = new Map();
} }
globalThis.watches.set(id, params); globalThis.watches.set(id, params);
const returnValue = await methods[<keyof typeof methods>method](params); const returnValue = await methods[<keyof typeof methods>method](params);
console.log({
caller: 'ChangeHandler / getAndWatch',
params,
returnValue,
watches: globalThis.watches,
});
return returnValue; return returnValue;
}; };

View File

@ -392,6 +392,31 @@ export const getGpx = async (params: any) => {
return gpx; return gpx;
}; };
export const getNextGpxTrkId = async (gpxId: string) => {
const docs = await getFamily(gpxId, { include_docs: true });
const gpxIdObj = getUri('gpx', gpxId);
console.log({ caller: 'getNextGpxTrkId', gpxId, gpxIdObj, docs });
let trkId = '';
docs.rows.forEach((row: any) => {
if (row.doc.type === 'trk') {
trkId = row.doc._id;
}
});
if (trkId !== '') {
const trkIdObj = getUri('trk', trkId);
return getUri('trk', { gpx: gpxIdObj.gpx, trk: trkIdObj.trk + 1 });
}
return getUri('trk', { gpx: gpxIdObj.gpx, trk: 0 });
};
export const appendTrk = async (params: any) => {
const { gpxId, trk } = params;
const trkId = await getNextGpxTrkId(gpxId);
console.log({ caller: 'appendTrk', gpxId, trk, trkId });
await put(trkId, 'trk', (doc) => trk, {});
return trkId;
};
export const putGpx = async (params: any) => { export const putGpx = async (params: any) => {
let { id, gpx } = params; let { id, gpx } = params;

View File

@ -10,7 +10,7 @@ export const put = async (
update: (doc: any) => any, update: (doc: any) => any,
defaultDoc: any defaultDoc: any
) => { ) => {
var current; let current;
try { try {
current = await db.get(_id); current = await db.get(_id);
} catch { } catch {

View File

@ -1,7 +1,8 @@
import getUri from '../lib/ids'; import getUri, { intToTrkptId } from '../lib/ids';
import { appendToArray } from './gpx'; import { appendToArray } from './gpx';
import { getFamily, put } from './lib'; import { getFamily, put } from './lib';
import { putNewTrk } from './trk'; import { putNewTrk } from './trk';
import { emptyWpt } from './wpt';
const emptyTrkseg: Trkseg = { const emptyTrkseg: Trkseg = {
trkpt: undefined, trkpt: undefined,
@ -46,3 +47,30 @@ export const getTrkseg = async (params: any) => {
}); });
return trkseg; return trkseg;
}; };
export const putTrkseg = async (trkSegId: string, trkSeg = emptyTrkseg) => {
await put(
trkSegId,
'trkseg',
(doc) => {
return trkSeg;
},
emptyTrkseg
);
return trkSegId;
};
export const appendTrkpt = async (params: any) => {
const { trksegIdObj, trkpt } = params;
const idTrkseg = getUri('trkseg', trksegIdObj);
try {
await db.get(idTrkseg);
} catch {
await put(idTrkseg, 'trkseg', (doc) => doc, emptyTrkseg);
}
const trkptIdPart = intToTrkptId(new Date(trkpt.time as string).valueOf());
const trkptIdObj = { ...trksegIdObj, trkpt: trkptIdPart };
const trkptId = getUri('trkpt', trkptIdObj);
await put(trkptId, 'trkpt', (doc) => trkpt, emptyWpt);
return trkptId;
};

View File

@ -0,0 +1,96 @@
import { BackgroundGeolocationPlugin } from '@capacitor-community/background-geolocation';
import { registerPlugin } from '@capacitor/core';
const BackgroundGeolocation = registerPlugin<BackgroundGeolocationPlugin>(
'BackgroundGeolocation'
);
const backgroundGeolocationConfig = {
// If the "backgroundMessage" option is defined, the watcher will
// provide location updates whether the app is in the background or the
// foreground. If it is not defined, location updates are only
// guaranteed in the foreground. This is true on both platforms.
// On Android, a notification must be shown to continue receiving
// location updates in the background. This option specifies the text of
// that notification.
backgroundMessage: 'Cancel to prevent battery drain.',
// The title of the notification mentioned above. Defaults to "Using
// your location".
backgroundTitle: 'Tracking You.',
// Whether permissions should be requested from the user automatically,
// if they are not already granted. Defaults to "true".
requestPermissions: true,
// If "true", stale locations may be delivered while the device
// obtains a GPS fix. You are responsible for checking the "time"
// property. If "false", locations are guaranteed to be up to date.
// Defaults to "false".
stale: false,
// The minimum number of metres between subsequent locations. Defaults
// to 0.
distanceFilter: 10,
};
export const startBackgroundGeolocation = async (
newLocationHandler: any,
distanceFilter: number
) => {
backgroundGeolocationConfig.distanceFilter = distanceFilter;
const locationHandler = (location: any, error: any) => {
console.log('com.dyomedea.dyomedea LOG', ' - Callback');
if (error) {
if (error.code === 'NOT_AUTHORIZED') {
if (
window.confirm(
'This app needs your location, ' +
'but does not have permission.\n\n' +
'Open settings now?'
)
) {
// It can be useful to direct the user to their device's
// settings when location permissions have been denied. The
// plugin provides the 'openSettings' method to do exactly
// this.
BackgroundGeolocation.openSettings();
}
}
return console.error('com.dyomedea.dyomedea LOG', ' - error: ', error);
}
console.log(location);
if (location !== undefined) {
newLocationHandler(location);
}
return console.log('com.dyomedea.dyomedea LOG', ' - location: ', location);
};
var watcher_id;
console.log('com.dyomedea.dyomedea LOG', ' - Adding the watcher');
await BackgroundGeolocation.addWatcher(
backgroundGeolocationConfig,
locationHandler
)
.then(function after_the_watcher_has_been_added(id) {
// When a watcher is no longer needed, it should be removed by calling
// 'removeWatcher' with an object containing its ID.
console.log('com.dyomedea.dyomedea LOG', ' - Watcher added');
watcher_id = id;
/*BackgroundGeolocation.removeWatcher({
id: watcher_id,
}); */
})
.catch((reason) => {
console.error('com.dyomedea.dyomedea LOG', ' - reason: ', reason);
});
return watcher_id;
};
export const stopBackgroundGeolocation = (watcher_id: any) => {
BackgroundGeolocation.removeWatcher({
id: watcher_id,
});
};

View File

@ -7,11 +7,12 @@ import {
pruneAndSaveImportedGpx, pruneAndSaveImportedGpx,
getGpx, getGpx,
putGpx, putGpx,
appendTrk,
getAllGpxes, getAllGpxes,
getAllGpxesWithSummary, getAllGpxesWithSummary,
} from '../db/gpx'; } from '../db/gpx';
import { getTrk, putNewTrk } from '../db/trk'; import { getTrk, putNewTrk } from '../db/trk';
import { getTrkseg } from '../db/trkseg'; import { getTrkseg, appendTrkpt } from '../db/trkseg';
import { getWpt, putWpt } from '../db/wpt'; import { getWpt, putWpt } from '../db/wpt';
//const self = globalThis as unknown as WorkerGlobalScope; //const self = globalThis as unknown as WorkerGlobalScope;
@ -29,8 +30,10 @@ onmessage = async function (e) {
getAllGpxesWithSummary, getAllGpxesWithSummary,
getGpx, getGpx,
putGpx, putGpx,
appendTrk,
getTrk, getTrk,
getTrkseg, getTrkseg,
appendTrkpt,
getWpt, getWpt,
putWpt, putWpt,