Skip to content
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
15 changes: 15 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,19 @@ class App extends React.Component {
const tripLogs = new TripLogs(data.rawLogs, data.solutionType);
const newVisibleToggles = getVisibleToggles(data.solutionType);

let newToggleOptions = { ...this.state.toggleOptions };
let newExtraColumns = [...this.state.extraColumns];

if (data.solutionType === "LMFS") {
const tasksToggleId = "showTasksAsCreated";
const tasksToggle = _.find(ALL_TOGGLES, { id: tasksToggleId });
if (tasksToggle && !newToggleOptions[tasksToggleId]) {
log("Auto-enabling 'showTasksAsCreated' for LMFS dataset.");
newToggleOptions[tasksToggleId] = true;
newExtraColumns = _.union(newExtraColumns, tasksToggle.columns);
}
}

this.setState(
(prevState) => ({
activeDatasetIndex: index,
Expand All @@ -711,6 +724,8 @@ class App extends React.Component {
},
visibleToggles: newVisibleToggles,
dynamicMarkerLocations: {}, // Clear markers when switching datasets
toggleOptions: newToggleOptions,
extraColumns: newExtraColumns,
}),
() => {
log(`Switched to dataset ${index}`);
Expand Down
2 changes: 1 addition & 1 deletion src/Dataframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ function Dataframe({ featuredObject, extraColumns, onColumnToggle, onToggleMarke
if (depth === 2 && indexOrName === "request") {
return false;
}
if (depth === 3 && ["vehicle", "trip"].includes(indexOrName)) {
if (depth === 3 && ["vehicle", "trip", "deliveryvehicle", "task"].includes(indexOrName)) {
return false;
}
return true;
Expand Down
8 changes: 6 additions & 2 deletions src/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,10 @@ function MapComponent({

if (!map || !selectedRow) return;

const location = _.get(selectedRow.lastlocation, "location") || _.get(selectedRow.lastlocationResponse, "location");
const location =
_.get(selectedRow.lastlocation, "location") ||
_.get(selectedRow.lastlocation, "rawlocation") ||
_.get(selectedRow.lastlocationResponse, "location");

if (location?.latitude && location?.longitude) {
const pos = { lat: location.latitude, lng: location.longitude };
Expand Down Expand Up @@ -380,8 +383,9 @@ function MapComponent({
trafficLayerRef,
locationProviderRef,
jwt,
focusSelectedRow,
});
}, [mapRef.current, tripLogs, taskLogs, minDate, maxDate, jwt, setFeaturedObject, setTimeRange]);
}, [mapRef.current, tripLogs, taskLogs, minDate, maxDate, jwt, setFeaturedObject, setTimeRange, focusSelectedRow]);

useEffect(() => {
if (_.isEmpty(toggleHandlers)) {
Expand Down
102 changes: 98 additions & 4 deletions src/MapToggles.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function getToggleHandlers({
trafficLayerRef,
locationProviderRef,
jwt,
focusSelectedRow,
}) {
const GenerateBubbles = (bubbleName, cb) => (showBubble) => {
_.forEach(bubbleMapRef.current[bubbleName], (bubble) => bubble.setMap(null));
Expand Down Expand Up @@ -422,12 +423,105 @@ export function getToggleHandlers({
const bubbleName = "showTasksAsCreated";
_.forEach(bubbleMapRef.current[bubbleName], (b) => b.setMap(null));
delete bubbleMapRef.current[bubbleName];

const getIcon = (task) => {
const outcome = task.taskoutcome || "unknown";
const urlBase = "http://maps.google.com/mapfiles/kml/shapes/";
const icon = {
url: urlBase,
scaledSize: new window.google.maps.Size(30, 30),
};
if (outcome.includes("SUCCEEDED")) {
icon.url += "flag.png";
} else if (outcome.includes("FAIL")) {
icon.url += "caution.png";
} else {
icon.url += "shaded_dot.png";
}
return icon;
};

if (enabled) {
log(`Enabling ${bubbleName}`);
bubbleMapRef.current[bubbleName] = _.map(
taskLogs.getTasks(maxDate).value(),
(t) => new window.google.maps.Marker({ map, position: t.plannedlocation.point })
);
const tasks = taskLogs.getTasks(maxDate).value();

bubbleMapRef.current[bubbleName] = _(tasks)
.map((task) => {
if (!task.plannedlocation?.point) {
return null;
}

const marker = new window.google.maps.Marker({
position: {
lat: task.plannedlocation.point.latitude,
lng: task.plannedlocation.point.longitude,
},
map: map,
icon: getIcon(task),
title: `${task.state}: ${task.taskid} - ${task.trackingid}`,
});

marker.addListener("click", () => {
log(`Task marker clicked: ${task.taskid}`);
const latestUpdateLog = _.findLast(tripLogs.rawLogs, (le) => {
const type = le["@type"];
if (type === "createTask" || type === "updateTask" || type === "getTask") {
const idInResp = _.get(le, "response.name", "").split("/").pop();
if (idInResp === task.taskid) return true;

const idInReq = _.get(le, "request.taskid");
if (idInReq === task.taskid) return true;
}
return false;
});

if (latestUpdateLog) {
log(`Found matching log entry for task ${task.taskid}`, latestUpdateLog);
setFeaturedObject(latestUpdateLog);
setTimeout(() => focusSelectedRow(), 0);
} else {
setFeaturedObject(task);
}
});

const ret = [marker];
if (task.taskoutcomelocation?.point && task.plannedlocation?.point) {
log(`Task ${task.taskid} has taskoutcomelocation, drawing arrow.`);
const arrowColor = task.plannedVsActualDeltaMeters > 50 ? "#FF1111" : "#11FF11";
const offSetPath = new window.google.maps.Polyline({
path: [
{
lat: task.plannedlocation.point.latitude,
lng: task.plannedlocation.point.longitude,
},
{
lat: task.taskoutcomelocation.point.latitude,
lng: task.taskoutcomelocation.point.longitude,
},
],
geodesic: true,
strokeColor: arrowColor,
strokeOpacity: 0.6,
strokeWeight: 4,
map: map,
icons: [
{
icon: {
path: window.google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
strokeColor: arrowColor,
strokeWeight: 4,
},
offset: "100%",
},
],
});
ret.push(offSetPath);
}
return ret;
})
.flatten()
.compact()
.value();
}
},
showPlannedPaths: (enabled) => {
Expand Down
12 changes: 10 additions & 2 deletions src/TripLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ function processRawLogs(rawLogs, solutionType) {
// Create lastlocation object using deep cloned data where available
newLog.lastlocation = currentLocation ? _.cloneDeep(currentLocation) : {};

// Data Normalization within a single log, create location from rawlocation when absent
if (!newLog.lastlocation.location && newLog.lastlocation.rawlocation) {
log(`processRawLogs: Falling back to rawlocation for log at ${newLog.timestamp}`);
newLog.lastlocation.location = _.cloneDeep(newLog.lastlocation.rawlocation);
}

// Apply last known location if needed
if (!newLog.lastlocation.location && lastKnownState.location) {
newLog.lastlocation.location = _.cloneDeep(lastKnownState.location);
Expand Down Expand Up @@ -222,8 +228,10 @@ function processRawLogs(rawLogs, solutionType) {
}

// Update lastKnownState for next iterations
if (currentLocation?.location) {
lastKnownState.location = _.cloneDeep(currentLocation.location);
const locToStore = currentLocation?.location || currentLocation?.rawlocation;
if (locToStore) {
log(`processRawLogs: Storing last known location for log at ${newLog.timestamp}`);
lastKnownState.location = _.cloneDeep(locToStore);
lastKnownState.heading = currentLocation.heading ?? lastKnownState.heading;
}

Expand Down
85 changes: 85 additions & 0 deletions src/TripLogs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,91 @@ test("basic lmfs trip log loading", async () => {
]);
});

describe("Location Data Processing", () => {
test("LMFS log populates .location from .rawlocation as a fallback", () => {
const rawLocation = { latitude: 37.422, longitude: -122.084 };
const mockLogs = [
{
timestamp: "2023-01-01T12:00:00Z",
jsonpayload: {
"@type": "updateDeliveryVehicle",
request: {
deliveryvehicle: {
lastlocation: {
rawlocation: rawLocation,
},
},
},
response: {},
},
},
];

const tripLogs = new TripLogs(mockLogs, "LMFS");
expect(tripLogs.rawLogs[0].lastlocation.location).toEqual(rawLocation);
});

test("ODRD log with both location and rawlocation prefers location", () => {
const snappedLocation = { latitude: 37.7749, longitude: -122.4194 };
const rawLocation = { latitude: 37.775, longitude: -122.4195 };
const mockLogs = [
{
timestamp: "2023-01-01T10:00:00Z",
jsonpayload: {
"@type": "updateVehicle",
request: {
vehicle: {
lastlocation: {
location: snappedLocation,
rawlocation: rawLocation,
},
},
},
response: {},
},
},
];

const tripLogs = new TripLogs(mockLogs, "ODRD");
expect(tripLogs.rawLogs[0].lastlocation.location).toEqual(snappedLocation);
expect(tripLogs.rawLogs[0].lastlocation.location).not.toEqual(rawLocation);
});

test("lastKnownState propagates location from an LMFS rawlocation", () => {
const rawLocation = { latitude: 37.422, longitude: -122.084 };
const mockLogs = [
{
timestamp: "2023-01-01T12:00:00Z",
jsonpayload: {
"@type": "updateDeliveryVehicle",
request: {
deliveryvehicle: {
lastlocation: {
rawlocation: rawLocation,
heading: 180,
},
},
},
response: {},
},
},
{
timestamp: "2023-01-01T12:01:00Z",
jsonpayload: {
"@type": "updateDeliveryVehicle",
request: {
deliveryvehicle: {},
},
response: {},
},
},
];
const tripLogs = new TripLogs(mockLogs, "LMFS");
expect(tripLogs.rawLogs[1].lastlocation.location).toEqual(rawLocation);
expect(tripLogs.rawLogs[1].lastlocation.heading).toBe(180);
});
});

test("lastKnownState location is correctly applied to subsequent logs", () => {
// Create mock data with two log entries
const mockLogs = [
Expand Down
Loading