mirror of
https://github.com/owntracks/frontend.git
synced 2024-11-15 17:28:19 -07:00
Add "distance travelled" feature
This commit is contained in:
parent
8dc9611a77
commit
4078597f7a
@ -65,6 +65,7 @@ window.owntracks.config = {};
|
||||
- [`primaryColor`](#primarycolor)
|
||||
- [`selectedDevice`](#selecteddevice)
|
||||
- [`selectedUser`](#selecteduser)
|
||||
- [`showDistanceTravelled`](#showdistancetravelled)
|
||||
- [`startDateTime`](#startdatetime)
|
||||
- [`verbose`](#verbose)
|
||||
|
||||
@ -435,6 +436,16 @@ amount of data fetched after page load.
|
||||
};
|
||||
```
|
||||
|
||||
### `showDistanceTravelled`
|
||||
|
||||
Whether to calculate and show the travelled distance of the last fetched data in the
|
||||
header bar. `maxPointDistance` is being takein into account, if a distance between two
|
||||
subsequent points is greater than `maxPointDistance`, it will not contibute to the
|
||||
calculated travelled distance.
|
||||
|
||||
- Type: [`Boolean`]
|
||||
- Default: `true`
|
||||
|
||||
### `startDateTime`
|
||||
|
||||
Initial start date and time (browser timezone) for fetched data.
|
||||
|
@ -98,6 +98,16 @@
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="nav-shrink">
|
||||
<div
|
||||
class="nav-item"
|
||||
v-if="$config.showDistanceTravelled && distanceTravelled"
|
||||
>
|
||||
{{
|
||||
$t("Distance travelled: {distance}", {
|
||||
distance: humanReadableDistance(distanceTravelled),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<button
|
||||
class="button button-flat button-icon"
|
||||
@ -141,6 +151,7 @@ import "vue-ctk-date-time-picker/dist/vue-ctk-date-time-picker.css";
|
||||
import Dropdown from "@/components/Dropdown";
|
||||
import { DATE_TIME_FORMAT } from "@/constants";
|
||||
import * as types from "@/store/mutation-types";
|
||||
import { humanReadableDistance } from "@/util";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -165,7 +176,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["users", "devices", "map"]),
|
||||
...mapState(["users", "devices", "map", "distanceTravelled"]),
|
||||
selectedUser: {
|
||||
get() {
|
||||
return this.$store.state.selectedUser;
|
||||
@ -224,6 +235,7 @@ export default {
|
||||
"setStartDateTime",
|
||||
"setEndDateTime",
|
||||
]),
|
||||
humanReadableDistance,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -73,6 +73,7 @@ const DEFAULT_CONFIG = {
|
||||
primaryColor: "#3f51b5",
|
||||
selectedDevice: null,
|
||||
selectedUser: null,
|
||||
showDistanceTravelled: true,
|
||||
startDateTime,
|
||||
verbose: false,
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
"Select user": "Benutzer auswählen",
|
||||
"Show all": "Alle anzeigen",
|
||||
"Select device": "Gerät auswählen",
|
||||
"Distance travelled: {distance}": "Gereiste Entfernung: {distance}",
|
||||
"Download raw data": "Rohdaten herunterladen",
|
||||
"Information": "Information",
|
||||
"Show last known locations": "Zeige letzte bekannte Standorte",
|
||||
|
@ -10,6 +10,7 @@
|
||||
"Select user": "Select user",
|
||||
"Show all": "Show all",
|
||||
"Select device": "Select device",
|
||||
"Distance travelled: {distance}": "Distance travelled: {distance}",
|
||||
"Download raw data": "Download raw data",
|
||||
"Information": "Information",
|
||||
"Show last known locations": "Show last known locations",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as types from "@/store/mutation-types";
|
||||
import * as api from "@/api";
|
||||
import config from "@/config";
|
||||
import { isIsoDateTime } from "@/util";
|
||||
import { distanceBetweenCoordinates, isIsoDateTime } from "@/util";
|
||||
|
||||
/** @typedef {import("./types").QueryParams} QueryParams */
|
||||
/** @typedef {import("./types").User} User */
|
||||
@ -121,6 +121,35 @@ const getLastLocations = async ({ commit, state }) => {
|
||||
commit(types.SET_LAST_LOCATIONS, lastLocations);
|
||||
};
|
||||
|
||||
const _getDistanceTravelled = locationHistory => {
|
||||
let distanceTravelled = 0;
|
||||
Object.keys(locationHistory).forEach(user => {
|
||||
Object.keys(locationHistory[user]).forEach(device => {
|
||||
let lastLatLng = null;
|
||||
locationHistory[user][device].forEach(coordinate => {
|
||||
const latLng = L.latLng(coordinate.lat, coordinate.lon);
|
||||
if (lastLatLng !== null) {
|
||||
const distance = distanceBetweenCoordinates(lastLatLng, latLng);
|
||||
if (
|
||||
typeof config.map.maxPointDistance === "number" &&
|
||||
config.map.maxPointDistance > 0
|
||||
) {
|
||||
if (distance <= config.map.maxPointDistance) {
|
||||
// Part of the current group, add calculated distance to total
|
||||
distanceTravelled += distance;
|
||||
}
|
||||
} else {
|
||||
// If grouping is disabled always add calculated distance to total
|
||||
distanceTravelled += distance;
|
||||
}
|
||||
}
|
||||
lastLatLng = latLng;
|
||||
});
|
||||
});
|
||||
});
|
||||
return distanceTravelled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load location history of all devices, in the selected date range.
|
||||
*/
|
||||
@ -136,15 +165,19 @@ const getLocationHistory = async ({ commit, state }) => {
|
||||
} else {
|
||||
devices = state.devices;
|
||||
}
|
||||
commit(
|
||||
types.SET_LOCATION_HISTORY,
|
||||
await api.getLocationHistory(
|
||||
const locationHistory = await api.getLocationHistory(
|
||||
devices,
|
||||
state.startDateTime,
|
||||
state.endDateTime
|
||||
)
|
||||
);
|
||||
commit(types.SET_IS_LOADING, false);
|
||||
commit(types.SET_LOCATION_HISTORY, locationHistory);
|
||||
if (config.showDistanceTravelled) {
|
||||
commit(
|
||||
types.SET_DISTANCE_TRAVELLED,
|
||||
_getDistanceTravelled(locationHistory)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,11 @@ const locationHistoryLatLngGroups = state => {
|
||||
const latLng = L.latLng(coordinate.lat, coordinate.lon);
|
||||
// Skip if group splitting is disabled or this is the first
|
||||
// coordinate in the current group
|
||||
if (config.map.maxPointDistance !== null && latLngs.length > 0) {
|
||||
if (
|
||||
typeof config.map.maxPointDistance === "number" &&
|
||||
config.map.maxPointDistance > 0 &&
|
||||
latLngs.length > 0
|
||||
) {
|
||||
const lastLatLng = latLngs.slice(-1)[0];
|
||||
if (
|
||||
distanceBetweenCoordinates(lastLatLng, latLng) >
|
||||
|
@ -27,6 +27,7 @@ export default new Vuex.Store({
|
||||
zoom: config.map.zoom,
|
||||
layers: config.map.layers,
|
||||
},
|
||||
distanceTravelled: null,
|
||||
},
|
||||
getters,
|
||||
mutations,
|
||||
|
@ -11,3 +11,4 @@ export const SET_END_DATE_TIME = "SET_END_DATE_TIME";
|
||||
export const SET_MAP_CENTER = "SET_MAP_CENTER";
|
||||
export const SET_MAP_ZOOM = "SET_MAP_ZOOM";
|
||||
export const SET_MAP_LAYER_VISIBILITY = "SET_MAP_LAYER_VISIBILITY";
|
||||
export const SET_DISTANCE_TRAVELLED = "SET_DISTANCE_TRAVELLED";
|
||||
|
@ -40,4 +40,7 @@ export default {
|
||||
[types.SET_MAP_LAYER_VISIBILITY](state, { layer, visibility }) {
|
||||
state.map.layers[layer] = visibility;
|
||||
},
|
||||
[types.SET_DISTANCE_TRAVELLED](state, distanceTravelled) {
|
||||
state.distanceTravelled = distanceTravelled;
|
||||
},
|
||||
};
|
||||
|
@ -108,6 +108,7 @@ pre {
|
||||
nav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 20px;
|
||||
|
20
src/util.js
20
src/util.js
@ -83,3 +83,23 @@ export const download = (text, filename, mimeType = "text/plain") => {
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a distance in meters into a human-readable string with unit.
|
||||
*
|
||||
* This only supports m / km for now, but could read a config option and return
|
||||
* ft / mi.
|
||||
*
|
||||
* @param {Number} distance Distance in meters
|
||||
* @param {String} [mimeType] Formatted string including unit
|
||||
*/
|
||||
export const humanReadableDistance = distance => {
|
||||
let unit = "m";
|
||||
if (Math.abs(distance) >= 1000) {
|
||||
distance = distance / 1000;
|
||||
unit = "km";
|
||||
}
|
||||
return `${distance.toLocaleString(config.locale, {
|
||||
maximumFractionDigits: 1,
|
||||
})} ${unit}`;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
isIsoDateTime,
|
||||
degreesToRadians,
|
||||
distanceBetweenCoordinates,
|
||||
humanReadableDistance,
|
||||
} from "@/util";
|
||||
|
||||
describe("getApiUrl", () => {
|
||||
@ -102,3 +103,22 @@ describe("distanceBetweenCoordinates", () => {
|
||||
).toBe(9105627.810109457);
|
||||
});
|
||||
});
|
||||
|
||||
describe("humanReadableDistance", () => {
|
||||
test("expected results", () => {
|
||||
expect(humanReadableDistance(0)).toBe("0 m");
|
||||
expect(humanReadableDistance(1)).toBe("1 m");
|
||||
expect(humanReadableDistance(123)).toBe("123 m");
|
||||
expect(humanReadableDistance(123.4567)).toBe("123.5 m");
|
||||
expect(humanReadableDistance(999)).toBe("999 m");
|
||||
expect(humanReadableDistance(1000)).toBe("1 km");
|
||||
expect(humanReadableDistance(9000)).toBe("9 km");
|
||||
expect(humanReadableDistance(9900)).toBe("9.9 km");
|
||||
expect(humanReadableDistance(9990)).toBe("10 km");
|
||||
expect(humanReadableDistance(9999)).toBe("10 km");
|
||||
expect(humanReadableDistance(9999.0)).toBe("10 km");
|
||||
expect(humanReadableDistance(9999.9999)).toBe("10 km");
|
||||
expect(humanReadableDistance(100000)).toBe("100 km");
|
||||
expect(humanReadableDistance(-42)).toBe("-42 m");
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user