diff --git a/www/js/diary/timelineHelper.ts b/www/js/diary/timelineHelper.ts index f140f1750..6c87cd0b9 100644 --- a/www/js/diary/timelineHelper.ts +++ b/www/js/diary/timelineHelper.ts @@ -14,6 +14,8 @@ import { TimestampRange, CompositeTrip, UnprocessedTrip, + SectionData, + CompositeTripLocation, } from '../types/diaryTypes'; import { getLabelInputDetails, getLabelInputs } from '../survey/multilabel/confirmHelper'; import { LabelOptions } from '../types/labelTypes'; @@ -215,10 +217,10 @@ const location2GeojsonPoint = (locationPoint: Point, featureType: string): Featu */ function locations2GeojsonTrajectory( trip: CompositeTrip, - locationList: Array, + locationList: CompositeTripLocation[], trajectoryColor?: string, -) { - let sectionsPoints; +): Feature[] { + let sectionsPoints: CompositeTripLocation[][]; if (!trip.sections) { // this is a unimodal trip so we put all the locations in one section sectionsPoints = [locationList]; @@ -242,6 +244,9 @@ function locations2GeojsonTrajectory( color for the sensed mode of this section, and fall back to dark grey */ color: trajectoryColor || getBaseModeByKey(section?.sensed_mode_str)?.color || '#333', }, + properties: { + feature_type: 'section_trajectory', + }, }; }); } @@ -288,7 +293,10 @@ const dateTime2localdate = (currtime: DateTime, tz: string) => ({ second: currtime.second, }); -function points2TripProps(locationPoints: Array>) { +/** + * @description Given an array of location points, creates an UnprocessedTrip object. + */ +function points2UnprocessedTrip(locationPoints: Array>): UnprocessedTrip { const startPoint = locationPoints[0]; const endPoint = locationPoints[locationPoints.length - 1]; const tripAndSectionId = `unprocessed_${startPoint.data.ts}_${endPoint.data.ts}`; @@ -318,24 +326,51 @@ function points2TripProps(locationPoints: Array>) { speed: speeds[i], })); - return { - _id: { $oid: tripAndSectionId }, - key: 'UNPROCESSED_trip', - origin_key: 'UNPROCESSED_trip', - additions: [], - confidence_threshold: 0, + // baseProps: these are the properties that are the same between the trip and its section + const baseProps = { distance: dists.reduce((a, b) => a + b, 0), duration: endPoint.data.ts - startPoint.data.ts, end_fmt_time: endTime.toISO() || displayErrorMsg('end_fmt_time: invalid DateTime') || '', + end_loc: { + type: 'Point', + coordinates: [endPoint.data.longitude, endPoint.data.latitude], + } as Point, end_local_dt: dateTime2localdate(endTime, endPoint.metadata.time_zone), end_ts: endPoint.data.ts, - expectation: { to_label: true }, - inferred_labels: [], - locations: locations, source: 'unprocessed', start_fmt_time: startTime.toISO() || displayErrorMsg('start_fmt_time: invalid DateTime') || '', + start_loc: { + type: 'Point', + coordinates: [startPoint.data.longitude, startPoint.data.latitude], + } as Point, start_local_dt: dateTime2localdate(startTime, startPoint.metadata.time_zone), start_ts: startPoint.data.ts, + } as const; + + // section: baseProps + some properties that are unique to the section + const singleSection: SectionData = { + ...baseProps, + _id: { $oid: `unprocessed_section_${tripAndSectionId}` }, + cleaned_section: { $oid: `unprocessed_section_${tripAndSectionId}` }, + key: 'UNPROCESSED_section', + origin_key: 'UNPROCESSED_section', + sensed_mode: 4, // MotionTypes.UNKNOWN (4) + sensed_mode_str: 'UNKNOWN', + trip_id: { $oid: tripAndSectionId }, + }; + + // the complete UnprocessedTrip: baseProps + properties that are unique to the trip, including the section + return { + ...baseProps, + _id: { $oid: tripAndSectionId }, + additions: [], + confidence_threshold: 0, + expectation: { to_label: true }, + inferred_labels: [], + key: 'UNPROCESSED_trip', + locations: locations, + origin_key: 'UNPROCESSED_trip', + sections: [singleSection], user_input: {}, }; } @@ -343,7 +378,11 @@ function points2TripProps(locationPoints: Array>) { const tsEntrySort = (e1: BEMData, e2: BEMData) => e1.data.ts - e2.data.ts; // compare timestamps -function transitionTrip2TripObj(trip: Array): Promise { +/** + * @description Given an array of 2 transitions, queries the location data during that time and promises an UnprocessedTrip object. + * @param trip An array of transitions representing one trip; i.e. [start transition, end transition] + */ +function tripTransitions2UnprocessedTrip(trip: Array): Promise { const tripStartTransition = trip[0]; const tripEndTransition = trip[1]; const tq = { @@ -385,20 +424,7 @@ function transitionTrip2TripObj(trip: Array): Promise) { // Logger.log("Returning false"); return false; } -/* - * This is going to be a bit tricky. As we can see from - * https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286279163, - * when we read local transitions, they have a string for the transition - * (e.g. `T_DATA_PUSHED`), while the remote transitions have an integer - * (e.g. `2`). - * See https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286338606 - * - * Also, at least on iOS, it is possible for trip end to be detected way - * after the end of the trip, so the trip end transition of a processed - * trip may actually show up as an unprocessed transition. - * See https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286279163 - * - * Let's abstract this out into our own minor state machine. + +/** + * @description Given an array of transitions, finds which transitions represent the start and end of a detected trip and returns them as pairs. + * @returns An 2D array of transitions, where each inner array represents one trip; i.e. [start transition, end transition] */ -function transitions2Trips(transitionList: Array>) { +function transitions2TripTransitions(transitionList: Array>) { + /* This is going to be a bit tricky. As we can see from + * https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286279163, + * when we read local transitions, they have a string for the transition + * (e.g. `T_DATA_PUSHED`), while the remote transitions have an integer + * (e.g. `2`). + * See https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286338606 + * + * Also, at least on iOS, it is possible for trip end to be detected way + * after the end of the trip, so the trip end transition of a processed + * trip may actually show up as an unprocessed transition. + * See https://github.com/e-mission/e-mission-phone/issues/214#issuecomment-286279163 + * + * Let's abstract this out into our own minor state machine. + */ let inTrip = false; const tripList: [BEMData, BEMData][] = []; let currStartTransitionIndex = -1; @@ -520,12 +550,12 @@ export function readUnprocessedTrips( return []; } else { logDebug(`Found ${transitionList.length} transitions. yay!`); - const tripsList = transitions2Trips(transitionList); + const tripsList = transitions2TripTransitions(transitionList); logDebug(`Mapped into ${tripsList.length} trips. yay!`); tripsList.forEach((trip) => { logDebug(JSON.stringify(trip, null, 2)); }); - const tripFillPromises = tripsList.map(transitionTrip2TripObj); + const tripFillPromises = tripsList.map(tripTransitions2UnprocessedTrip); return Promise.all(tripFillPromises).then( (rawTripObjs: (UnprocessedTrip | undefined)[]) => { // Now we need to link up the trips. linking unprocessed trips diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts index 75c43b2d6..8b8de469c 100644 --- a/www/js/types/diaryTypes.ts +++ b/www/js/types/diaryTypes.ts @@ -5,7 +5,7 @@ import { BaseModeKey, MotionTypeKey } from '../diary/diaryHelper'; import { MultilabelKey } from './labelTypes'; import { BEMData, LocalDt } from './serverData'; -import { FeatureCollection, Feature, Geometry, Point } from 'geojson'; +import { FeatureCollection, Feature, Geometry, Point, Position } from 'geojson'; type ObjectId = { $oid: string }; @@ -45,14 +45,9 @@ export type TripTransition = { ts: number; }; -export type LocationCoord = { - type: string; // e.x., "Point" - coordinates: [number, number]; -}; - -type CompTripLocations = { +export type CompositeTripLocation = { loc: { - coordinates: number[]; // e.g. [1, 2.3] + coordinates: Position; // [lon, lat] }; speed: number; ts: number; @@ -61,24 +56,27 @@ type CompTripLocations = { // Used for return type of readUnprocessedTrips export type UnprocessedTrip = { _id: ObjectId; - additions: UserInputEntry[]; + additions: []; // unprocessed trips won't have any matched processed inputs, so this is always empty confidence_threshold: number; distance: number; duration: number; end_fmt_time: string; end_loc: Point; end_local_dt: LocalDt; - expectation: any; // TODO "{to_label: boolean}" - inferred_labels: any[]; // TODO - key: string; - locations?: CompTripLocations[]; - origin_key: string; // e.x., UNPROCESSED_trip - source: string; + end_ts: number; + expectation: { to_label: true }; // unprocessed trips are always expected to be labeled + inferred_labels: []; // unprocessed trips won't have inferred labels + key: 'UNPROCESSED_trip'; + locations?: CompositeTripLocation[]; + origin_key: 'UNPROCESSED_trip'; + sections: SectionData[]; + source: 'unprocessed'; + start_fmt_time: string; start_local_dt: LocalDt; start_ts: number; start_loc: Point; starting_trip?: any; - user_input: UserInput; + user_input: {}; // unprocessed trips won't have any matched processed inputs, so this is always empty }; /* These are the properties received from the server (basically matches Python code) @@ -98,16 +96,16 @@ export type CompositeTrip = { end_local_dt: LocalDt; end_place: ObjectId; end_ts: number; - expectation: any; // TODO "{to_label: boolean}" + expectation: { to_label: boolean }; expected_trip: ObjectId; inferred_labels: InferredLabels; inferred_section_summary: SectionSummary; inferred_trip: ObjectId; key: string; - locations: any[]; // TODO + locations: CompositeTripLocation[]; origin_key: string; raw_trip: ObjectId; - sections: any[]; // TODO + sections: SectionData[]; source: string; start_confirmed_place: BEMData; start_fmt_time: string; @@ -188,23 +186,25 @@ export type Location = { latitude: number; fmt_time: string; // ISO mode: number; - loc: LocationCoord; + loc: Point; ts: number; // Unix altitude: number; distance: number; }; -// used in readAllCompositeTrips export type SectionData = { + _id: ObjectId; end_ts: number; // Unix time, e.x. 1696352498.804 - end_loc: LocationCoord; + end_loc: Point; start_fmt_time: string; // ISO time end_fmt_time: string; + key: string; + origin_key: string; trip_id: ObjectId; sensed_mode: number; source: string; // e.x., "SmoothedHighConfidenceMotion" start_ts: number; // Unix - start_loc: LocationCoord; + start_loc: Point; cleaned_section: ObjectId; start_local_dt: LocalDt; end_local_dt: LocalDt; @@ -213,7 +213,7 @@ export type SectionData = { distance: number; }; -// used in timelineHelper's `transitionTrip2TripObj` +// used in timelineHelper's `transitionTrip2UnprocessedTrip` export type FilteredLocation = { accuracy: number; altitude: number;