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

feat: Add labeled icons showing route stop order in post-solve view #170

Merged
merged 20 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Feature, Point } from '@turf/helpers';
import RoutesMetadataSelectors from './routes-metadata.selectors';
import * as fromUI from './ui.selectors';
import ShipmentRouteSelectors from './shipment-route.selectors';
import { selectVisitRequestStopOrder } from './shipment-route.selectors';

export const selectVisitRequests = createSelector(
fromVisitRequest.selectAll,
Expand Down Expand Up @@ -118,6 +119,56 @@ export const selectFilteredVisitRequests = createSelector(
}
);

export const selectFilteredVisitRequestsWithStopOrderAndSelectionStatus = createSelector(
selectFilteredVisitRequests,
selectFilteredRouteVisitRequestsSelected,
selectVisitRequestStopOrder,
fromVisit.selectEntities,
RoutesChartSelectors.selectSelectedRoutesColors,
(visitRequests, selectedVisitRequests, stopOrder, visits, colors) => {
const visitRequestsWithOrder = [];
visitRequests.forEach((vr) => {
const made = !!visits[vr.id];
visitRequestsWithOrder.push({
...vr,
color: made ? colors[visits[vr.id].shipmentRouteId] : null,
stopOrder: stopOrder[vr.id],
selected: selectedVisitRequests.some((svr) => svr.id === vr.id),
});
});
return visitRequestsWithOrder;
}
);

export const selectFilteredVisitRequestsWithStopOrder = createSelector(
selectFilteredVisitRequests,
selectVisitRequestStopOrder,
(visitRequests, stopOrder) => {
const visitRequestsWithOrder = [];
visitRequests.forEach((vr) =>
visitRequestsWithOrder.push({ ...vr, stopOrder: stopOrder[vr.id] })
);
return visitRequestsWithOrder;
}
);

export const selectFilteredVisitRequestsSelectedWithStopOrder = createSelector(
selectFilteredRouteVisitRequestsSelected,
selectVisitRequestStopOrder,
fromVisit.selectEntities,
RoutesChartSelectors.selectSelectedRoutesColors,
(visitRequests, stopOrder, visits, colors) => {
return visitRequests.map((visitRequest) => {
const made = !!visits[visitRequest.id];
return {
...visitRequestToDeckGL(visitRequest, made),
color: made ? colors[visits[visitRequest.id].shipmentRouteId] : null,
stopOrder: stopOrder[visitRequest.id],
};
});
}
);

export const selectFilteredVisitRequestsSelected = createSelector(
selectFilteredRouteVisitRequestsSelected,
fromVisit.selectEntities,
Expand Down Expand Up @@ -166,7 +217,7 @@ export const selectFilteredVisitRequestsTurfPoints = createSelector(
);

export const selectMouseOverVisitRequests = createSelector(
selectFilteredRouteVisitRequests,
selectFilteredVisitRequestsWithStopOrder,
RoutesChartSelectors.selectSelectedRoutesVisitIds,
fromVisit.selectEntities,
RoutesChartSelectors.selectSelectedRoutesColors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,16 @@ const selectRoutesDuration = createSelector(
}
);

export const selectVisitRequestStopOrder = createSelector(selectEntities, (routes) => {
const stopOrders = {};
Object.keys(routes).forEach((key) =>
routes[key].visits.forEach((id, index) => {
stopOrders[id] = index + 1;
})
);
return stopOrders;
});

export const ShipmentRouteSelectors = {
selectRouteByIdFn,
selectOverviewPolylinePointsFn,
Expand All @@ -354,6 +364,7 @@ export const ShipmentRouteSelectors = {
selectRouteShipmentCount,
selectRouteIndexById,
selectRoutesDuration,
selectVisitRequestStopOrder,
};

export default ShipmentRouteSelectors;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export abstract class BaseVisitRequestLayer {
protected store: Store<State>,
protected zone: NgZone
) {
this.getIconMapping();
this.iconMapping = this.createIconMapping(this.iconSize);
}
protected abstract layerId: string;
get visible(): boolean {
Expand All @@ -42,15 +42,15 @@ export abstract class BaseVisitRequestLayer {
this.gLayer.setMap(this._visible ? this.mapService.map : null);
}

private gLayer: GoogleMapsOverlay = new GoogleMapsOverlay({});
private layer: IconLayer = new IconLayer({});
private selectedDataLayer: IconLayer = new IconLayer({});
private mouseOverLayer: IconLayer = new IconLayer({});
protected gLayer: GoogleMapsOverlay = new GoogleMapsOverlay({});
protected layer: IconLayer = new IconLayer({});
protected selectedDataLayer: IconLayer = new IconLayer({});
protected mouseOverLayer: IconLayer = new IconLayer({});

private _visible: boolean;

readonly iconSize = [64, 44];
private iconMapping = {};
readonly iconSize: [number, number] = [64, 44];
protected iconMapping = {};
readonly defaultColor = 'blue-grey';
readonly defaultSelectedColor = 'red';

Expand Down Expand Up @@ -100,34 +100,61 @@ export abstract class BaseVisitRequestLayer {
'pickup',
];

private getIconMapping(): void {
protected createIconMapping(iconSize: [number, number]): any {
const mapping = {};
// dynamically create icon mapping based on sprite
for (let i = 0; i < this.iconMappingOrder.length; i++) {
const icon = this.iconMappingOrder[i];
this.iconMapping[icon] = {
mapping[icon] = {
x: 0,
y: this.iconSize[1] * i,
width: this.iconSize[0],
y: iconSize[1] * i,
width: iconSize[0],
// clip height by 1 pixel to reduce a noticeable artifact that's more
// prominent on deliveries when zoomed out
height: this.iconSize[1] - 1,
height: iconSize[1] - 1,
};
}
return mapping;
}

protected getIconMapping(): any {
return this.iconMapping;
}

protected getIconAtlas(): string {
return './assets/images/dropoff_pickup_sprite.png';
}

protected getSizeScale(): number {
return 1.75;
}

protected getIcon(data): string {
const color = (data.color && data.color.name) || this.defaultSelectedColor;
const key = data.pickup ? `pickup-${color}` : `dropoff-${color}`;
return key in this.iconMapping
? key
: data.pickup
? `pickup-${this.defaultSelectedColor}`
: `dropoff-${this.defaultSelectedColor}`;
}

protected updateLayers(): void {
this.gLayer.setProps({
layers: [this.layer, this.selectedDataLayer, this.mouseOverLayer],
});
}

abstract getDefaultIconFn(data): string;
protected onDataFiltered(data): void {
this.layer = new IconLayer({
id: this.layerId,
data,
iconAtlas: './assets/images/dropoff_pickup_sprite.png',
iconMapping: this.iconMapping,
iconAtlas: this.getIconAtlas(),
iconMapping: this.getIconMapping(),
getIcon: (d) => this.getDefaultIconFn(d),
sizeUnits: 'meters',
sizeMinPixels: 15,
sizeMaxPixels: 43.5,
getSize: 15,
sizeScale: 10,
getSize: 10,
sizeScale: this.getSizeScale(),
getPosition: (d) => d.arrivalPosition,
pickable: true,
onHover: ({ object }) => {
Expand All @@ -139,61 +166,36 @@ export abstract class BaseVisitRequestLayer {
});
},
});
this.gLayer.setProps({ layers: [this.layer, this.selectedDataLayer, this.mouseOverLayer] });
this.updateLayers();
}

protected onDataSelected(data): void {
this.selectedDataLayer = new IconLayer({
id: this.layerId + '-selected',
data,
iconAtlas: './assets/images/dropoff_pickup_sprite.png',
iconMapping: this.iconMapping,
getIcon: (d) => {
const color = (d.color && d.color.name) || this.defaultSelectedColor;
const key = d.pickup ? `pickup-${color}` : `dropoff-${color}`;
return key in this.iconMapping
? key
: d.pickup
? `pickup-${this.defaultSelectedColor}`
: `dropoff-${this.defaultSelectedColor}`;
},
sizeUnits: 'meters',
sizeMinPixels: 15,
sizeMaxPixels: 43.5,
getSize: 15,
sizeScale: 10,
iconAtlas: this.getIconAtlas(),
iconMapping: this.getIconMapping(),
getIcon: (d) => this.getIcon(d),
getSize: 10,
sizeScale: this.getSizeScale(),
getPosition: (d) => d.arrivalPosition,
pickable: false,
});
this.gLayer.setProps({ layers: [this.layer, this.selectedDataLayer, this.mouseOverLayer] });
this.updateLayers();
}

protected onDataMouseOver(data): void {
this.mouseOverLayer = new IconLayer({
id: this.layerId + '-mouseOver',
data,
iconAtlas: './assets/images/dropoff_pickup_sprite.png',
iconMapping: this.iconMapping,
getIcon: (d) => {
if (!d.selected) {
return this.getDefaultIconFn(d);
}
const color = (d.color && d.color.name) || this.defaultSelectedColor;
const key = d.pickup ? `pickup-${color}` : `dropoff-${color}`;
return key in this.iconMapping
? key
: d.pickup
? `pickup-${this.defaultSelectedColor}`
: `dropoff-${this.defaultSelectedColor}`;
},
sizeUnits: 'meters',
sizeMinPixels: 21,
sizeMaxPixels: 61,
getSize: 25,
sizeScale: 10,
iconAtlas: this.getIconAtlas(),
iconMapping: this.getIconMapping(),
getIcon: (d) => (d.selected ? this.getIcon(d) : this.getDefaultIconFn(d)),
getSize: 13,
sizeScale: this.getSizeScale(),
getPosition: (d) => d.arrivalPosition,
pickable: false,
});
this.gLayer.setProps({ layers: [this.layer, this.selectedDataLayer, this.mouseOverLayer] });
this.updateLayers();
}
}
4 changes: 4 additions & 0 deletions application/frontend/src/app/core/services/map.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class MapService {
private readonly completeEdits$ = new Subject<void>();
private center: google.maps.LatLngLiteral;
private zoom = 0;
readonly zoomChanged$ = new Subject<number>();

get map(): google.maps.Map {
return this.map$.getValue();
Expand All @@ -84,6 +85,9 @@ export class MapService {
myMap?.setZoom(this.zoom);
}
myMap?.setTilt(0); // disable 45° imagery
myMap?.addListener('zoom_changed', () => {
this.zone.run(() => this.zoomChanged$.next(this.map.getZoom()));
});
this.map$.next(myMap);
})
);
Expand Down
Loading
Loading