Skip to content

Commit e5eb76f

Browse files
committed
feat: Response Location Toggle
1 parent 052c655 commit e5eb76f

6 files changed

Lines changed: 231 additions & 64 deletions

File tree

src/App.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class App extends React.Component {
150150
activeMenuIndex: null,
151151
initialMapBounds: null,
152152
selectedRowIndexPerDataset: [-1, -1, -1, -1, -1],
153+
useResponseLocationPerDataset: [false, false, false, false, false],
153154
currentLogData: {
154155
...this.props.logData,
155156
taskLogs: new TaskLogs(this.props.logData.tripLogs),
@@ -223,6 +224,16 @@ class App extends React.Component {
223224
this.setState({ featuredObject: featuredObject });
224225
}
225226

227+
setUseResponseLocation = (useResponseLocation) => {
228+
if (this.state.activeDatasetIndex !== null) {
229+
this.setState((prevState) => {
230+
const newValues = [...prevState.useResponseLocationPerDataset];
231+
newValues[prevState.activeDatasetIndex] = useResponseLocation;
232+
return { useResponseLocationPerDataset: newValues };
233+
});
234+
}
235+
};
236+
226237
setFocusOnRowFunction = (func) => {
227238
this.focusOnRowFunction = func;
228239
};
@@ -909,9 +920,9 @@ class App extends React.Component {
909920

910921
setTimeout(() => {
911922
if (savedRowIndex >= 0) {
912-
const minDate = new Date(this.state.timeRange.minTime);
913-
const maxDate = new Date(this.state.timeRange.maxTime);
914-
const logs = tripLogs.getLogs_(minDate, maxDate).value();
923+
const logs = tripLogs
924+
.getLogs_(new Date(this.state.timeRange.minTime), new Date(this.state.timeRange.maxTime))
925+
.value();
915926

916927
if (savedRowIndex < logs.length) {
917928
log(`Restoring row at index ${savedRowIndex}`);
@@ -1014,6 +1025,12 @@ class App extends React.Component {
10141025
focusSelectedRow={this.focusOnSelectedRow}
10151026
initialMapBounds={this.state.initialMapBounds}
10161027
filters={filters}
1028+
useResponseLocation={
1029+
this.state.activeDatasetIndex !== null
1030+
? this.state.useResponseLocationPerDataset[this.state.activeDatasetIndex]
1031+
: false
1032+
}
1033+
setUseResponseLocation={this.setUseResponseLocation}
10171034
/>
10181035
</div>
10191036
<TimeSlider

src/Map.js

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ function MapComponent({
2424
setCenterOnLocation,
2525
setRenderMarkerOnMap,
2626
filters,
27+
useResponseLocation,
28+
setUseResponseLocation,
2729
}) {
2830
const { tripLogs, taskLogs, jwt, projectId, mapId } = logData;
2931

@@ -112,6 +114,7 @@ function MapComponent({
112114
mapRef.current = map;
113115

114116
const tripObjects = new TripObjects({ map, setFeaturedObject, setTimeRange });
117+
mapDivRef.current.tripObjects = tripObjects;
115118

116119
const addTripPolys = () => {
117120
const trips = tripLogs.getTrips();
@@ -163,15 +166,52 @@ function MapComponent({
163166
};
164167
map.controls[window.google.maps.ControlPosition.TOP_LEFT].push(polylineButton);
165168

169+
const bottomControlsWrapper = document.createElement("div");
170+
bottomControlsWrapper.className = "map-controls-bottom-left";
171+
166172
const followButton = document.createElement("div");
167173
followButton.className = "follow-vehicle-button";
168174
followButton.innerHTML = `<div class="follow-vehicle-background"></div><svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 20 20" width="24" height="24" class="follow-vehicle-chevron"><path d="M -10,10 L 0,-10 L 10,10 L 0,5 z" fill="#4285F4" stroke="#4285F4" stroke-width="1"/></svg>`;
169175
followButton.onclick = () => {
170176
log("Follow vehicle button clicked.");
171177
recenterOnVehicleWrapper();
172-
map.setZoom(17);
173178
};
174-
map.controls[window.google.maps.ControlPosition.LEFT_BOTTOM].push(followButton);
179+
180+
const toggleContainer = document.createElement("div");
181+
toggleContainer.className = "map-toggle-container";
182+
183+
const updateToggleStyles = (reqActive) => {
184+
reqBtn.className = reqActive ? "map-toggle-button active" : "map-toggle-button";
185+
resBtn.className = reqActive ? "map-toggle-button" : "map-toggle-button active";
186+
};
187+
188+
const reqBtn = document.createElement("button");
189+
reqBtn.textContent = "Request";
190+
reqBtn.className = "map-toggle-button";
191+
reqBtn.onclick = () => {
192+
setUseResponseLocation(false);
193+
updateToggleStyles(true);
194+
};
195+
196+
const resBtn = document.createElement("button");
197+
resBtn.textContent = "Response";
198+
resBtn.className = "map-toggle-button";
199+
resBtn.onclick = () => {
200+
setUseResponseLocation(true);
201+
updateToggleStyles(false);
202+
};
203+
204+
const separator = document.createElement("div");
205+
separator.className = "map-toggle-separator";
206+
207+
updateToggleStyles(!useResponseLocation);
208+
209+
toggleContainer.appendChild(reqBtn);
210+
toggleContainer.appendChild(resBtn);
211+
212+
bottomControlsWrapper.appendChild(followButton);
213+
bottomControlsWrapper.appendChild(toggleContainer);
214+
map.controls[window.google.maps.ControlPosition.LEFT_BOTTOM].push(bottomControlsWrapper);
175215

176216
const centerListener = map.addListener(
177217
"center_changed",
@@ -245,16 +285,17 @@ function MapComponent({
245285
if (!map) return;
246286
log("recenterOnVehicleWrapper called for follow mode.");
247287

248-
let position = null;
249-
if (selectedRow?.lastlocation?.rawlocation) {
250-
position = selectedRow.lastlocation.rawlocation;
251-
} else if (lastValidPositionRef.current) {
252-
position = lastValidPositionRef.current;
288+
if (!isFollowingVehicle) {
289+
const locationObj = useResponseLocation ? selectedRow?.lastlocationResponse : selectedRow?.lastlocation;
290+
const position = locationObj?.location || locationObj?.rawlocation || lastValidPositionRef.current;
291+
if (position) {
292+
map.setCenter({ lat: position.latitude, lng: position.longitude });
293+
map.setZoom(17);
294+
}
253295
}
254296

255-
if (position) map.setCenter({ lat: position.latitude, lng: position.longitude });
256297
setIsFollowingVehicle((prev) => !prev);
257-
}, [selectedRow]);
298+
}, [selectedRow, useResponseLocation, isFollowingVehicle]);
258299

259300
useEffect(() => {
260301
const followButton = document.querySelector(".follow-vehicle-button");
@@ -338,16 +379,13 @@ function MapComponent({
338379
return;
339380
}
340381

341-
const location =
342-
_.get(selectedRow.lastlocation, "location") ||
343-
_.get(selectedRow.lastlocation, "rawlocation") ||
344-
_.get(selectedRow.lastlocationResponse, "location");
382+
const locationObj = useResponseLocation ? selectedRow.lastlocationResponse : selectedRow.lastlocation;
383+
const location = _.get(locationObj, "location") || _.get(locationObj, "rawlocation");
345384

346385
if (location?.latitude && location?.longitude) {
347386
const pos = { lat: location.latitude, lng: location.longitude };
348387
lastValidPositionRef.current = pos;
349-
const heading =
350-
_.get(selectedRow.lastlocation, "heading") || _.get(selectedRow.lastlocationResponse, "heading") || 0;
388+
const heading = _.get(locationObj, "heading") || 0;
351389

352390
if (vehicleMarkersRef.current.background) {
353391
vehicleMarkersRef.current.background.setPosition(pos);
@@ -396,7 +434,7 @@ function MapComponent({
396434
});
397435
}
398436

399-
const rawLocation = _.get(selectedRow.lastlocation, "rawlocation");
437+
const rawLocation = _.get(locationObj, "rawlocation");
400438
if (rawLocation?.latitude && rawLocation?.longitude) {
401439
const rawPos = { lat: rawLocation.latitude, lng: rawLocation.longitude };
402440
if (vehicleMarkersRef.current.rawLocation) {
@@ -429,7 +467,37 @@ function MapComponent({
429467
} else {
430468
Object.values(vehicleMarkersRef.current).forEach((marker) => marker && marker.setMap(null));
431469
}
432-
}, [selectedRow, isFollowingVehicle]);
470+
}, [selectedRow, isFollowingVehicle, useResponseLocation]);
471+
472+
// Update trip objects when toggle changes
473+
useEffect(() => {
474+
if (mapDivRef.current && mapDivRef.current.tripObjects) {
475+
log(`Updating TripObjects useResponseLocation to ${useResponseLocation}`);
476+
const tripObjects = mapDivRef.current.tripObjects;
477+
tripObjects.setUseResponseLocation(useResponseLocation);
478+
479+
// Redraw trips
480+
const trips = tripLogs.getTrips();
481+
_.forEach(trips, (trip) => {
482+
tripObjects.addTripVisuals(trip, minDate, maxDate);
483+
});
484+
}
485+
}, [useResponseLocation, tripLogs, minDate, maxDate]);
486+
487+
// Update toggle button UI
488+
useEffect(() => {
489+
const container = document.querySelector(".map-toggle-container");
490+
if (container) {
491+
const [reqBtn, , resBtn] = container.children;
492+
if (reqBtn && resBtn) {
493+
reqBtn.className = `map-toggle-button${!useResponseLocation ? " active" : ""}`;
494+
resBtn.className = `map-toggle-button${useResponseLocation ? " active" : ""}`;
495+
}
496+
}
497+
if (isFollowingVehicle && selectedRow) {
498+
// Re-center if we are following and the toggle changed
499+
}
500+
}, [useResponseLocation, isFollowingVehicle, selectedRow, recenterOnVehicleWrapper]);
433501

434502
const toggleHandlers = useMemo(() => {
435503
const map = mapRef.current;

src/Trip.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Trip {
99
this.tripName = tripName;
1010
this.updateRequests = 1;
1111
this.pathCoords = [];
12+
this.pathCoordsResponse = [];
1213
this.tripDuration = 0;
1314
this.creationTime = "Unknown";
1415
this.firstUpdate = firstUpdate;
@@ -37,11 +38,12 @@ class Trip {
3738
};
3839
}
3940

40-
getPathCoords(minDate, maxDate) {
41+
getPathCoords(minDate, maxDate, useResponse = false) {
42+
const coords = useResponse ? this.pathCoordsResponse : this.pathCoords;
4143
if (!(minDate && maxDate)) {
42-
return this.pathCoords;
44+
return coords;
4345
}
44-
return _(this.pathCoords)
46+
return _(coords)
4547
.filter((le) => {
4648
return le.date >= minDate && le.date <= maxDate;
4749
})
@@ -59,6 +61,15 @@ class Trip {
5961
});
6062
}
6163

64+
appendResponseCoords(lastLocation, timestamp) {
65+
this.pathCoordsResponse.push({
66+
lat: lastLocation.location.latitude,
67+
lng: lastLocation.location.longitude,
68+
trip_id: this.tripName,
69+
date: new Date(timestamp),
70+
});
71+
}
72+
6273
setPlannedPath(plannedPath) {
6374
this.plannedPath = plannedPath.map((coords) => {
6475
return { lat: coords.latitude, lng: coords.longitude };

src/TripLogs.js

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ function processRawLogs(rawLogs, solutionType) {
138138
routeSegment: null,
139139
routeSegmentTraffic: null,
140140
currentTrips: [],
141+
responseLocation: null,
142+
responseHeading: 0,
141143
};
142144

143145
for (let idx = 0; idx < sortedLogs.length; idx++) {
@@ -161,38 +163,51 @@ function processRawLogs(rawLogs, solutionType) {
161163

162164
// Get current data from the API call
163165
const currentLocation = _.get(newLog, `${vehiclePath}.lastlocation`);
166+
const currentResponseLocation = _.get(newLog, "response.lastlocation");
164167
const currentRouteSegment = _.get(newLog, `${vehiclePath}.currentroutesegment`);
165168
const currentRouteSegmentTraffic = _.get(newLog, `${vehiclePath}.currentroutesegmenttraffic`);
166169

167-
// Navigation status (fallback to response because it's typically only in the request when it changes)
168-
newLog.navStatus = _.get(newLog, `${vehiclePath}.navstatus`) || _.get(newLog, "response.navigationstatus");
169-
170-
// Create lastlocation object using deep cloned data where available
170+
// Location & Heading Normalization - create lastlocation objects using deep cloned data
171171
newLog.lastlocation = currentLocation ? _.cloneDeep(currentLocation) : {};
172+
// For calculations of server/client time deltas
173+
newLog.lastlocationResponse = currentResponseLocation ? _.cloneDeep(currentResponseLocation) : {};
172174

173175
// Data Normalization within a single log, create location from rawlocation when absent
174176
if (!newLog.lastlocation.location && newLog.lastlocation.rawlocation) {
175-
log(`processRawLogs: Falling back to rawlocation for log at ${newLog.timestamp}`);
177+
log(`processRawLogs Request: Falling back to rawlocation for log at ${newLog.timestamp}`);
176178
newLog.lastlocation.location = _.cloneDeep(newLog.lastlocation.rawlocation);
177179
}
180+
if (!newLog.lastlocationResponse.location && newLog.lastlocationResponse.rawlocation) {
181+
log(`processRawLogs Response: Falling back to rawlocation for log at ${newLog.timestamp}`);
182+
newLog.lastlocationResponse.location = _.cloneDeep(newLog.lastlocationResponse.rawlocation);
183+
}
178184

179-
// Apply last known location if needed
185+
// Apply last known locations if needed
180186
if (!newLog.lastlocation.location && lastKnownState.location) {
181187
newLog.lastlocation.location = _.cloneDeep(lastKnownState.location);
182188
newLog.lastlocation.heading = lastKnownState.heading;
183189
}
190+
if (!newLog.lastlocationResponse.location && lastKnownState.responseLocation) {
191+
newLog.lastlocationResponse.location = _.cloneDeep(lastKnownState.responseLocation);
192+
newLog.lastlocationResponse.heading = lastKnownState.responseHeading;
193+
}
184194

185-
// Keep the same current trips if we had an API error
186-
if (hasApiError && lastKnownState.currentTrips.length > 0) {
187-
log(`Preserving current trips due to API error for log at ${newLog.timestamp}`);
188-
if (!newLog.response) {
189-
newLog.response = {};
190-
}
191-
newLog.response.currenttrips = [...lastKnownState.currentTrips];
192-
} else if (_.get(newLog, "response.currenttrips")) {
193-
lastKnownState.currentTrips = [...newLog.response.currenttrips];
195+
// Update lastKnownState for next iterations
196+
const locToStore = currentLocation?.location || currentLocation?.rawlocation;
197+
if (locToStore) {
198+
lastKnownState.location = _.cloneDeep(locToStore);
199+
lastKnownState.heading = currentLocation.heading ?? lastKnownState.heading;
200+
}
201+
const respLocToStore = currentResponseLocation?.location || currentResponseLocation?.rawlocation;
202+
if (respLocToStore) {
203+
lastKnownState.responseLocation = _.cloneDeep(respLocToStore);
204+
lastKnownState.responseHeading = currentResponseLocation.heading ?? lastKnownState.responseHeading;
194205
}
195206

207+
// Route & Traffic Logic
208+
// Navigation status (fallback to response because it's typically only in the request when it changes)
209+
newLog.navStatus = _.get(newLog, `${vehiclePath}.navstatus`) || _.get(newLog, "response.navigationstatus");
210+
196211
// If Navigation SDK is NO_GUIDANCE, reset the lastKnownState planned route and traffic
197212
if (typeof newLog.navStatus === "string" && newLog.navStatus.endsWith("NO_GUIDANCE")) {
198213
lastKnownState.routeSegment = null;
@@ -217,26 +232,21 @@ function processRawLogs(rawLogs, solutionType) {
217232
}
218233
}
219234

220-
// Create other synthetic fields needed for the app
221-
// For calculations of server/client time deltas
222-
newLog.lastlocationResponse = _.get(newLog, "response.lastlocation")
223-
? _.cloneDeep(_.get(newLog, "response.lastlocation"))
224-
: null;
225-
226-
newLog.error = _.get(newLog, "error.message");
235+
// Keep the same current trips if we had an API error
236+
if (hasApiError && lastKnownState.currentTrips.length > 0) {
237+
log(`Preserving current trips due to API error for log at ${newLog.timestamp}`);
238+
if (!newLog.response) newLog.response = {};
239+
newLog.response.currenttrips = [...lastKnownState.currentTrips];
240+
} else if (_.get(newLog, "response.currenttrips")) {
241+
lastKnownState.currentTrips = [...newLog.response.currenttrips];
242+
}
227243

228244
// Sort currentTrips array since sometimes it could contain multiple trip ids in random order
229245
if (_.get(newLog, "response.currenttrips")) {
230246
newLog.response.currenttrips.sort();
231247
}
232248

233-
// Update lastKnownState for next iterations
234-
const locToStore = currentLocation?.location || currentLocation?.rawlocation;
235-
if (locToStore) {
236-
lastKnownState.location = _.cloneDeep(locToStore);
237-
lastKnownState.heading = currentLocation.heading ?? lastKnownState.heading;
238-
}
239-
249+
newLog.error = _.get(newLog, "error.message");
240250
newLogs.push(newLog);
241251
}
242252
}
@@ -554,6 +564,10 @@ class TripLogs {
554564
if (lastLocation && lastLocation.location) {
555565
curTripData.appendCoords(lastLocation, le.timestamp);
556566
}
567+
const lastLocationResponse = le.lastlocationResponse;
568+
if (lastLocationResponse && lastLocationResponse.location) {
569+
curTripData.appendResponseCoords(lastLocationResponse, le.timestamp);
570+
}
557571
}
558572
const tripStatus = _.get(le, "response.tripstatus");
559573
if (tripStatus && tripStatus !== lastTripStatus) {

0 commit comments

Comments
 (0)