Skip to content

Commit

Permalink
update: frontend from tanega commit:ab01183
Browse files Browse the repository at this point in the history
  • Loading branch information
herve.le-bars committed Jul 4, 2024
1 parent 9954325 commit 81aadd0
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 144 deletions.
Binary file added backend/vessel_width.xlsx
Binary file not shown.
3 changes: 0 additions & 3 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# data
public/data/geometries/

# dependencies
node_modules
.pnp
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/details/amp/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import mockData from "@/mock-data-details.json"
import mockData from "@/public/data/mock-data-details.json"

import DetailsContainer from "@/components/details/details-container"

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/details/vessel/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import mockData from "@/mock-data-details.json"
import mockData from "@/public/data/mock-data-details.json"

import DetailsContainer from "@/components/details/details-container"

Expand Down
7 changes: 6 additions & 1 deletion frontend/app/map/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Metadata } from "next"

import { siteConfig } from "@/config/site"
import { MapStoreProvider } from "@/components/providers/map-store-provider"
import { VesselsStoreProvider } from "@/components/providers/vessels-store-provider"

export const metadata: Metadata = {
title: {
Expand All @@ -29,7 +30,11 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) {
return (
<section className="relative flex h-screen w-full flex-row">
<MapStoreProvider>{children}</MapStoreProvider>
<MapStoreProvider>
<VesselsStoreProvider>
{children}
</VesselsStoreProvider>
</MapStoreProvider>
</section>
)
}
23 changes: 20 additions & 3 deletions frontend/app/map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ import LeftPanel from "@/components/core/left-panel"
import MapControls from "@/components/core/map-controls"
import DemoMap from "@/components/core/map/main-map"
import PositionPreview from "@/components/core/map/position-preview"
import { getVessels, getVesselsLatestPositions } from "@/services/backend-rest-client"

export default function MapPage() {
async function fetchVessels() {
const response = await getVessels();
return response.data ?? [];
}

async function fetchLatestPositions() {
// TODO(CT): move this logic within a cron job
const response = await getVesselsLatestPositions();
return response.data ?? [];
}

export default async function MapPage() {
const vessels = await fetchVessels();
const latestPositions = await fetchLatestPositions();

// TODO: create new client component dedicated to update store
// -> then Map + LeftPanel can just use storeProvider
return (
<>
<LeftPanel />
<DemoMap />
<LeftPanel vessels={vessels} />
<DemoMap vesselsPositions={latestPositions} />
<MapControls />
<PositionPreview />
</>
Expand Down
23 changes: 10 additions & 13 deletions frontend/components/core/command/vessel-finder.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
"use client"

import { useState } from "react"
import allVessels from "@/public/data/geometries/all_vessels_with_mmsi.json"
import latestPositions from "@/public/data/geometries/vessels_latest_positions.json"
import { FlyToInterpolator } from "deck.gl"
import { ShipIcon } from "lucide-react"

import { Vessel } from "@/types/vessel"
import { Vessel, VesselPosition } from "@/types/vessel"
import {
CommandDialog,
CommandEmpty,
Expand All @@ -17,8 +14,7 @@ import {
CommandSeparator,
} from "@/components/ui/command"
import { useMapStore } from "@/components/providers/map-store-provider"

import { VesselPosition } from "../map/main-map"
import { useVesselsStore } from "@/components/providers/vessels-store-provider"

type Props = {
wideMode: boolean
Expand All @@ -36,6 +32,8 @@ export function VesselFinderDemo({ wideMode }: Props) {
viewState,
setViewState,
} = useMapStore((state) => state)
const { vessels: allVessels } = useVesselsStore((state) => state);
const { latestPositions } = useMapStore((state) => state);

const onSelectVessel = (vesselIdentifier: string) => {
const mmsi = parseInt(vesselIdentifier.split(SEPARATOR)[1])
Expand All @@ -44,14 +42,14 @@ export function VesselFinderDemo({ wideMode }: Props) {
}
if (mmsi) {
const selectedVesselLatestPosition = latestPositions.find(
(position) => position.vessel_mmsi === mmsi
(position) => position.vessel.mmsi === mmsi
)
if (selectedVesselLatestPosition) {
setActivePosition(selectedVesselLatestPosition as VesselPosition)
setViewState({
...viewState,
longitude: selectedVesselLatestPosition.position_longitude,
latitude: selectedVesselLatestPosition.position_latitude,
longitude: selectedVesselLatestPosition.position.coordinates[0],
latitude: selectedVesselLatestPosition.position.coordinates[1],
zoom: 7,
pitch: 40,
transitionInterpolator: new FlyToInterpolator({ speed: 2 }),
Expand Down Expand Up @@ -108,12 +106,11 @@ export function VesselFinderDemo({ wideMode }: Props) {
{allVessels.map((vessel: Vessel) => {
return (
<CommandItem
key={`${vessel.imo}-${vessel.name}`}
key={`${vessel.id}`}
onSelect={(value) => onSelectVessel(value)}
value={`${vessel.name}${SEPARATOR}${vessel.mmsi}${SEPARATOR}${vessel.imo}`} // so we can search by name, mmsi, imo
value={`${vessel.ship_name}${SEPARATOR}${vessel.mmsi}${SEPARATOR}${vessel.imo}`} // so we can search by name, mmsi, imo
>
<ShipIcon className="mr-2 size-4" />
<span>{vessel.name}</span>
<span>{vessel.ship_name}</span>
<span className="ml-2 text-xxxs">
{" "}
MMSI {vessel.mmsi} | IMO {vessel.imo}
Expand Down
25 changes: 15 additions & 10 deletions frontend/components/core/left-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import NavigationLink from "@/components/ui/navigation-link"
import { VesselFinderDemo } from "@/components/core/command/vessel-finder"

import TrackedVesselsPanel from "./tracked-vessels-panel"
import { useVesselsStore } from "@/components/providers/vessels-store-provider"
import { Vessel } from "@/types/vessel"

const containerVariants = {
close: {
Expand Down Expand Up @@ -39,19 +41,24 @@ const svgVariants = {
},
}

const LeftPanel = () => {
type LeftPanelProps = {
vessels: Vessel[];
}

export default function({ vessels }: LeftPanelProps) {
const [isOpen, setIsOpen] = useState(false)
const containerControls = useAnimationControls()
const svgControls = useAnimationControls()
const { setVessels } = useVesselsStore((state) => state);

useEffect(() => {
setVessels(vessels);
}, [vessels]);

useEffect(() => {
if (isOpen) {
containerControls.start("open")
svgControls.start("open")
} else {
containerControls.start("close")
svgControls.start("close")
}
const control = isOpen ? "open" : "close";
containerControls.start(control);
svgControls.start(control);
}, [isOpen])

const handleOpenClose = () => {
Expand Down Expand Up @@ -120,5 +127,3 @@ const LeftPanel = () => {
</>
)
}

export default LeftPanel
110 changes: 37 additions & 73 deletions frontend/components/core/map/main-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,64 +16,22 @@ import Map from "react-map-gl/maplibre"

import MapTooltip from "@/components/ui/tooltip-map-template"
import { useMapStore } from "@/components/providers/map-store-provider"
import { VesselPosition, VesselPositions, VesselTrailPropertiesType } from "@/types/vessel"

const MESH_URL_LOCAL = `../../../data/mesh/boat.obj`

export type VesselVoyageTracksPropertiesType = {
vessel_ais_class: string
vessel_flag: string
vessel_name: string
vessel_callsign: string
vessel_ship_type?: string
vessel_sub_ship_type?: string
vessel_mmsi: number
vessel_imo: number
vessel_width: number
vessel_length: number
voyage_destination?: string
voyage_draught?: number
voyage_eta: string
}

export type VesselTrailPropertiesType = {
vessel_mmsi: number
speed: number
heading?: number
navigational_status: string
}

export type VesselPositions = VesselPosition[]

export interface VesselPosition {
vessel_flag: string
vessel_name: string
vessel_callsign: string
vessel_ship_type?: string
vessel_mmsi: number
vessel_imo: number
vessel_width: number
vessel_length: number
position_accuracy: string
position_collection_type: string
position_course: number
position_heading?: number
position_latitude: number
position_longitude: number
position_navigational_status: string
position_speed: number
position_timestamp: string
voyage_destination?: string
voyage_draught?: number
}

type BartStation = {
name: string
entries: number
exits: number
coordinates: [longitude: number, latitude: number]
}

export default function CoreMap() {
type CoreMapProps = {
vesselsPositions: VesselPositions;
}

export default function CoreMap({ vesselsPositions }: CoreMapProps) {
const { setTheme, theme } = useTheme()

const {
Expand All @@ -82,6 +40,7 @@ export default function CoreMap() {
activePosition,
setActivePosition,
trackedVesselMMSIs,
setLatestPositions,
} = useMapStore((state) => state)

// Use a piece of state that changes when `activePosition` changes to force re-render
Expand All @@ -98,22 +57,26 @@ export default function CoreMap() {
setLayerKey((prevKey) => prevKey + 1)
}, [activePosition, trackedVesselMMSIs])

useEffect(() => {
setLatestPositions(vesselsPositions);
}, [vesselsPositions])

const latestPositions = new ScatterplotLayer<VesselPosition>({
id: `vessels-latest-positions-${layerKey}`,
data: `../../../data/geometries/vessels_latest_positions.json`,
getPosition: (d: VesselPosition) => [
d.position_longitude,
d.position_latitude,
data: vesselsPositions,
getPosition: (vp: VesselPosition) => [
vp?.position?.coordinates[0],
vp?.position?.coordinates[1],
],
stroked: true,
radiusUnits: "meters",
getRadius: (d: VesselPosition) => d.vessel_length,
getRadius: (vp: VesselPosition) => vp.vessel.length,
radiusMinPixels: 3,
radiusMaxPixels: 25,
radiusScale: 200,
getFillColor: (d: VesselPosition) => {
return d.vessel_mmsi === activePosition?.vessel_mmsi ||
trackedVesselMMSIs.includes(d.vessel_mmsi)
getFillColor: (vp: VesselPosition) => {
return vp.vessel.mmsi === activePosition?.vessel.mmsi ||
trackedVesselMMSIs.includes(vp.vessel.mmsi)
? [128, 16, 189, 210]
: [16, 181, 16, 210]
},
Expand All @@ -124,8 +87,8 @@ export default function CoreMap() {
setActivePosition(object as VesselPosition)
setViewState({
...viewState,
longitude: object.position_longitude,
latitude: object.position_latitude,
longitude: object?.position?.coordinates[0],
latitude: object?.position?.coordinates[1],
zoom: 7,
pitch: 40,
transitionInterpolator: new FlyToInterpolator({ speed: 2 }),
Expand All @@ -134,6 +97,7 @@ export default function CoreMap() {
},
})

// TODO(CT): call backend
const tracksByVesselAndVoyage = trackedVesselMMSIs.map(
(trackedVesselMMSI) => {
return new GeoJsonLayer<VesselTrailPropertiesType>({
Expand All @@ -159,27 +123,27 @@ export default function CoreMap() {

const positions_mesh_layer = new SimpleMeshLayer({
id: `vessels-positions-mesh-layer-${layerKey}`,
data: `../../../data/geometries/vessels_latest_positions.json`,
data: vesselsPositions,
mesh: MESH_URL_LOCAL,
getPosition: (d: VesselPosition) => [
d.position_longitude,
d.position_latitude,
getPosition: (vp: VesselPosition) => [
vp?.position?.coordinates[0],
vp?.position?.coordinates[1],
],
getColor: (d: VesselPosition) => {
return d.vessel_mmsi === activePosition?.vessel_mmsi ||
trackedVesselMMSIs.includes(d.vessel_mmsi)
getColor: (vp: VesselPosition) => {
return vp.vessel.mmsi === activePosition?.vessel.mmsi ||
trackedVesselMMSIs.includes(vp.vessel.mmsi)
? [128, 16, 189, 210]
: [16, 181, 16, 210]
},
getOrientation: (d: VesselPosition) => [
getOrientation: (vp: VesselPosition) => [
0,
Math.round(d.position_heading ? d.position_heading : 0),
Math.round(vp.heading ? vp.heading : 0),
90,
],
getScale: (d: VesselPosition) => [
d.vessel_length,
d.vessel_length * 1.5,
d.vessel_length / 1.5,
getScale: (vp: VesselPosition) => [
vp.vessel.length,
vp.vessel.length * 1.5,
vp.vessel.length / 1.5,
],
scaleUnits: "pixels",
sizeScale: 100,
Expand All @@ -188,8 +152,8 @@ export default function CoreMap() {
setActivePosition(object as VesselPosition)
setViewState({
...viewState,
longitude: object.position_longitude,
latitude: object.position_latitude,
longitude: object?.position?.coordinates[0],
latitude: object?.position?.coordinates[1],
zoom: 10,
transitionInterpolator: new FlyToInterpolator({ speed: 2 }),
transitionDuration: "auto",
Expand Down
1 change: 0 additions & 1 deletion frontend/components/core/map/position-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import {
AnimatePresence,
motion,
useDeprecatedInvertedScale,
} from "framer-motion"

import PreviewCard from "@/components/core/map/preview-card"
Expand Down
Loading

0 comments on commit 81aadd0

Please sign in to comment.