From 81f7a36c03268c37b7004a3b6e2f364a27af7903 Mon Sep 17 00:00:00 2001 From: geoffsee <> Date: Wed, 23 Jul 2025 09:58:57 -0400 Subject: [PATCH] Refactor `MapNext` to `GeoMap`, streamline code, and integrate optimized GeoJSON-based port rendering. --- Cargo.lock | 1 + crates/base-map/map/src/App.tsx | 4 +- crates/base-map/map/src/CustomGeolocate.ts | 2 +- crates/base-map/map/src/GeoMap.tsx | 96 +++++++++ crates/base-map/map/src/MapNext.tsx | 238 --------------------- 5 files changed, 100 insertions(+), 241 deletions(-) create mode 100644 crates/base-map/map/src/GeoMap.tsx delete mode 100644 crates/base-map/map/src/MapNext.tsx diff --git a/Cargo.lock b/Cargo.lock index 9052c71..db395be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9426,6 +9426,7 @@ checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" name = "yachtpit" version = "0.1.0" dependencies = [ + "ais", "anyhow", "base-map", "bevy", diff --git a/crates/base-map/map/src/App.tsx b/crates/base-map/map/src/App.tsx index 3a3e184..449a75e 100644 --- a/crates/base-map/map/src/App.tsx +++ b/crates/base-map/map/src/App.tsx @@ -2,7 +2,7 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import {Box, Button, HStack, Text} from '@chakra-ui/react'; import {useColorMode} from './components/ui/color-mode'; import {useCallback, useEffect, useState} from "react"; -import MapNext from "@/MapNext.tsx"; +import GeoMap from "@/GeoMap"; import {getNeumorphicColors, getNeumorphicStyle} from './theme/neumorphic-theme'; import {layers, LayerSelector} from "@/LayerSelector.tsx"; import {useAISProvider, type VesselData} from './ais-provider'; @@ -373,7 +373,7 @@ function App() { - void; + vesselPopup?: VesselData | null; + onVesselPopupClose?: () => void; +} + +export default function GeoMap(props: MapNextProps) { + const mapRef = useRef(null); + + const portsGeoJSON = useMemo>(() => ({ + type: 'FeatureCollection', + features: PORTS.map(port => ({ + type: 'Feature', + geometry: {type: 'Point', coordinates: [port.longitude, port.latitude]}, + properties: {city: port.city, state: port.state} + } as Feature)) + }), []); + + const handleGeolocate = useCallback((pos: GeolocationPosition) => { + console.log('User location loaded:', pos); + }, []); + + return ( + + + + + + + + + + + + + ); +} diff --git a/crates/base-map/map/src/MapNext.tsx b/crates/base-map/map/src/MapNext.tsx deleted file mode 100644 index 122a836..0000000 --- a/crates/base-map/map/src/MapNext.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import {useState, useMemo, useCallback, useRef} from 'react'; -import Map, { - Marker, - Popup, - NavigationControl, - FullscreenControl, - ScaleControl, - GeolocateControl, - type MapRef -} from 'react-map-gl/mapbox'; - -import ControlPanel from './control-panel.tsx'; -import Pin from './pin.tsx'; -import VesselMarker from './vessel-marker'; -import type { VesselData } from './ais-provider'; - -import PORTS from './test_data/nautical-base-data.json'; -import {Box} from "@chakra-ui/react"; - - -export interface Geolocation { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Geolocation/clearWatch) */ - clearWatch(watchId: number): void; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Geolocation/getCurrentPosition) */ - getCurrentPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null, options?: PositionOptions): void; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Geolocation/watchPosition) */ - watchPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null, options?: PositionOptions): number; -} - - - -interface MapNextProps { - mapboxPublicKey: string; - geolocation: Geolocation; - vesselPosition?: any; - layer?: any; - mapView?: any; - aisVessels?: VesselData[]; - onVesselClick?: (vessel: VesselData) => void; - vesselPopup?: VesselData | null; - onVesselPopupClose?: () => void; -} - -export default function MapNext(props: MapNextProps) { - const [popupInfo, setPopupInfo] = useState(null); - const mapRef = useRef(null); - - // Handle user location events - const handleGeolocate = useCallback((position: GeolocationPosition) => { - console.log('User location loaded:', position); - }, []); - - const handleTrackUserLocationStart = useCallback(() => { - console.log('Started tracking user location'); - }, []); - - const handleTrackUserLocationEnd = useCallback(() => { - console.log('Stopped tracking user location'); - }, []); - - const pins = useMemo( - () => - PORTS.map((city, index) => ( - { - // If we let the click event propagates to the map, it will immediately close the popup - // with `closeOnClick: true` - e.originalEvent.stopPropagation(); - /* - src/MapNext.tsx:34:38 - error TS2345: Argument of type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' is not assignable to parameter of type 'SetStateAction'. - Type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' provides no match for the signature '(prevState: null): null'. - */ - // @ts-ignore - setPopupInfo(city); - }} - > - - - )), - [] - ); - - // Helper function to get vessel color based on type - const getVesselColor = (type: string): string => { - switch (type.toLowerCase()) { - case 'yacht': - case 'pleasure craft': - return '#00cc66'; - case 'fishing vessel': - case 'fishing': - return '#ff6600'; - case 'cargo': - case 'container': - return '#cc0066'; - case 'tanker': - return '#ff0000'; - case 'passenger': - return '#6600cc'; - default: - return '#0066cc'; - } - }; - - // Create vessel markers - const vesselMarkers = useMemo(() => - (props.aisVessels || []).map((vessel) => ( - { - e.originalEvent.stopPropagation(); - if (props.onVesselClick) { - props.onVesselClick(vessel); - } - }} - > - - - )), - [props.aisVessels, props.onVesselClick] - ); - - return ( - - - - - - - - {pins} - {vesselMarkers} - - {/* Vessel Popup */} - {props.vesselPopup && ( - props.onVesselPopupClose && props.onVesselPopupClose()} - closeButton={true} - closeOnClick={false} - > -
-

{props.vesselPopup.name}

-
MMSI: {props.vesselPopup.mmsi}
-
Type: {props.vesselPopup.type}
-
Speed: {props.vesselPopup.speed.toFixed(1)} knots
-
Heading: {props.vesselPopup.heading}°
-
Position: {props.vesselPopup.latitude.toFixed(4)}, {props.vesselPopup.longitude.toFixed(4)}
-
- Last update: {props.vesselPopup.lastUpdate.toLocaleTimeString()} -
-
-
- )} - - {popupInfo && ( - setPopupInfo(null)} - > -
- {/*src/MapNext.tsx:71:40 - error TS2339: Property 'city' does not exist on type 'never'. - -71 {popupInfo.city}, {popupInfo.state} |{' '} - ~~~~*/} - - {/*@ts-ignore*/}{/*@ts-ignore*/} - {popupInfo.city},{popupInfo.state} - {/*@ts-ignore*/} - - Wikipedia - -
- {/*@ts-ignore*/} - -
- )} - - - -
- - -
- ); -} \ No newline at end of file