Modernize stack (React 19, Vite, Node 24) and add map preview features

This commit is contained in:
(jenkins)
2026-03-16 19:46:08 +00:00
parent c90fc37d0d
commit abae851315
15 changed files with 8729 additions and 28 deletions
+91 -1
View File
@@ -7,6 +7,7 @@ import './styles/App.css';
const APP = () => {
const mapContainerRef = useRef(null);
const mapRef = useRef(null);
const startMarkerRef = useRef(null);
const [selectedPoint, setSelectedPoint] = useState({ lat: 51.5074, lon: -0.1278 });
const [direction, setDirection] = useState(45);
const [lineOfSightData, setLineOfSightData] = useState(null);
@@ -26,13 +27,47 @@ const APP = () => {
mapRef.current.on('load', () => {
console.log('Map loaded successfully');
// Initialize start marker
startMarkerRef.current = new maplibregl.Marker({ color: '#FF6B6B' })
.setLngLat([selectedPoint.lon, selectedPoint.lat])
.addTo(mapRef.current);
// Initialize preview line source and layer
mapRef.current.addSource('preview-line', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: []
}
}
});
mapRef.current.addLayer({
id: 'preview-line',
type: 'line',
source: 'preview-line',
paint: {
'line-color': '#FF6B6B',
'line-width': 2,
'line-dasharray': [2, 2]
}
});
updatePreviewLine(selectedPoint, direction);
});
mapRef.current.on('click', (e) => {
const { lng, lat } = e.lngLat;
setSelectedPoint({ lat, lon: lng });
// Clear previous line
if (startMarkerRef.current) {
startMarkerRef.current.setLngLat([lng, lat]);
}
// Clear previous final line when moving start point
if (mapRef.current.getSource('line-of-sight')) {
mapRef.current.removeLayer('line-of-sight');
mapRef.current.removeSource('line-of-sight');
@@ -44,6 +79,61 @@ const APP = () => {
};
}, []);
useEffect(() => {
// Update preview whenever point or direction changes
if (mapRef.current && mapRef.current.isStyleLoaded()) {
updatePreviewLine(selectedPoint, direction);
}
}, [selectedPoint, direction]);
const updatePreviewLine = (point, bearing) => {
const map = mapRef.current;
if (!map || !map.getSource('preview-line')) return;
// Generate 50 points along a 5000km great circle path for a smooth curve
const path = [];
const steps = 50;
const totalDistance = 5000;
for (let i = 0; i <= steps; i++) {
const dist = (totalDistance * i) / steps;
path.push(calculateDestination(point.lat, point.lon, bearing, dist));
}
map.getSource('preview-line').setData({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: path.map(p => [p.lon, p.lat])
}
});
};
// Helper to calculate destination point given start, bearing, and distance (km)
const calculateDestination = (lat, lon, bearing, distance) => {
const R = 6371; // Earth's radius in km
const brng = (bearing * Math.PI) / 180;
const φ1 = (lat * Math.PI) / 180;
const λ1 = (lon * Math.PI) / 180;
const δ = distance / R;
const φ2 = Math.asin(
Math.sin(φ1) * Math.cos(δ) +
Math.cos(φ1) * Math.sin(δ) * Math.cos(brng)
);
const λ2 =
λ1 +
Math.atan2(
Math.sin(brng) * Math.sin(δ) * Math.cos(φ1),
Math.cos(δ) - Math.sin(φ1) * Math.sin(φ2)
);
return {
lat: (φ2 * 180) / Math.PI,
lon: (((λ2 * 180) / Math.PI + 540) % 360) - 180
};
};
useEffect(() => {
// Update map style when toggle changes
if (mapRef.current) {
+1 -1
View File
@@ -1,6 +1,6 @@
import axios from 'axios';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api';
const api = axios.create({
baseURL: API_BASE_URL,