@mytec: 2nd iteration implemented for tests

This commit is contained in:
2026-01-30 08:39:49 +02:00
parent e59eb59525
commit d7f1204e35
6 changed files with 342 additions and 50 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet.heat';
@@ -28,9 +28,39 @@ function rsrpToIntensity(rsrp: number): number {
return Math.max(0, Math.min(1, (rsrp - min) / (max - min)));
}
/**
* Calculate adaptive heatmap radius and blur based on zoom level.
* Lower zoom (zoomed out) = larger radius for smoother appearance.
* Higher zoom (zoomed in) = smaller radius to avoid blocky squares.
*
* Zoom 6 (country): radius=35, blur=18
* Zoom 10 (region): radius=25, blur=13
* Zoom 14 (city): radius=15, blur=8
* Zoom 18 (street): radius=8, blur=6
*/
function getHeatmapParams(zoom: number) {
const radius = Math.max(8, Math.min(40, 50 - zoom * 2.5));
const blur = Math.max(6, Math.min(20, 30 - zoom * 1.5));
return { radius: Math.round(radius), blur: Math.round(blur) };
}
export default function Heatmap({ points, visible }: HeatmapProps) {
const map = useMap();
const [mapZoom, setMapZoom] = useState(map.getZoom());
// Track zoom changes
useEffect(() => {
const handleZoomEnd = () => {
setMapZoom(map.getZoom());
};
map.on('zoomend', handleZoomEnd);
return () => {
map.off('zoomend', handleZoomEnd);
};
}, [map]);
// Recreate heatmap layer when points, visibility, or zoom changes
useEffect(() => {
if (!visible || points.length === 0) return;
@@ -40,9 +70,11 @@ export default function Heatmap({ points, visible }: HeatmapProps) {
rsrpToIntensity(p.rsrp),
]);
const { radius, blur } = getHeatmapParams(mapZoom);
const heatLayer = L.heatLayer(heatData, {
radius: 15,
blur: 20,
radius,
blur,
maxZoom: 17,
max: 1.0,
minOpacity: 0.3,
@@ -61,7 +93,7 @@ export default function Heatmap({ points, visible }: HeatmapProps) {
return () => {
map.removeLayer(heatLayer);
};
}, [map, points, visible]);
}, [map, points, visible, mapZoom]);
return null;
}

View File

@@ -4,6 +4,7 @@ import 'leaflet/dist/leaflet.css';
import type { Map as LeafletMap } from 'leaflet';
import type { Site } from '@/types/index.ts';
import { useSitesStore } from '@/store/sites.ts';
import { useSettingsStore } from '@/store/settings.ts';
import SiteMarker from './SiteMarker.tsx';
interface MapViewProps {
@@ -42,6 +43,8 @@ function MapRefSetter({ mapRef }: { mapRef: React.MutableRefObject<LeafletMap |
export default function MapView({ onMapClick, onEditSite, children }: MapViewProps) {
const sites = useSitesStore((s) => s.sites);
const isPlacingMode = useSitesStore((s) => s.isPlacingMode);
const showTerrain = useSettingsStore((s) => s.showTerrain);
const setShowTerrain = useSettingsStore((s) => s.setShowTerrain);
const mapRef = useRef<LeafletMap | null>(null);
const handleFitToSites = useCallback(() => {
@@ -62,10 +65,19 @@ export default function MapView({ onMapClick, onEditSite, children }: MapViewPro
className={`w-full h-full ${isPlacingMode ? 'cursor-crosshair' : ''}`}
>
<MapRefSetter mapRef={mapRef} />
{/* Base OSM layer */}
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* Terrain overlay (OpenTopoMap, semi-transparent when enabled) */}
{showTerrain && (
<TileLayer
attribution='Map data: &copy; OpenStreetMap, SRTM | Style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a>'
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
opacity={0.6}
/>
)}
<MapClickHandler onMapClick={onMapClick} />
{sites
.filter((s) => s.visible)
@@ -97,6 +109,16 @@ export default function MapView({ onMapClick, onEditSite, children }: MapViewPro
>
Reset
</button>
<button
onClick={() => setShowTerrain(!showTerrain)}
className={`bg-white dark:bg-dark-surface shadow-lg rounded px-3 py-2 text-sm
hover:bg-gray-50 dark:hover:bg-dark-border transition-colors
text-gray-700 dark:text-dark-text min-h-[36px]
${showTerrain ? 'ring-2 ring-blue-500' : ''}`}
title={showTerrain ? 'Hide terrain' : 'Show terrain elevation'}
>
Topo
</button>
</div>
</>
);