Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include sections in UnprocessedTrips #1141

Merged
merged 4 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 75 additions & 45 deletions www/js/diary/timelineHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -215,10 +217,10 @@ const location2GeojsonPoint = (locationPoint: Point, featureType: string): Featu
*/
function locations2GeojsonTrajectory(
trip: CompositeTrip,
locationList: Array<Point>,
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];
Expand All @@ -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',
},
};
});
}
Expand Down Expand Up @@ -288,7 +293,10 @@ const dateTime2localdate = (currtime: DateTime, tz: string) => ({
second: currtime.second,
});

function points2TripProps(locationPoints: Array<BEMData<FilteredLocation>>) {
/**
* @description Given an array of location points, creates an UnprocessedTrip object.
*/
function points2UnprocessedTrip(locationPoints: Array<BEMData<FilteredLocation>>): UnprocessedTrip {
const startPoint = locationPoints[0];
const endPoint = locationPoints[locationPoints.length - 1];
const tripAndSectionId = `unprocessed_${startPoint.data.ts}_${endPoint.data.ts}`;
Expand Down Expand Up @@ -318,32 +326,63 @@ function points2TripProps(locationPoints: Array<BEMData<FilteredLocation>>) {
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: {},
};
}

const tsEntrySort = (e1: BEMData<FilteredLocation>, e2: BEMData<FilteredLocation>) =>
e1.data.ts - e2.data.ts; // compare timestamps

function transitionTrip2TripObj(trip: Array<any>): Promise<UnprocessedTrip | undefined> {
/**
* @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<any>): Promise<UnprocessedTrip | undefined> {
const tripStartTransition = trip[0];
const tripEndTransition = trip[1];
const tq = {
Expand Down Expand Up @@ -385,20 +424,7 @@ function transitionTrip2TripObj(trip: Array<any>): Promise<UnprocessedTrip | und
logDebug(`transitions: start = ${JSON.stringify(tripStartTransition.data)};
end = ${JSON.stringify(tripEndTransition.data)}`);
}

const tripProps = points2TripProps(filteredLocationList);

return {
...tripProps,
start_loc: {
type: 'Point',
coordinates: [tripStartPoint.data.longitude, tripStartPoint.data.latitude],
},
end_loc: {
type: 'Point',
coordinates: [tripEndPoint.data.longitude, tripEndPoint.data.latitude],
},
};
return points2UnprocessedTrip(filteredLocationList);
},
);
}
Expand Down Expand Up @@ -427,22 +453,26 @@ function isEndingTransition(transWrapper: BEMData<TripTransition>) {
// 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<BEMData<TripTransition>>) {
function transitions2TripTransitions(transitionList: Array<BEMData<TripTransition>>) {
/* 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<TripTransition>, BEMData<TripTransition>][] = [];
let currStartTransitionIndex = -1;
Expand Down Expand Up @@ -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
Expand Down
48 changes: 24 additions & 24 deletions www/js/types/diaryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Comment on lines +71 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious! Is there any specific reason why key and origin_key are a combination of uppercase and lowercase letters? It seems unfamiliar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is just how unprocessed trips have always been represented.

This isn't a "real" key because it doesn't come from the server. It's a client-side construction of something similar to a composite_trip object, but it wouldn't really be accurate to call it a de facto composite_trip. So it doesn't get the analysis/composite_trip key.

Somewhere else in the codebase, we identify draft trips by seeing if the key includes 'UNPROCESSED'.

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)
Expand All @@ -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<ConfirmedPlace>;
start_fmt_time: string;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Loading