From 680542ed0eb884ed2e2af505b133e7a0584ff71a Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:18:01 -0800 Subject: [PATCH] feat: Add TripObjects to handle polylines and markers of trips --- src/TripObjects.js | 217 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/TripObjects.js diff --git a/src/TripObjects.js b/src/TripObjects.js new file mode 100644 index 0000000..b12040a --- /dev/null +++ b/src/TripObjects.js @@ -0,0 +1,217 @@ +// src/TripObjects.js + +import { log } from "./Utils"; +import { getColor } from "./Trip"; + +export class TripObjects { + constructor({ map, setFeaturedObject, setTimeRange }) { + this.map = map; + this.markers = new Map(); + this.paths = new Map(); + this.arrows = new Map(); + this.setFeaturedObject = setFeaturedObject; + this.setTimeRange = setTimeRange; + } + + createSVGMarker(type, color) { + const isPickup = type.toLowerCase().includes("pickup"); + const isActual = type.includes("actual"); + + const createChevronPath = (direction, shift = 0) => { + let points; + if (direction === "up") { + points = [ + { x: 12, y: 20 + shift }, // Top point of chevron + { x: 8, y: 24 + shift }, // Bottom left + { x: 16, y: 24 + shift }, // Bottom right + { x: 12, y: 20 + shift }, + ]; + } else { + // "down" + points = [ + { x: 12, y: 24 + shift }, // Bottom point of chevron + { x: 8, y: 20 + shift }, // Top left + { x: 16, y: 20 + shift }, // Top right + { x: 12, y: 24 + shift }, + ]; + } + + return ``; + }; + + const svgBase = ` + + ${isActual ? (isPickup ? createChevronPath("up", -4) : createChevronPath("down", -4)) : ""} + ${isPickup ? createChevronPath("up") : createChevronPath("down")} + `; + + return { + url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svgBase), + scaledSize: new google.maps.Size(24, 24), + anchor: new google.maps.Point(12, 24), + }; + } + + createMarkerWithEvents(point, type, color, pairedPoint, tripId) { + if (!point) return null; + + const marker = new google.maps.Marker({ + position: { lat: point.latitude, lng: point.longitude }, + icon: this.createSVGMarker(type, color), + map: this.map, + }); + + if (pairedPoint) { + // Store the arrow in the class instance using a unique key + const arrowKey = `${tripId}_${type.includes("actual") ? type.replace("actual", "") : type}`; + + google.maps.event.addListener(marker, "click", () => { + log(`${type} marker clicked`); + + if (this.arrows.has(arrowKey)) { + this.arrows.get(arrowKey).setMap(null); + this.arrows.delete(arrowKey); + } else { + // Create arrow from requested to actual + const from = type.includes("actual") ? pairedPoint : point; + const to = type.includes("actual") ? point : pairedPoint; + const arrow = this.createConnectingArrow(from, to, color); + if (arrow) { + this.arrows.set(arrowKey, arrow); + } + } + }); + } + + return marker; + } + + createConnectingArrow(from, to, color) { + if (!from || !to) return null; + + return new google.maps.Polyline({ + path: [ + { lat: from.latitude, lng: from.longitude }, + { lat: to.latitude, lng: to.longitude }, + ], + geodesic: true, + strokeColor: color, + strokeOpacity: 1, + strokeWeight: 1, + icons: [ + { + icon: { + path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW, + scale: 1, + }, + offset: "100%", + }, + ], + map: this.map, + }); + } + + addTripVisuals(trip, minDate, maxDate) { + const tripId = trip.tripName; + log(`Processing trip visuals for ${tripId}`, { + pickupPoint: trip.getPickupPoint(), + actualPickupPoint: trip.getActualPickupPoint(), + dropoffPoint: trip.getDropoffPoint(), + actualDropoffPoint: trip.getActualDropoffPoint(), + }); + + this.clearTripObjects(tripId); + + // Add path polyline + const tripCoords = trip.getPathCoords(minDate, maxDate); + if (tripCoords.length > 0) { + const path = new google.maps.Polyline({ + path: tripCoords, + geodesic: true, + strokeColor: getColor(trip.tripIdx), + strokeOpacity: 0.5, + strokeWeight: 6, + map: this.map, + }); + + // Add path events + google.maps.event.addListener(path, "mouseover", () => { + path.setOptions({ strokeOpacity: 1, strokeWeight: 8 }); + }); + + google.maps.event.addListener(path, "mouseout", () => { + path.setOptions({ strokeOpacity: 0.5, strokeWeight: 6 }); + }); + + google.maps.event.addListener(path, "click", () => { + log("Trip polyline clicked"); + const fd = trip.getFeaturedData(); + this.setFeaturedObject(fd); + // TODO: https://github.com/googlemaps/fleet-debugger/issues/79 + // this time range won't capture the createTrip logs + this.setTimeRange(fd.firstUpdate.getTime(), fd.lastUpdate.getTime()); + }); + + this.paths.set(tripId, path); + } + + const markers = []; + + // Get points + const pickupPoint = trip.getPickupPoint(); + const actualPickupPoint = trip.getActualPickupPoint(); + const dropoffPoint = trip.getDropoffPoint(); + const actualDropoffPoint = trip.getActualDropoffPoint(); + + // Create pickup markers + const pickupMarker = this.createMarkerWithEvents(pickupPoint, "pickup", "#3d633d", actualPickupPoint, tripId); + if (pickupMarker) markers.push(pickupMarker); + + const actualPickupMarker = this.createMarkerWithEvents( + actualPickupPoint, + "actualPickup", + "#3d633d", + pickupPoint, + tripId + ); + if (actualPickupMarker) markers.push(actualPickupMarker); + + // Create dropoff markers + const dropoffMarker = this.createMarkerWithEvents(dropoffPoint, "dropoff", "#0000FF", actualDropoffPoint, tripId); + if (dropoffMarker) markers.push(dropoffMarker); + + const actualDropoffMarker = this.createMarkerWithEvents( + actualDropoffPoint, + "actualDropoff", + "#0000FF", + dropoffPoint, + tripId + ); + if (actualDropoffMarker) markers.push(actualDropoffMarker); + + this.markers.set(tripId, markers); + } + + clearTripObjects(tripId) { + if (this.paths.has(tripId)) { + this.paths.get(tripId).setMap(null); + this.paths.delete(tripId); + } + + if (this.markers.has(tripId)) { + this.markers.get(tripId).forEach((marker) => marker.setMap(null)); + this.markers.delete(tripId); + } + + if (this.arrows.has(tripId)) { + this.arrows.get(tripId).forEach((arrow) => arrow.setMap(null)); + this.arrows.delete(tripId); + } + } + + clearAll() { + for (const tripId of this.paths.keys()) { + this.clearTripObjects(tripId); + } + } +}