Skip to content

Commit ed6c4b0

Browse files
committed
Migrated Split Map (WIP)
1 parent cbd3d27 commit ed6c4b0

12 files changed

Lines changed: 10356 additions & 10 deletions

app/tools/split-map/page.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import SplitMapPage from "@/components/split-map-page";
2+
import "./styles.css";
3+
4+
export const metadata = {
5+
title: "Split Map | ZHU Controller Toolkit",
6+
description:
7+
"Generate and visualize ZHU enroute split configurations.",
8+
};
9+
10+
export default function SplitMapToolPage() {
11+
return <SplitMapPage />;
12+
}

app/tools/split-map/styles.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.split-map-label {
2+
cursor: pointer;
3+
background: rgba(0, 0, 0, 0.62);
4+
border: none;
5+
border-radius: 0.25rem;
6+
box-shadow: none;
7+
color: #ffffff;
8+
font-family: ui-monospace, monospace;
9+
font-size: 1.25rem;
10+
font-weight: 700;
11+
padding: 0.2rem 0.5rem;
12+
white-space: nowrap;
13+
}
14+
15+
.split-map-label::before {
16+
display: none;
17+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"use client";
2+
3+
import { useEffect, useMemo } from "react";
4+
import { MapContainer, Marker, Polygon, TileLayer, Tooltip, useMap } from "react-leaflet";
5+
import L from "leaflet";
6+
7+
const TILE_URL = "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png";
8+
9+
const SPECIALTY_COLORS = {
10+
AUS: "#f87171", CRP: "#fb923c", LCH: "#a3e635",
11+
LFK: "#a78bfa", NEW: "#fbbf24", OCN: "#60a5fa", RSG: "#e879f9",
12+
};
13+
const DIRECTION_COLORS = {
14+
N: "#38bdf8", S: "#fb923c", E: "#34d399", W: "#c084fc",
15+
};
16+
const EW_COLORS = {
17+
E: "#38bdf8", W: "#fb923c",
18+
};
19+
const STANDARD_LABELS = {
20+
specialty: [
21+
{ label: "83", position: [30.555, -96.972] },
22+
{ label: "87", position: [27.782, -97.491] },
23+
{ label: "43", position: [28.935, -93.794] },
24+
{ label: "38", position: [31.182, -92.864] },
25+
{ label: "24", position: [29.984, -89.889] },
26+
{ label: "53", position: [26.289, -90.800] },
27+
{ label: "50", position: [30.396, -100.248] },
28+
],
29+
direction: [
30+
{ label: "50", position: [30.696, -99.248] },
31+
{ label: "38", position: [31.182, -92.864] },
32+
{ label: "87", position: [28.638, -97.187] },
33+
{ label: "24", position: [28.212, -89.634] },
34+
],
35+
ew: [
36+
{ label: "50", position: [29.696, -98.648] },
37+
{ label: "46", position: [29.696, -91.634] },
38+
],
39+
};
40+
41+
function geoJsonRingToLatLngs(ring) {
42+
return ring.map(([lng, lat]) => [lat, lng]);
43+
}
44+
45+
function MapInvalidator() {
46+
const map = useMap();
47+
useEffect(() => {
48+
map.invalidateSize();
49+
}, [map]);
50+
return null;
51+
}
52+
53+
54+
function getStandardFill(feature, standardView) {
55+
const p = feature.properties;
56+
if (standardView === "specialty") return SPECIALTY_COLORS[p.category] ?? "#94a3b8";
57+
if (standardView === "direction") return DIRECTION_COLORS[p.direction] ?? "#94a3b8";
58+
if (standardView === "ew") return p.direction === "E" ? EW_COLORS.E : EW_COLORS.W;
59+
return "#94a3b8";
60+
}
61+
62+
export default function SplitMapExportView({ features, strata, mode, standardView, customColors, customLabels, mapView }) {
63+
const enrouteFeatures = useMemo(
64+
() => features.filter((f) => f.properties.strata === strata),
65+
[features, strata],
66+
);
67+
68+
const strataLabels = useMemo(
69+
() => Array.from(customLabels.entries()).filter(([, lbl]) => lbl.strata === strata),
70+
[customLabels, strata],
71+
);
72+
73+
return (
74+
<MapContainer
75+
center={mapView?.center ? [mapView.center.lat, mapView.center.lng] : [30.5, -97.5]}
76+
zoom={mapView?.zoom ?? 6}
77+
zoomControl={false}
78+
attributionControl={false}
79+
style={{ height: "100%", width: "100%", background: "#0b1220" }}
80+
>
81+
<TileLayer url={TILE_URL} crossOrigin={true} />
82+
<MapInvalidator />
83+
{enrouteFeatures.map((f) => {
84+
const name = f.properties.name;
85+
let pathOptions;
86+
if (mode === "standard") {
87+
pathOptions = { color: "#0f172a", weight: 2, opacity: 1, fillColor: getStandardFill(f, standardView), fillOpacity: 1 };
88+
} else {
89+
const colorHex = customColors.get(`${strata}-${name}`);
90+
pathOptions = colorHex
91+
? { color: "#0f172a", weight: 2, opacity: 1, fillColor: colorHex, fillOpacity: 1 }
92+
: { color: "#334155", weight: 2, opacity: 1, fillColor: "#151c2c", fillOpacity: 1 };
93+
}
94+
return (
95+
<Polygon
96+
key={name}
97+
positions={geoJsonRingToLatLngs(f.geometry.coordinates[0])}
98+
pathOptions={pathOptions}
99+
/>
100+
);
101+
})}
102+
{mode === "standard" && STANDARD_LABELS[standardView].map(({ label, position }) => (
103+
<Marker
104+
key={`stdlabel-${label}-${position[0]}`}
105+
position={position}
106+
opacity={0}
107+
icon={L.divIcon({ className: "", iconSize: [0, 0] })}
108+
interactive={false}
109+
>
110+
<Tooltip permanent direction="center" opacity={1} className="split-map-label" >
111+
<span style={{ fontFamily: "ui-monospace, monospace", fontSize: "1.25rem", fontWeight: 700, color: "#ffffff" }}>{label}</span>
112+
</Tooltip>
113+
</Marker>
114+
))}
115+
{mode === "custom" && strataLabels.map(([id, lbl]) => (
116+
<Marker
117+
key={id}
118+
position={lbl.position}
119+
opacity={0}
120+
icon={L.divIcon({ className: "", iconSize: [0, 0] })}
121+
interactive={false}
122+
>
123+
<Tooltip permanent direction="center" opacity={1} className="split-map-label">
124+
<span style={{ fontFamily: "ui-monospace, monospace", fontSize: "1.25rem", fontWeight: 700, color: "#ffffff" }}>{lbl.text}</span>
125+
</Tooltip>
126+
</Marker>
127+
))}
128+
</MapContainer>
129+
);
130+
}

0 commit comments

Comments
 (0)