diff --git a/www/__mocks__/fakeLabels.json b/www/__mocks__/fakeLabels.json
new file mode 100644
index 000000000..676dc97b6
--- /dev/null
+++ b/www/__mocks__/fakeLabels.json
@@ -0,0 +1,208 @@
+{
+ "MODE": [
+ {
+ "value": "walk",
+ "baseMode": "WALKING",
+ "met_equivalent": "WALKING",
+ "kgCo2PerKm": 0
+ },
+ {
+ "value": "e-bike",
+ "baseMode": "E_BIKE",
+ "met": {
+ "ALL": {
+ "range": [0, -1],
+ "mets": 4.9
+ }
+ },
+ "kgCo2PerKm": 0.00728
+ },
+ {
+ "value": "bike",
+ "baseMode": "BICYCLING",
+ "met_equivalent": "BICYCLING",
+ "kgCo2PerKm": 0
+ },
+ {
+ "value": "bikeshare",
+ "baseMode": "BICYCLING",
+ "met_equivalent": "BICYCLING",
+ "kgCo2PerKm": 0
+ },
+ {
+ "value": "scootershare",
+ "baseMode": "E_SCOOTER",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.00894
+ },
+ {
+ "value": "drove_alone",
+ "baseMode": "CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.22031
+ },
+ {
+ "value": "shared_ride",
+ "baseMode": "CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.11015
+ },
+ {
+ "value": "hybrid_drove_alone",
+ "baseMode": "CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.127
+ },
+ {
+ "value": "hybrid_shared_ride",
+ "baseMode": "CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.0635
+ },
+ {
+ "value": "e_car_drove_alone",
+ "baseMode": "E_CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.08216
+ },
+ {
+ "value": "e_car_shared_ride",
+ "baseMode": "E_CAR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.04108
+ },
+ {
+ "value": "taxi",
+ "baseMode": "TAXI",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.30741
+ },
+ {
+ "value": "bus",
+ "baseMode": "BUS",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.20727
+ },
+ {
+ "value": "train",
+ "baseMode": "TRAIN",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.12256
+ },
+ {
+ "value": "free_shuttle",
+ "baseMode": "BUS",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.20727
+ },
+ {
+ "value": "air",
+ "baseMode": "AIR",
+ "met_equivalent": "IN_VEHICLE",
+ "kgCo2PerKm": 0.09975
+ },
+ {
+ "value": "not_a_trip",
+ "baseMode": "UNKNOWN",
+ "met_equivalent": "UNKNOWN",
+ "kgCo2PerKm": 0
+ },
+ {
+ "value": "other",
+ "baseMode": "OTHER",
+ "met_equivalent": "UNKNOWN",
+ "kgCo2PerKm": 0
+ }
+ ],
+ "PURPOSE": [
+ {
+ "value": "home"
+ },
+ {
+ "value": "work"
+ },
+ {
+ "value": "at_work"
+ },
+ {
+ "value": "school"
+ },
+ {
+ "value": "transit_transfer"
+ },
+ {
+ "value": "shopping"
+ },
+ {
+ "value": "meal"
+ },
+ {
+ "value": "pick_drop_person"
+ },
+ {
+ "value": "pick_drop_item"
+ },
+ {
+ "value": "personal_med"
+ },
+ {
+ "value": "access_recreation"
+ },
+ {
+ "value": "exercise"
+ },
+ {
+ "value": "entertainment"
+ },
+ {
+ "value": "religious"
+ },
+ {
+ "value": "other"
+ }
+ ],
+ "REPLACED_MODE": [
+ {
+ "value": "no_travel"
+ },
+ {
+ "value": "walk"
+ },
+ {
+ "value": "bike"
+ },
+ {
+ "value": "bikeshare"
+ },
+ {
+ "value": "scootershare"
+ },
+ {
+ "value": "drove_alone"
+ },
+ {
+ "value": "shared_ride"
+ },
+ {
+ "value": "e_car_drove_alone"
+ },
+ {
+ "value": "e_car_shared_ride"
+ },
+ {
+ "value": "taxi"
+ },
+ {
+ "value": "bus"
+ },
+ {
+ "value": "train"
+ },
+ {
+ "value": "free_shuttle"
+ },
+ {
+ "value": "other"
+ }
+ ]
+}
diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts
new file mode 100644
index 000000000..0ae025bff
--- /dev/null
+++ b/www/__tests__/customMetricsHelper.test.ts
@@ -0,0 +1,51 @@
+import { getConfig } from '../js/config/dynamicConfig';
+import {
+ getCustomFootprint,
+ getCustomMETs,
+ initCustomDatasetHelper,
+} from '../js/metrics/customMetricsHelper';
+import { mockBEMUserCache } from '../__mocks__/cordovaMocks';
+import { mockLogger } from '../__mocks__/globalMocks';
+import fakeLabels from '../__mocks__/fakeLabels.json';
+
+mockBEMUserCache();
+mockLogger();
+
+global.fetch = (url: string) =>
+ new Promise((rs, rj) => {
+ setTimeout(() =>
+ rs({
+ text: () =>
+ new Promise((rs, rj) => {
+ let myJSON = JSON.stringify(fakeLabels);
+ setTimeout(() => rs(myJSON), 100);
+ }),
+ }),
+ );
+ }) as any;
+
+it('gets the custom mets', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getCustomMETs()).toMatchObject({
+ walk: expect.any(Object),
+ bike: expect.any(Object),
+ bikeshare: expect.any(Object),
+ 'e-bike': expect.any(Object),
+ scootershare: expect.any(Object),
+ drove_alone: expect.any(Object),
+ });
+});
+
+it('gets the custom footprint', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getCustomFootprint()).toMatchObject({
+ walk: expect.any(Number),
+ bike: expect.any(Number),
+ bikeshare: expect.any(Number),
+ 'e-bike': expect.any(Number),
+ scootershare: expect.any(Number),
+ drove_alone: expect.any(Number),
+ });
+});
diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts
new file mode 100644
index 000000000..842442153
--- /dev/null
+++ b/www/__tests__/footprintHelper.test.ts
@@ -0,0 +1,63 @@
+import { initCustomDatasetHelper } from '../js/metrics/customMetricsHelper';
+import {
+ clearHighestFootprint,
+ getFootprintForMetrics,
+ getHighestFootprint,
+ getHighestFootprintForDistance,
+} from '../js/metrics/footprintHelper';
+import { getConfig } from '../js/config/dynamicConfig';
+import { mockBEMUserCache } from '../__mocks__/cordovaMocks';
+import { mockLogger } from '../__mocks__/globalMocks';
+import fakeLabels from '../__mocks__/fakeLabels.json';
+
+mockBEMUserCache();
+mockLogger();
+
+global.fetch = (url: string) =>
+ new Promise((rs, rj) => {
+ setTimeout(() =>
+ rs({
+ text: () =>
+ new Promise((rs, rj) => {
+ let myJSON = JSON.stringify(fakeLabels);
+ setTimeout(() => rs(myJSON), 100);
+ }),
+ }),
+ );
+ }) as any;
+
+beforeEach(() => {
+ clearHighestFootprint();
+});
+
+const custom_metrics = [
+ { key: 'walk', values: 3000 },
+ { key: 'bike', values: 6500 },
+ { key: 'drove_alone', values: 10000 },
+ { key: 'scootershare', values: 25000 },
+ { key: 'unicycle', values: 5000 },
+];
+
+it('gets footprint for metrics (custom, fallback 0)', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getFootprintForMetrics(custom_metrics, 0)).toBe(2.4266);
+});
+
+it('gets footprint for metrics (custom, fallback 0.1)', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getFootprintForMetrics(custom_metrics, 0.1)).toBe(2.4266 + 0.5);
+});
+
+it('gets the highest footprint from the dataset, custom', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getHighestFootprint()).toBe(0.30741);
+});
+
+it('gets the highest footprint for distance, custom', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getHighestFootprintForDistance(12345)).toBe(0.30741 * (12345 / 1000));
+});
diff --git a/www/__tests__/metHelper.test.ts b/www/__tests__/metHelper.test.ts
new file mode 100644
index 000000000..bc477daa0
--- /dev/null
+++ b/www/__tests__/metHelper.test.ts
@@ -0,0 +1,40 @@
+import { getMet } from '../js/metrics/metHelper';
+import { mockBEMUserCache } from '../__mocks__/cordovaMocks';
+import { mockLogger } from '../__mocks__/globalMocks';
+import fakeLabels from '../__mocks__/fakeLabels.json';
+import { getConfig } from '../js/config/dynamicConfig';
+import { initCustomDatasetHelper } from '../js/metrics/customMetricsHelper';
+
+mockBEMUserCache();
+mockLogger();
+
+global.fetch = (url: string) =>
+ new Promise((rs, rj) => {
+ setTimeout(() =>
+ rs({
+ text: () =>
+ new Promise((rs, rj) => {
+ let myJSON = JSON.stringify(fakeLabels);
+ setTimeout(() => rs(myJSON), 100);
+ }),
+ }),
+ );
+ }) as any;
+
+it('gets met for mode and speed', () => {
+ expect(getMet('WALKING', 1.47523, 0)).toBe(4.3);
+ expect(getMet('BICYCLING', 4.5, 0)).toBe(6.8);
+ expect(getMet('UNICYCLE', 100, 0)).toBe(0);
+ expect(getMet('CAR', 25, 1)).toBe(0);
+});
+
+it('gets custom met for mode and speed', async () => {
+ const appConfig = await getConfig();
+ await initCustomDatasetHelper(appConfig);
+ expect(getMet('walk', 1.47523, 0)).toBe(4.3);
+ expect(getMet('bike', 4.5, 0)).toBe(6.8);
+ expect(getMet('unicycle', 100, 0)).toBe(0);
+ expect(getMet('drove_alone', 25, 1)).toBe(0);
+ expect(getMet('e-bike', 6, 1)).toBe(4.9);
+ expect(getMet('e-bike', 12, 1)).toBe(4.9);
+});
diff --git a/www/index.js b/www/index.js
index 74ebc4c5d..997141073 100644
--- a/www/index.js
+++ b/www/index.js
@@ -13,6 +13,4 @@ import './js/i18n-utils.js';
import './js/main.js';
import './js/diary.js';
import './js/diary/services.js';
-import './js/metrics-factory.js';
-import './js/metrics-mappings.js';
import './js/plugin/logger.ts';
diff --git a/www/js/App.tsx b/www/js/App.tsx
index a955b032d..2eece7f55 100644
--- a/www/js/App.tsx
+++ b/www/js/App.tsx
@@ -19,6 +19,7 @@ import { initPushNotify } from './splash/pushNotifySettings';
import { initStoreDeviceSettings } from './splash/storeDeviceSettings';
import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler';
import { withErrorBoundary } from './plugin/ErrorBoundary';
+import { initCustomDatasetHelper } from './metrics/customMetricsHelper';
const defaultRoutes = (t) => [
{
@@ -77,6 +78,7 @@ const App = () => {
initPushNotify();
initStoreDeviceSettings();
initRemoteNotifyHandler();
+ initCustomDatasetHelper(appConfig);
}, [appConfig]);
const appContextValue = {
diff --git a/www/js/components/Chart.tsx b/www/js/components/Chart.tsx
index 4ebf49c24..257eb3cf6 100644
--- a/www/js/components/Chart.tsx
+++ b/www/js/components/Chart.tsx
@@ -138,7 +138,9 @@ const Chart = ({
logDebug(`Horizontal axis callback: i = ${i};
chartDatasets = ${JSON.stringify(chartDatasets)};
chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`);
- const label = chartDatasets[0].data[i].y;
+ //account for different data possiblities
+ const label =
+ chartDatasets[0].data[i]?.y || chartDatasets[i].data[0]?.y;
if (typeof label == 'string' && label.includes('\n'))
return label.split('\n');
return label;
@@ -175,7 +177,9 @@ const Chart = ({
logDebug(`Vertical axis callback: i = ${i};
chartDatasets = ${JSON.stringify(chartDatasets)};
chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`);
- const label = chartDatasets[0].data[i].x;
+ //account for different data possiblities - one mode per week, one mode both weeks, mixed weeks
+ const label =
+ chartDatasets[0].data[i]?.x || chartDatasets[i].data[0]?.x;
if (typeof label == 'string' && label.includes('\n'))
return label.split('\n');
return label;
diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx
index f239b8b29..914c97d82 100644
--- a/www/js/control/ProfileSettings.jsx
+++ b/www/js/control/ProfileSettings.jsx
@@ -43,7 +43,6 @@ const ProfileSettings = () => {
const { setPermissionsPopupVis } = useContext(AppContext);
//angular services needed
- const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper');
const NotificationScheduler = getAngularService('NotificationScheduler');
const ControlHelper = getAngularService('ControlHelper');
@@ -54,7 +53,6 @@ const ProfileSettings = () => {
//states and variables used to control/create the settings
const [opCodeVis, setOpCodeVis] = useState(false);
const [nukeSetVis, setNukeVis] = useState(false);
- const [carbonDataVis, setCarbonDataVis] = useState(false);
const [forceStateVis, setForceStateVis] = useState(false);
const [logoutVis, setLogoutVis] = useState(false);
const [invalidateSuccessVis, setInvalidateSuccessVis] = useState(false);
@@ -82,9 +80,6 @@ const ProfileSettings = () => {
const [uploadReason, setUploadReason] = useState('');
const appVersion = useRef();
- let carbonDatasetString =
- t('general-settings.carbon-dataset') + ': ' + CarbonDatasetHelper.getCurrentCarbonDatasetCode();
- const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions();
const stateActions = [
{ text: 'Initialize', transition: 'INITIALIZE' },
{ text: 'Start trip', transition: 'EXITED_GEOFENCE' },
@@ -358,16 +353,6 @@ const ProfileSettings = () => {
forceTransition(stateObject.transition);
};
- const onSelectCarbon = function (carbonObject) {
- console.log('changeCarbonDataset(): chose locale ' + carbonObject.value);
- CarbonDatasetHelper.saveCurrentCarbonDatasetLocale(carbonObject.value); //there's some sort of error here
- //Unhandled Promise Rejection: While logging, error -[NSNull UTF8String]: unrecognized selector sent to instance 0x7fff8a625fb0
- carbonDatasetString =
- i18next.t('general-settings.carbon-dataset') +
- ': ' +
- CarbonDatasetHelper.getCurrentCarbonDatasetCode();
- };
-
//conditional creation of setting sections
let logUploadSection;
@@ -438,10 +423,6 @@ const ProfileSettings = () => {
textKey="control.medium-accuracy"
action={toggleLowAccuracy}
switchValue={collectSettings.lowAccuracy}>
- setCarbonDataVis(true)}>
{
- {/* menu for "set carbon dataset - only somewhat working" */}
- clearNotifications()}>
-
{/* force state sheet */}
999 ? Math.round(v / 1000) + 'k kg CO₂' : Math.round(v) + ' kg CO₂';
- };
- fh.getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) {
- var footprint = fh.getFootprint();
- var result = 0;
- for (var i in userMetrics) {
- var mode = userMetrics[i].key;
- if (mode == 'ON_FOOT') {
- mode = 'WALKING';
- }
-
- if (mode in footprint) {
- result += footprint[mode] * mtokm(userMetrics[i].values);
- } else if (mode == 'IN_VEHICLE') {
- result +=
- ((footprint['CAR'] +
- footprint['BUS'] +
- footprint['LIGHT_RAIL'] +
- footprint['TRAIN'] +
- footprint['TRAM'] +
- footprint['SUBWAY']) /
- 6) *
- mtokm(userMetrics[i].values);
- } else {
- console.warn(
- 'WARNING FootprintHelper.getFootprintFromMetrics() was requested for an unknown mode: ' +
- mode +
- ' metrics JSON: ' +
- JSON.stringify(userMetrics),
- );
- result += defaultIfMissing * mtokm(userMetrics[i].values);
- }
- }
- return result;
- };
- fh.getLowestFootprintForDistance = function (distance) {
- var footprint = fh.getFootprint();
- var lowestFootprint = Number.MAX_SAFE_INTEGER;
- for (var mode in footprint) {
- if (mode == 'WALKING' || mode == 'BICYCLING') {
- // these modes aren't considered when determining the lowest carbon footprint
- } else {
- lowestFootprint = Math.min(lowestFootprint, footprint[mode]);
- }
- }
- return lowestFootprint * mtokm(distance);
- };
-
- fh.getHighestFootprint = function () {
- if (!highestFootprint) {
- var footprint = fh.getFootprint();
- let footprintList = [];
- for (var mode in footprint) {
- footprintList.push(footprint[mode]);
- }
- highestFootprint = Math.max(...footprintList);
- }
- return highestFootprint;
- };
-
- fh.getHighestFootprintForDistance = function (distance) {
- return fh.getHighestFootprint() * mtokm(distance);
- };
-
- var getLowestMotorizedNonAirFootprint = function (footprint, rlmCO2) {
- var lowestFootprint = Number.MAX_SAFE_INTEGER;
- for (var mode in footprint) {
- if (mode == 'AIR_OR_HSR' || mode == 'air') {
- console.log('Air mode, ignoring');
- } else {
- if (footprint[mode] == 0 || footprint[mode] <= rlmCO2) {
- console.log(
- 'Non motorized mode or footprint <= range_limited_motorized',
- mode,
- footprint[mode],
- rlmCO2,
- );
- } else {
- lowestFootprint = Math.min(lowestFootprint, footprint[mode]);
- }
- }
- }
- return lowestFootprint;
- };
-
- fh.getOptimalDistanceRanges = function () {
- const FIVE_KM = 5 * 1000;
- const SIX_HUNDRED_KM = 600 * 1000;
- if (!fh.useCustom) {
- const defaultFootprint = CarbonDatasetHelper.getCurrentCarbonDatasetFootprint();
- const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint(defaultFootprint);
- const airFootprint = defaultFootprint['AIR_OR_HSR'];
- return [
- { low: 0, high: FIVE_KM, optimal: 0 },
- { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir },
- { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint },
- ];
- } else {
- // custom footprint, let's get the custom values
- const customFootprint = CustomDatasetHelper.getCustomFootprint();
- let airFootprint = customFootprint['air'];
- if (!airFootprint) {
- // 2341 BTU/PMT from
- // https://tedb.ornl.gov/wp-content/uploads/2021/02/TEDB_Ed_39.pdf#page=68
- // 159.25 lb per million BTU from EIA
- // https://www.eia.gov/environment/emissions/co2_vol_mass.php
- // (2341 * (159.25/1000000))/(1.6*2.2) = 0.09975, rounded up a bit
- console.log('No entry for air in ', customFootprint, ' using default');
- airFootprint = 0.1;
- }
- const rlm = CustomDatasetHelper.range_limited_motorized;
- if (!rlm) {
- return [
- { low: 0, high: FIVE_KM, optimal: 0 },
- { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir },
- { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint },
- ];
- } else {
- console.log('Found range_limited_motorized mode', rlm);
- const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint(
- customFootprint,
- rlm.kgCo2PerKm,
- );
- return [
- { low: 0, high: FIVE_KM, optimal: 0 },
- { low: FIVE_KM, high: rlm.range_limit_km * 1000, optimal: rlm.kgCo2PerKm },
- {
- low: rlm.range_limit_km * 1000,
- high: SIX_HUNDRED_KM,
- optimal: lowestMotorizedNonAir,
- },
- { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint },
- ];
- }
- }
- };
-
- return fh;
- })
-
- .factory('CalorieCal', function (METDatasetHelper, CustomDatasetHelper) {
- var cc = {};
- var highestMET = 0;
- var USER_DATA_KEY = 'user-data';
- cc.useCustom = false;
-
- cc.setUseCustomFootprint = function () {
- cc.useCustom = true;
- };
-
- cc.getMETs = function () {
- if (this.useCustom == true) {
- return CustomDatasetHelper.getCustomMETs();
- } else {
- return METDatasetHelper.getStandardMETs();
- }
- };
-
- cc.set = function (info) {
- return storageSet(USER_DATA_KEY, info);
- };
- cc.get = function () {
- return storageGet(USER_DATA_KEY);
- };
- cc.delete = function () {
- return storageRemove(USER_DATA_KEY);
- };
- Number.prototype.between = function (min, max) {
- return this >= min && this <= max;
- };
- cc.getHighestMET = function () {
- if (!highestMET) {
- var met = cc.getMETs();
- let metList = [];
- for (var mode in met) {
- var rangeList = met[mode];
- for (var range in rangeList) {
- metList.push(rangeList[range].mets);
- }
- }
- highestMET = Math.max(...metList);
- }
- return highestMET;
- };
- cc.getMet = function (mode, speed, defaultIfMissing) {
- if (mode == 'ON_FOOT') {
- console.log("CalorieCal.getMet() converted 'ON_FOOT' to 'WALKING'");
- mode = 'WALKING';
- }
- let currentMETs = cc.getMETs();
- if (!currentMETs[mode]) {
- console.warn('CalorieCal.getMet() Illegal mode: ' + mode);
- return defaultIfMissing; //So the calorie sum does not break with wrong return type
- }
- for (var i in currentMETs[mode]) {
- if (mpstomph(speed).between(currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) {
- return currentMETs[mode][i].mets;
- } else if (mpstomph(speed) < 0) {
- console.log('CalorieCal.getMet() Negative speed: ' + mpstomph(speed));
- return 0;
- }
- }
- };
- var mpstomph = function (mps) {
- return 2.23694 * mps;
- };
- var lbtokg = function (lb) {
- return lb * 0.453592;
- };
- var fttocm = function (ft) {
- return ft * 30.48;
- };
- cc.getCorrectedMet = function (met, gender, age, height, heightUnit, weight, weightUnit) {
- var height = heightUnit == 0 ? fttocm(height) : height;
- var weight = weightUnit == 0 ? lbtokg(weight) : weight;
- if (gender == 1) {
- //male
- var met =
- (met * 3.5) /
- (((66.473 + 5.0033 * height + 13.7516 * weight - 6.755 * age) / 1440 / 5 / weight) *
- 1000);
- return met;
- } else if (gender == 0) {
- //female
- var met =
- (met * 3.5) /
- (((655.0955 + 1.8496 * height + 9.5634 * weight - 4.6756 * age) / 1440 / 5 / weight) *
- 1000);
- return met;
- }
- };
- cc.getuserCalories = function (durationInMin, met) {
- return 65 * durationInMin * met;
- };
- cc.getCalories = function (weightInKg, durationInMin, met) {
- return weightInKg * durationInMin * met;
- };
- return cc;
- });
diff --git a/www/js/metrics-mappings.js b/www/js/metrics-mappings.js
deleted file mode 100644
index 38836a3a1..000000000
--- a/www/js/metrics-mappings.js
+++ /dev/null
@@ -1,425 +0,0 @@
-import angular from 'angular';
-import { getLabelOptions } from './survey/multilabel/confirmHelper';
-import { getConfig } from './config/dynamicConfig';
-import { storageGet, storageSet } from './plugin/storage';
-
-angular
- .module('emission.main.metrics.mappings', ['emission.plugin.logger'])
-
- .service('CarbonDatasetHelper', function () {
- var CARBON_DATASET_KEY = 'carbon_dataset_locale';
-
- // Values are in Kg/PKm (kilograms per passenger-kilometer)
- // Sources for EU values:
- // - Tremod: 2017, CO2, CH4 and N2O in CO2-equivalent
- // - HBEFA: 2020, CO2 (per country)
- // German data uses Tremod. Other EU countries (and Switzerland) use HBEFA for car and bus,
- // and Tremod for train and air (because HBEFA doesn't provide these).
- // EU data is an average of the Tremod/HBEFA data for the countries listed;
- // for this average the HBEFA data was used also in the German set (for car and bus).
- var carbonDatasets = {
- US: {
- regionName: 'United States',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 267 / 1609,
- BUS: 278 / 1609,
- LIGHT_RAIL: 120 / 1609,
- SUBWAY: 74 / 1609,
- TRAM: 90 / 1609,
- TRAIN: 92 / 1609,
- AIR_OR_HSR: 217 / 1609,
- },
- },
- EU: {
- // Plain average of values for the countries below (using HBEFA for car and bus, Tremod for others)
- regionName: 'European Union',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.14515,
- BUS: 0.04751,
- LIGHT_RAIL: 0.064,
- SUBWAY: 0.064,
- TRAM: 0.064,
- TRAIN: 0.048,
- AIR_OR_HSR: 0.201,
- },
- },
- DE: {
- regionName: 'Germany',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.139, // Tremod (passenger car)
- BUS: 0.0535, // Tremod (average city/coach)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- FR: {
- regionName: 'France',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.13125, // HBEFA (passenger car, considering 1 passenger)
- BUS: 0.04838, // HBEFA (average short/long distance, considering 16/25 passengers)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- AT: {
- regionName: 'Austria',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.14351, // HBEFA (passenger car, considering 1 passenger)
- BUS: 0.04625, // HBEFA (average short/long distance, considering 16/25 passengers)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- SE: {
- regionName: 'Sweden',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.13458, // HBEFA (passenger car, considering 1 passenger)
- BUS: 0.04557, // HBEFA (average short/long distance, considering 16/25 passengers)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- NO: {
- regionName: 'Norway',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.13265, // HBEFA (passenger car, considering 1 passenger)
- BUS: 0.04185, // HBEFA (average short/long distance, considering 16/25 passengers)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- CH: {
- regionName: 'Switzerland',
- footprintData: {
- WALKING: 0,
- BICYCLING: 0,
- CAR: 0.17638, // HBEFA (passenger car, considering 1 passenger)
- BUS: 0.04866, // HBEFA (average short/long distance, considering 16/25 passengers)
- LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway)
- SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAM: 0.064, // Tremod (DE tram, urban rail and subway)
- TRAIN: 0.048, // Tremod (DE average short/long distance)
- AIR_OR_HSR: 0.201, // Tremod (DE airplane)
- },
- },
- };
-
- var defaultCarbonDatasetCode = 'US';
- var currentCarbonDatasetCode = defaultCarbonDatasetCode;
-
- // we need to call the method from within a promise in initialize()
- // and using this.setCurrentCarbonDatasetLocale doesn't seem to work
- var setCurrentCarbonDatasetLocale = function (localeCode) {
- for (var code in carbonDatasets) {
- if (code == localeCode) {
- currentCarbonDatasetCode = localeCode;
- break;
- }
- }
- };
-
- this.loadCarbonDatasetLocale = function () {
- return storageGet(CARBON_DATASET_KEY).then(function (localeCode) {
- Logger.log(
- 'CarbonDatasetHelper.loadCarbonDatasetLocale() obtained value from storage [' +
- localeCode +
- ']',
- );
- if (!localeCode) {
- localeCode = defaultCarbonDatasetCode;
- Logger.log(
- 'CarbonDatasetHelper.loadCarbonDatasetLocale() no value in storage, using [' +
- localeCode +
- '] instead',
- );
- }
- setCurrentCarbonDatasetLocale(localeCode);
- });
- };
-
- this.saveCurrentCarbonDatasetLocale = function (localeCode) {
- setCurrentCarbonDatasetLocale(localeCode);
- storageSet(CARBON_DATASET_KEY, currentCarbonDatasetCode);
- Logger.log(
- 'CarbonDatasetHelper.saveCurrentCarbonDatasetLocale() saved value [' +
- currentCarbonDatasetCode +
- '] to storage',
- );
- };
-
- this.getCarbonDatasetOptions = function () {
- var options = [];
- for (var code in carbonDatasets) {
- options.push({
- text: code, //carbonDatasets[code].regionName,
- value: code,
- });
- }
- return options;
- };
-
- this.getCurrentCarbonDatasetCode = function () {
- return currentCarbonDatasetCode;
- };
-
- this.getCurrentCarbonDatasetFootprint = function () {
- return carbonDatasets[currentCarbonDatasetCode].footprintData;
- };
- })
- .service('METDatasetHelper', function () {
- var standardMETs = {
- WALKING: {
- VERY_SLOW: {
- range: [0, 2.0],
- mets: 2.0,
- },
- SLOW: {
- range: [2.0, 2.5],
- mets: 2.8,
- },
- MODERATE_0: {
- range: [2.5, 2.8],
- mets: 3.0,
- },
- MODERATE_1: {
- range: [2.8, 3.2],
- mets: 3.5,
- },
- FAST: {
- range: [3.2, 3.5],
- mets: 4.3,
- },
- VERY_FAST_0: {
- range: [3.5, 4.0],
- mets: 5.0,
- },
- 'VERY_FAST_!': {
- range: [4.0, 4.5],
- mets: 6.0,
- },
- VERY_VERY_FAST: {
- range: [4.5, 5],
- mets: 7.0,
- },
- SUPER_FAST: {
- range: [5, 6],
- mets: 8.3,
- },
- RUNNING: {
- range: [6, Number.MAX_VALUE],
- mets: 9.8,
- },
- },
- BICYCLING: {
- VERY_VERY_SLOW: {
- range: [0, 5.5],
- mets: 3.5,
- },
- VERY_SLOW: {
- range: [5.5, 10],
- mets: 5.8,
- },
- SLOW: {
- range: [10, 12],
- mets: 6.8,
- },
- MODERATE: {
- range: [12, 14],
- mets: 8.0,
- },
- FAST: {
- range: [14, 16],
- mets: 10.0,
- },
- VERT_FAST: {
- range: [16, 19],
- mets: 12.0,
- },
- RACING: {
- range: [20, Number.MAX_VALUE],
- mets: 15.8,
- },
- },
- UNKNOWN: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- IN_VEHICLE: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- CAR: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- BUS: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- LIGHT_RAIL: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- TRAIN: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- TRAM: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- SUBWAY: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- AIR_OR_HSR: {
- ALL: {
- range: [0, Number.MAX_VALUE],
- mets: 0,
- },
- },
- };
- this.getStandardMETs = function () {
- return standardMETs;
- };
- })
- .factory('CustomDatasetHelper', function (METDatasetHelper, Logger, $ionicPlatform) {
- var cdh = {};
-
- cdh.getCustomMETs = function () {
- console.log('Getting custom METs', cdh.customMETs);
- return cdh.customMETs;
- };
-
- cdh.getCustomFootprint = function () {
- console.log('Getting custom footprint', cdh.customPerKmFootprint);
- return cdh.customPerKmFootprint;
- };
-
- cdh.populateCustomMETs = function () {
- let standardMETs = METDatasetHelper.getStandardMETs();
- let modeOptions = cdh.inputParams['MODE'];
- let modeMETEntries = modeOptions.map((opt) => {
- if (opt.met_equivalent) {
- let currMET = standardMETs[opt.met_equivalent];
- return [opt.value, currMET];
- } else {
- if (opt.met) {
- let currMET = opt.met;
- // if the user specifies a custom MET, they can't specify
- // Number.MAX_VALUE since it is not valid JSON
- // we assume that they specify -1 instead, and we will
- // map -1 to Number.MAX_VALUE here by iterating over all the ranges
- for (const rangeName in currMET) {
- // console.log("Handling range ", rangeName);
- currMET[rangeName].range = currMET[rangeName].range.map((i) =>
- i == -1 ? Number.MAX_VALUE : i,
- );
- }
- return [opt.value, currMET];
- } else {
- console.warn(
- 'Did not find either met_equivalent or met for ' + opt.value + ' ignoring entry',
- );
- return undefined;
- }
- }
- });
- cdh.customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e)));
- console.log('After populating, custom METs = ', cdh.customMETs);
- };
-
- cdh.populateCustomFootprints = function () {
- let modeOptions = cdh.inputParams['MODE'];
- let modeCO2PerKm = modeOptions
- .map((opt) => {
- if (opt.range_limit_km) {
- if (cdh.range_limited_motorized) {
- Logger.displayError('Found two range limited motorized options', {
- first: cdh.range_limited_motorized,
- second: opt,
- });
- }
- cdh.range_limited_motorized = opt;
- console.log('Found range limited motorized mode', cdh.range_limited_motorized);
- }
- if (angular.isDefined(opt.kgCo2PerKm)) {
- return [opt.value, opt.kgCo2PerKm];
- } else {
- return undefined;
- }
- })
- .filter((modeCO2) => angular.isDefined(modeCO2));
- cdh.customPerKmFootprint = Object.fromEntries(modeCO2PerKm);
- console.log('After populating, custom perKmFootprint', cdh.customPerKmFootprint);
- };
-
- cdh.init = function (newConfig) {
- try {
- getLabelOptions(newConfig).then((inputParams) => {
- console.log('Input params = ', inputParams);
- cdh.inputParams = inputParams;
- cdh.populateCustomMETs();
- cdh.populateCustomFootprints();
- });
- } catch (e) {
- setTimeout(() => {
- Logger.displayError(
- 'Error in metrics-mappings while initializing custom dataset helper',
- e,
- );
- }, 1000);
- }
- };
-
- $ionicPlatform.ready().then(function () {
- getConfig().then((newConfig) => cdh.init(newConfig));
- });
-
- return cdh;
- });
diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx
index 7c9bf3891..835f20a22 100644
--- a/www/js/metrics/CarbonFootprintCard.tsx
+++ b/www/js/metrics/CarbonFootprintCard.tsx
@@ -3,6 +3,11 @@ import { View } from 'react-native';
import { Card, Text, useTheme } from 'react-native-paper';
import { MetricsData } from './metricsTypes';
import { cardStyles } from './MetricsTab';
+import {
+ getFootprintForMetrics,
+ getHighestFootprint,
+ getHighestFootprintForDistance,
+} from './footprintHelper';
import {
formatDateRangeOfDays,
parseDataFromMetrics,
@@ -13,13 +18,11 @@ import {
} from './metricsHelper';
import { useTranslation } from 'react-i18next';
import BarChart from '../components/BarChart';
-import { getAngularService } from '../angular-react-helper';
import ChangeIndicator from './ChangeIndicator';
import color from 'color';
type Props = { userMetrics: MetricsData; aggMetrics: MetricsData };
const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => {
- const FootprintHelper = getAngularService('FootprintHelper');
const { colors } = useTheme();
const { t } = useTranslation();
@@ -49,20 +52,12 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => {
//setting up data to be displayed
let graphRecords = [];
- //set custon dataset, if the labels are custom
- if (isCustomLabels(userThisWeekModeMap)) {
- FootprintHelper.setUseCustomFootprint();
- }
-
//calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago)
let userPrevWeek;
if (userLastWeekSummaryMap[0]) {
userPrevWeek = {
- low: FootprintHelper.getFootprintForMetrics(userLastWeekSummaryMap, 0),
- high: FootprintHelper.getFootprintForMetrics(
- userLastWeekSummaryMap,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(userLastWeekSummaryMap, 0),
+ high: getFootprintForMetrics(userLastWeekSummaryMap, getHighestFootprint()),
};
graphRecords.push({
label: t('main-metrics.unlabeled'),
@@ -78,11 +73,8 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => {
//calculate low-high and format range for past week (7 days ago -> yesterday)
let userPastWeek = {
- low: FootprintHelper.getFootprintForMetrics(userThisWeekSummaryMap, 0),
- high: FootprintHelper.getFootprintForMetrics(
- userThisWeekSummaryMap,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(userThisWeekSummaryMap, 0),
+ high: getFootprintForMetrics(userThisWeekSummaryMap, getHighestFootprint()),
};
graphRecords.push({
label: t('main-metrics.unlabeled'),
@@ -100,7 +92,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => {
}
//calculate worst-case carbon footprint
- let worstCarbon = FootprintHelper.getHighestFootprintForDistance(worstDistance);
+ let worstCarbon = getHighestFootprintForDistance(worstDistance);
graphRecords.push({
label: t('main-metrics.labeled'),
x: worstCarbon,
@@ -138,11 +130,8 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => {
let groupRecords = [];
let aggCarbon = {
- low: FootprintHelper.getFootprintForMetrics(aggCarbonData, 0),
- high: FootprintHelper.getFootprintForMetrics(
- aggCarbonData,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(aggCarbonData, 0),
+ high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()),
};
console.log('testing group past week', aggCarbon);
groupRecords.push({
diff --git a/www/js/metrics/CarbonTextCard.tsx b/www/js/metrics/CarbonTextCard.tsx
index 9f1b4490f..bf40c4a61 100644
--- a/www/js/metrics/CarbonTextCard.tsx
+++ b/www/js/metrics/CarbonTextCard.tsx
@@ -4,6 +4,11 @@ import { Card, Text, useTheme } from 'react-native-paper';
import { MetricsData } from './metricsTypes';
import { cardStyles } from './MetricsTab';
import { useTranslation } from 'react-i18next';
+import {
+ getFootprintForMetrics,
+ getHighestFootprint,
+ getHighestFootprintForDistance,
+} from './footprintHelper';
import {
formatDateRangeOfDays,
parseDataFromMetrics,
@@ -17,7 +22,6 @@ type Props = { userMetrics: MetricsData; aggMetrics: MetricsData };
const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => {
const { colors } = useTheme();
const { t } = useTranslation();
- const FootprintHelper = getAngularService('FootprintHelper');
const userText = useMemo(() => {
if (userMetrics?.distance?.length > 0) {
@@ -46,11 +50,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => {
//calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago)
if (userLastWeekSummaryMap[0]) {
let userPrevWeek = {
- low: FootprintHelper.getFootprintForMetrics(userLastWeekSummaryMap, 0),
- high: FootprintHelper.getFootprintForMetrics(
- userLastWeekSummaryMap,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(userLastWeekSummaryMap, 0),
+ high: getFootprintForMetrics(userLastWeekSummaryMap, getHighestFootprint()),
};
const label = `${t('main-metrics.prev-week')} (${formatDateRangeOfDays(lastWeekDistance)})`;
if (userPrevWeek.low == userPrevWeek.high)
@@ -64,11 +65,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => {
//calculate low-high and format range for past week (7 days ago -> yesterday)
let userPastWeek = {
- low: FootprintHelper.getFootprintForMetrics(userThisWeekSummaryMap, 0),
- high: FootprintHelper.getFootprintForMetrics(
- userThisWeekSummaryMap,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(userThisWeekSummaryMap, 0),
+ high: getFootprintForMetrics(userThisWeekSummaryMap, getHighestFootprint()),
};
const label = `${t('main-metrics.past-week')} (${formatDateRangeOfDays(thisWeekDistance)})`;
if (userPastWeek.low == userPastWeek.high)
@@ -80,7 +78,7 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => {
});
//calculate worst-case carbon footprint
- let worstCarbon = FootprintHelper.getHighestFootprintForDistance(worstDistance);
+ let worstCarbon = getHighestFootprintForDistance(worstDistance);
textList.push({ label: t('main-metrics.worst-case'), value: Math.round(worstCarbon) });
return textList;
@@ -113,11 +111,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => {
let groupText = [];
let aggCarbon = {
- low: FootprintHelper.getFootprintForMetrics(aggCarbonData, 0),
- high: FootprintHelper.getFootprintForMetrics(
- aggCarbonData,
- FootprintHelper.getHighestFootprint(),
- ),
+ low: getFootprintForMetrics(aggCarbonData, 0),
+ high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()),
};
console.log('testing group past week', aggCarbon);
const label = t('main-metrics.average');
diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx
index bdc426e62..3c28a4df0 100644
--- a/www/js/metrics/MetricsTab.tsx
+++ b/www/js/metrics/MetricsTab.tsx
@@ -1,5 +1,4 @@
import React, { useEffect, useState, useMemo } from 'react';
-import { getAngularService } from '../angular-react-helper';
import { View, ScrollView, useWindowDimensions } from 'react-native';
import { Appbar } from 'react-native-paper';
import NavBarButton from '../components/NavBarButton';
@@ -18,10 +17,16 @@ import CarbonTextCard from './CarbonTextCard';
import ActiveMinutesTableCard from './ActiveMinutesTableCard';
import { getAggregateData, getMetrics } from '../services/commHelper';
import { displayError, logDebug, logWarn } from '../plugin/logger';
+import useAppConfig from '../useAppConfig';
+import { ServerConnConfig } from '../types/appConfigTypes';
export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const;
-async function fetchMetricsFromServer(type: 'user' | 'aggregate', dateRange: DateTime[]) {
+async function fetchMetricsFromServer(
+ type: 'user' | 'aggregate',
+ dateRange: DateTime[],
+ serverConnConfig: ServerConnConfig,
+) {
const query = {
freq: 'D',
start_time: dateRange[0].toSeconds(),
@@ -30,7 +35,7 @@ async function fetchMetricsFromServer(type: 'user' | 'aggregate', dateRange: Dat
is_return_aggregate: type == 'aggregate',
};
if (type == 'user') return getMetrics('timestamp', query);
- return getAggregateData('result/metrics/timestamp', query);
+ return getAggregateData('result/metrics/timestamp', query, serverConnConfig);
}
function getLastTwoWeeksDtRange() {
@@ -41,6 +46,7 @@ function getLastTwoWeeksDtRange() {
}
const MetricsTab = () => {
+ const appConfig = useAppConfig();
const { t } = useTranslation();
const { getFormattedSpeed, speedSuffix, getFormattedDistance, distanceSuffix } =
useImperialConfig();
@@ -50,15 +56,16 @@ const MetricsTab = () => {
const [userMetrics, setUserMetrics] = useState(null);
useEffect(() => {
+ if (!appConfig?.server) return;
loadMetricsForPopulation('user', dateRange);
loadMetricsForPopulation('aggregate', dateRange);
- }, [dateRange]);
+ }, [dateRange, appConfig?.server]);
async function loadMetricsForPopulation(population: 'user' | 'aggregate', dateRange: DateTime[]) {
try {
logDebug(`MetricsTab: fetching metrics for population ${population}'
in date range ${JSON.stringify(dateRange)}`);
- const serverResponse = await fetchMetricsFromServer(population, dateRange);
+ const serverResponse = await fetchMetricsFromServer(population, dateRange, appConfig.server);
logDebug('MetricsTab: received metrics: ' + JSON.stringify(serverResponse));
const metrics = {};
const dataKey = population == 'user' ? 'user_metrics' : 'aggregate_metrics';
diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts
new file mode 100644
index 000000000..317113327
--- /dev/null
+++ b/www/js/metrics/customMetricsHelper.ts
@@ -0,0 +1,112 @@
+import angular from 'angular';
+import { getLabelOptions } from '../survey/multilabel/confirmHelper';
+import { displayError, displayErrorMsg, logDebug, logWarn } from '../plugin/logger';
+import { standardMETs } from './metDataset';
+import { AppConfig } from '../types/appConfigTypes';
+
+//variables to store values locally
+let _customMETs;
+let _customPerKmFootprint;
+let _range_limited_motorized;
+let _labelOptions;
+
+/**
+ * @function gets custom mets, must be initialized
+ * @returns the custom mets stored locally
+ */
+export function getCustomMETs() {
+ logDebug('Getting custom METs ' + JSON.stringify(_customMETs));
+ return _customMETs;
+}
+
+/**
+ * @function gets the custom footprint, must be initialized
+ * @returns custom footprint
+ */
+export function getCustomFootprint() {
+ logDebug('Getting custom footprint ' + JSON.stringify(_customPerKmFootprint));
+ return _customPerKmFootprint;
+}
+
+/**
+ * @function stores custom mets in local var
+ * needs _labelOptions, stored after gotten from config
+ */
+function populateCustomMETs() {
+ let modeOptions = _labelOptions['MODE'];
+ let modeMETEntries = modeOptions.map((opt) => {
+ if (opt.met_equivalent) {
+ let currMET = standardMETs[opt.met_equivalent];
+ return [opt.value, currMET];
+ } else {
+ if (opt.met) {
+ let currMET = opt.met;
+ // if the user specifies a custom MET, they can't specify
+ // Number.MAX_VALUE since it is not valid JSON
+ // we assume that they specify -1 instead, and we will
+ // map -1 to Number.MAX_VALUE here by iterating over all the ranges
+ for (const rangeName in currMET) {
+ // console.log("Handling range ", rangeName);
+ currMET[rangeName].range = currMET[rangeName].range.map((i) =>
+ i == -1 ? Number.MAX_VALUE : i,
+ );
+ }
+ return [opt.value, currMET];
+ } else {
+ logWarn(`Did not find either met_equivalent or met for ${opt.value} ignoring entry`);
+ return undefined;
+ }
+ }
+ });
+ _customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e)));
+ logDebug('After populating, custom METs = ' + JSON.stringify(_customMETs));
+}
+
+/**
+ * @function stores custom footprint in local var
+ * needs _inputParams which is stored after gotten from config
+ */
+function populateCustomFootprints() {
+ let modeOptions = _labelOptions['MODE'];
+ let modeCO2PerKm = modeOptions
+ .map((opt) => {
+ if (opt.range_limit_km) {
+ if (_range_limited_motorized) {
+ displayErrorMsg(
+ JSON.stringify({ first: _range_limited_motorized, second: opt }),
+ 'Found two range limited motorized options',
+ );
+ }
+ _range_limited_motorized = opt;
+ logDebug(`Found range limited motorized mode - ${_range_limited_motorized}`);
+ }
+ if (angular.isDefined(opt.kgCo2PerKm)) {
+ return [opt.value, opt.kgCo2PerKm];
+ } else {
+ return undefined;
+ }
+ })
+ .filter((modeCO2) => angular.isDefined(modeCO2));
+ _customPerKmFootprint = Object.fromEntries(modeCO2PerKm);
+ logDebug('After populating, custom perKmFootprint' + JSON.stringify(_customPerKmFootprint));
+}
+
+/**
+ * @function initializes the datasets based on configured label options
+ * calls popuplateCustomMETs and populateCustomFootprint
+ * @param newConfig the app config file
+ */
+export async function initCustomDatasetHelper(newConfig: AppConfig) {
+ try {
+ logDebug('initializing custom datasets with config' + newConfig);
+ const labelOptions = await getLabelOptions(newConfig);
+ logDebug('In custom metrics, label options = ' + JSON.stringify(labelOptions));
+ _labelOptions = labelOptions;
+ populateCustomMETs();
+ populateCustomFootprints();
+ } catch (e) {
+ setTimeout(() => {
+ displayError(e, 'Error while initializing custom dataset helper');
+ }, 1000);
+ }
+}
diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts
new file mode 100644
index 000000000..24677feaf
--- /dev/null
+++ b/www/js/metrics/footprintHelper.ts
@@ -0,0 +1,99 @@
+import { displayErrorMsg, logDebug, logWarn } from '../plugin/logger';
+import { getCustomFootprint } from './customMetricsHelper';
+
+//variables for the highest footprint in the set and if using custom
+let highestFootprint = 0;
+
+/**
+ * @function converts meters to kilometers
+ * @param {number} v value in meters to be converted
+ * @returns {number} converted value in km
+ */
+const mtokm = (v) => v / 1000;
+
+/**
+ * @function clears the stored highest footprint
+ */
+export function clearHighestFootprint() {
+ //need to clear for testing
+ highestFootprint = undefined;
+}
+
+/**
+ * @function gets the footprint
+ * currently will only be custom, as all labels are "custom"
+ * fallback is json/label-options.json.sample, with MET and kgCO2 defined
+ * @returns the footprint or undefined
+ */
+function getFootprint() {
+ let footprint = getCustomFootprint();
+ if (footprint) {
+ return footprint;
+ } else {
+ displayErrorMsg('failed to use custom labels', 'Error in Footprint Calculatons');
+ return undefined;
+ }
+}
+
+/**
+ * @function calculates footprint for given metrics
+ * @param {Array} userMetrics string mode + number distance in meters pairs
+ * ex: const custom_metrics = [ { key: 'walk', values: 3000 }, { key: 'bike', values: 6500 }, ];
+ * @param {number} defaultIfMissing optional, carbon intensity if mode not in footprint
+ * @returns {number} the sum of carbon emissions for userMetrics given
+ */
+export function getFootprintForMetrics(userMetrics, defaultIfMissing = 0) {
+ const footprint = getFootprint();
+ logDebug('getting footprint for ' + userMetrics + ' with ' + footprint);
+ let result = 0;
+ for (let i in userMetrics) {
+ let mode = userMetrics[i].key;
+ if (mode == 'ON_FOOT') {
+ mode = 'WALKING';
+ }
+
+ if (mode in footprint) {
+ result += footprint[mode] * mtokm(userMetrics[i].values);
+ } else if (mode == 'IN_VEHICLE') {
+ const sum =
+ footprint['CAR'] +
+ footprint['BUS'] +
+ footprint['LIGHT_RAIL'] +
+ footprint['TRAIN'] +
+ footprint['TRAM'] +
+ footprint['SUBWAY'];
+ result += (sum / 6) * mtokm(userMetrics[i].values);
+ } else {
+ logWarn(
+ `WARNING getFootprintFromMetrics() was requested for an unknown mode: ${mode} metrics JSON: ${JSON.stringify(
+ userMetrics,
+ )}`,
+ );
+ result += defaultIfMissing * mtokm(userMetrics[i].values);
+ }
+ }
+ return result;
+}
+
+/**
+ * @function gets highest co2 intensity in the footprint
+ * @returns {number} the highest co2 intensity in the footprint
+ */
+export function getHighestFootprint() {
+ if (!highestFootprint) {
+ const footprint = getFootprint();
+ let footprintList = [];
+ for (let mode in footprint) {
+ footprintList.push(footprint[mode]);
+ }
+ highestFootprint = Math.max(...footprintList);
+ }
+ return highestFootprint;
+}
+
+/**
+ * @function gets highest theoretical footprint for given distance
+ * @param {number} distance in meters to calculate max footprint
+ * @returns max footprint for given distance
+ */
+export const getHighestFootprintForDistance = (distance) => getHighestFootprint() * mtokm(distance);
diff --git a/www/js/metrics/metDataset.ts b/www/js/metrics/metDataset.ts
new file mode 100644
index 000000000..901c17ae6
--- /dev/null
+++ b/www/js/metrics/metDataset.ts
@@ -0,0 +1,128 @@
+export const standardMETs = {
+ WALKING: {
+ VERY_SLOW: {
+ range: [0, 2.0],
+ mets: 2.0,
+ },
+ SLOW: {
+ range: [2.0, 2.5],
+ mets: 2.8,
+ },
+ MODERATE_0: {
+ range: [2.5, 2.8],
+ mets: 3.0,
+ },
+ MODERATE_1: {
+ range: [2.8, 3.2],
+ mets: 3.5,
+ },
+ FAST: {
+ range: [3.2, 3.5],
+ mets: 4.3,
+ },
+ VERY_FAST_0: {
+ range: [3.5, 4.0],
+ mets: 5.0,
+ },
+ 'VERY_FAST_!': {
+ range: [4.0, 4.5],
+ mets: 6.0,
+ },
+ VERY_VERY_FAST: {
+ range: [4.5, 5],
+ mets: 7.0,
+ },
+ SUPER_FAST: {
+ range: [5, 6],
+ mets: 8.3,
+ },
+ RUNNING: {
+ range: [6, Number.MAX_VALUE],
+ mets: 9.8,
+ },
+ },
+ BICYCLING: {
+ VERY_VERY_SLOW: {
+ range: [0, 5.5],
+ mets: 3.5,
+ },
+ VERY_SLOW: {
+ range: [5.5, 10],
+ mets: 5.8,
+ },
+ SLOW: {
+ range: [10, 12],
+ mets: 6.8,
+ },
+ MODERATE: {
+ range: [12, 14],
+ mets: 8.0,
+ },
+ FAST: {
+ range: [14, 16],
+ mets: 10.0,
+ },
+ VERT_FAST: {
+ range: [16, 19],
+ mets: 12.0,
+ },
+ RACING: {
+ range: [20, Number.MAX_VALUE],
+ mets: 15.8,
+ },
+ },
+ UNKNOWN: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ IN_VEHICLE: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ CAR: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ BUS: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ LIGHT_RAIL: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ TRAIN: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ TRAM: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ SUBWAY: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+ AIR_OR_HSR: {
+ ALL: {
+ range: [0, Number.MAX_VALUE],
+ mets: 0,
+ },
+ },
+};
diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts
new file mode 100644
index 000000000..25bcc2e7e
--- /dev/null
+++ b/www/js/metrics/metHelper.ts
@@ -0,0 +1,59 @@
+import { logDebug, logWarn } from '../plugin/logger';
+import { getCustomMETs } from './customMetricsHelper';
+import { standardMETs } from './metDataset';
+
+/**
+ * @function gets the METs object
+ * @returns {object} mets either custom or standard
+ */
+function getMETs() {
+ let custom_mets = getCustomMETs();
+ if (custom_mets) {
+ return custom_mets;
+ } else {
+ return standardMETs;
+ }
+}
+
+/**
+ * @function checks number agains bounds
+ * @param num the number to check
+ * @param min lower bound
+ * @param max upper bound
+ * @returns {boolean} if number is within given bounds
+ */
+const between = (num, min, max) => num >= min && num <= max;
+
+/**
+ * @function converts meters per second to miles per hour
+ * @param mps meters per second speed
+ * @returns speed in miles per hour
+ */
+const mpstomph = (mps) => 2.23694 * mps;
+
+/**
+ * @function gets met for a given mode and speed
+ * @param {string} mode of travel
+ * @param {number} speed of travel in meters per second
+ * @param {number} defaultIfMissing default MET if mode not in METs
+ * @returns
+ */
+export function getMet(mode, speed, defaultIfMissing) {
+ if (mode == 'ON_FOOT') {
+ logDebug("getMet() converted 'ON_FOOT' to 'WALKING'");
+ mode = 'WALKING';
+ }
+ let currentMETs = getMETs();
+ if (!currentMETs[mode]) {
+ logWarn('getMet() Illegal mode: ' + mode);
+ return defaultIfMissing; //So the calorie sum does not break with wrong return type
+ }
+ for (let i in currentMETs[mode]) {
+ if (between(mpstomph(speed), currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) {
+ return currentMETs[mode][i].mets;
+ } else if (mpstomph(speed) < 0) {
+ logWarn('getMet() Negative speed: ' + mpstomph(speed));
+ return 0;
+ }
+ }
+}
diff --git a/www/js/services/commHelper.ts b/www/js/services/commHelper.ts
index 6dc71160a..206fc77c1 100644
--- a/www/js/services/commHelper.ts
+++ b/www/js/services/commHelper.ts
@@ -1,5 +1,6 @@
import { DateTime } from 'luxon';
import { logDebug } from '../plugin/logger';
+import { ServerConnConfig } from '../types/appConfigTypes';
/**
* @param url URL endpoint for the request
@@ -129,20 +130,17 @@ export function getMetrics(timeType: 'timestamp' | 'local_date', metricsQuery) {
});
}
-export function getAggregateData(path: string, data: any) {
+export function getAggregateData(path: string, query, serverConnConfig: ServerConnConfig) {
return new Promise((rs, rj) => {
- const fullUrl = `${window['$rootScope'].connectUrl}/${path}`;
- data['aggregate'] = true;
+ const fullUrl = `${serverConnConfig.connectUrl}/${path}`;
+ query['aggregate'] = true;
- if (window['$rootScope'].aggregateAuth === 'no_auth') {
- logDebug(
- `getting aggregate data without user authentication from ${fullUrl} with arguments ${JSON.stringify(
- data,
- )}`,
- );
+ if (serverConnConfig.aggregate_call_auth == 'no_auth') {
+ logDebug(`getting aggregate data without user authentication from ${fullUrl}
+ with arguments ${JSON.stringify(query)}`);
const options = {
method: 'post',
- data: data,
+ data: query,
responseType: 'json',
};
window['cordova'].plugin.http.sendRequest(
@@ -156,14 +154,9 @@ export function getAggregateData(path: string, data: any) {
},
);
} else {
- logDebug(
- `getting aggregate data with user authentication from ${fullUrl} with arguments ${JSON.stringify(
- data,
- )}`,
- );
- const msgFiller = (message) => {
- return Object.assign(message, data);
- };
+ logDebug(`getting aggregate data with user authentication from ${fullUrl}
+ with arguments ${JSON.stringify(query)}`);
+ const msgFiller = (message) => Object.assign(message, query);
window['cordova'].plugins.BEMServerComm.pushGetJSON(`/${path}`, msgFiller, rs, rj);
}
}).catch((error) => {
diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts
index 51674b0c3..f032f2f5a 100644
--- a/www/js/survey/multilabel/confirmHelper.ts
+++ b/www/js/survey/multilabel/confirmHelper.ts
@@ -37,7 +37,6 @@ export let inputDetails: InputDetails;
export async function getLabelOptions(appConfigParam?) {
if (appConfigParam) appConfig = appConfigParam;
if (labelOptions) return labelOptions;
-
if (appConfig.label_options) {
const labelOptionsJson = await fetchUrlCached(appConfig.label_options);
logDebug(