// map.jsx — Stylized cockpit map. SVG-based road network. Theme-aware via CSS vars.
const MAP_BG = "var(--map-bg)";
const MAP_GRID = "var(--map-grid)";
const MAP_PARK = "var(--map-park)";
const MAP_PARK_STROKE = "var(--map-park-stroke)";
const MAP_RIVER = "var(--map-river)";
const MAP_ROAD_MINOR = "var(--map-road-minor)";
const MAP_ROAD_MAJOR_OUTER = "var(--map-road-major-outer)";
const MAP_ROAD_MAJOR_INNER = "var(--map-road-major-inner)";
// 嘉兴乍浦 (Zhapu Port, Jiaxing) — port city on Hangzhou Bay.
// Layout: Hangzhou Bay sea fills the south (y > ~620). Port piers jutting south.
// G15 Shen-Hai expressway runs roughly N–S (right side). 乍嘉苏 expressway diagonal.
// Inland canals (东湖、独山港河) cross the city. Pinghu 老城 in the upper-middle.
const ROADS_MAJOR = [
// G15 沈海高速 (N–S, right)
"M 1020 40 L 1020 200 L 1010 380 L 1000 540 L 1000 620",
// 乍嘉苏高速 (NW–SE diagonal)
"M 60 120 L 240 220 L 420 320 L 600 400 L 760 480 L 880 560",
// 海盐塘公路 (E–W arterial through old town)
"M 60 280 L 280 280 L 520 300 L 780 290 L 1140 280",
// 乍浦大道 (E–W, mid, leading to port)
"M 60 460 L 280 460 L 520 470 L 800 470 L 1140 470",
// 港区疏港路 (curves down to port)
"M 600 470 L 600 560 L 580 620",
"M 800 470 L 820 560 L 840 620",
// 外环 — connector
"M 240 220 L 240 460 L 260 600",
"M 880 200 L 880 400 L 880 560",
];
const ROADS_MINOR = [
// city grid (north of bay)
"M 80 160 L 1140 160","M 80 220 L 1140 220","M 80 360 L 1140 360","M 80 410 L 1140 410","M 80 540 L 980 540",
"M 160 60 L 160 600","M 320 60 L 320 600","M 400 60 L 400 600","M 520 60 L 520 600","M 680 60 L 680 600","M 760 60 L 760 600","M 880 60 L 880 600","M 940 60 L 940 600",
// port grid
"M 540 540 L 540 620","M 660 540 L 660 620","M 720 540 L 720 620","M 780 540 L 780 620",
];
// 杭州湾 + 内河水系
const RIVERS = [
// 东湖塘 (E–W canal in city)
"M 0 350 Q 200 340 380 360 T 760 350 Q 920 340 1240 360",
// 独山港河 / pier inlet
"M 460 470 L 470 540 L 480 600",
];
// 杭州湾 — fills bottom of map
const SEA_PATH = "M -20 620 L 1260 620 L 1260 820 L -20 820 Z";
// 港池 — port basins (water inlets cut into land)
const PORT_BASINS = [
"M 360 620 L 380 540 L 440 540 L 460 620 Z",
"M 600 620 L 620 580 L 700 580 L 720 620 Z",
"M 820 620 L 840 580 L 920 580 L 940 620 Z",
];
// 防波堤 / 码头 — piers extending into the sea
const PIERS = [
"M 480 620 L 480 700 L 540 700 L 540 620",
"M 740 620 L 740 720 L 800 720 L 800 620",
"M 960 620 L 960 680 L 1020 680 L 1020 620",
];
const PARKS = [
// 九龙山 / 南湾绿地
{ x: 220, y: 80, w: 90, h: 70, label: "九龙山" },
// 东湖公园
{ x: 440, y: 220, w: 80, h: 50, label: "东湖" },
// 临港绿带
{ x: 80, y: 500, w: 140, h: 90, label: "南湾绿带" },
];
const POIS = [
{ x: 320, y: 180, label: "总站·乍浦城区" },
{ x: 600, y: 240, label: "氢能补能站·东湖" },
{ x: 880, y: 320, label: "维保中心·G15" },
{ x: 700, y: 540, label: "调度中心·港区" },
{ x: 960, y: 540, label: "重卡停车场" },
{ x: 240, y: 540, label: "化工园补能站" },
];
// 12 vehicles around the city
// src: T = TBOX 3296/2016国标, J = JT808/1078, B = both (TBOX + JT)
// VEHICLES dataset comes from data/fleet.js (window.VEHICLES) — asset-management model.
// Source badge — small T/JT chip
const SourceBadge = ({ src, size = "sm" }) => {
const items = src === "B" ? ["T","JT"] : src === "T" ? ["T"] : ["JT"];
const colors = { T: "var(--info)", JT: "var(--accent)" };
return (
{items.map(k => (
{k}
))}
);
};
window.SourceBadge = SourceBadge;
// recent path traces (last 30 min ghost trails) for animation feel
const TRACES = [
{ id: "浙F07179F", d: "M 200 260 L 240 250 L 280 260", color: "ok" },
{ id: "浙F02002F", d: "M 540 260 L 580 256 L 620 260", color: "ok" },
{ id: "浙F08638F", d: "M 540 460 L 560 500 L 580 540", color: "danger" },
];
const StatusColor = {
ok: "var(--ok)",
warn: "var(--warn)",
danger: "var(--danger)",
idle: "var(--fg-3)",
};
const VehiclePin = ({ v, selected, onClick, showHeading = true, animate = true }) => {
const color = StatusColor[v.status];
return (
onClick && onClick(v)}>
{/* heading cone */}
{showHeading && v.status !== "idle" && (
)}
{/* pulse ring (only for moving) */}
{animate && v.status === "ok" && v.speed > 0 && (
)}
{/* outer halo */}
{/* core */}
{selected && }
);
};
const PoiMarker = ({ poi }) => (
{poi.label}
);
const Compass = () => (
N
);
const ScaleBar = ({ x = 60, y = 720 }) => (
500m
);
// the main rendered map
const FleetMap = ({
selectedId,
onSelect,
vehicles,
showLabels = false,
showHeatmap = false,
showPaths = true,
highlightPath = null, // for playback view: a polyline string
playbackProgress = 0,
playbackPoint = null,
variant = "default", // "default" | "minimal" | "satellite"
}) => {
const isMin = variant === "minimal";
// Default: pull from global fleet, only those with map coords
const _vehicles = vehicles || (window.VEHICLES || []).filter(v => v.x != null && v.y != null);
return (
);
};
window.FleetMap = FleetMap;