diff --git a/heatmap-tool/frontend/package-lock.json b/heatmap-tool/frontend/package-lock.json index 3998afcb..5b577c65 100644 --- a/heatmap-tool/frontend/package-lock.json +++ b/heatmap-tool/frontend/package-lock.json @@ -10,9 +10,11 @@ "dependencies": { "axios": "^1.13.4", "leaflet": "^1.9.4", + "leaflet.heat": "^0.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "react-leaflet": "^5.0.0" + "react-leaflet": "^5.0.0", + "react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -2848,6 +2850,11 @@ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "license": "BSD-2-Clause" }, + "node_modules/leaflet.heat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz", + "integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3171,6 +3178,20 @@ "react-dom": "^19.0.0" } }, + "node_modules/react-leaflet-heatmap-layer-v3": { + "version": "3.0.3-beta-1", + "resolved": "https://registry.npmjs.org/react-leaflet-heatmap-layer-v3/-/react-leaflet-heatmap-layer-v3-3.0.3-beta-1.tgz", + "integrity": "sha512-oNv6ul6JHxMIuBXqaGc5P/VM/P6bpiUYAeg1CMCmyeYxWkHfl2YiT4m8VdaJC4TjSxVoi/Ry2ChRTFjF5UsPsg==", + "license": "MIT", + "dependencies": { + "simpleheat": "^0.4.0" + }, + "peerDependencies": { + "leaflet": "^1.0.0", + "react": "^17.0.0", + "react-leaflet": "^3.0.0" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -3275,6 +3296,12 @@ "node": ">=8" } }, + "node_modules/simpleheat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/simpleheat/-/simpleheat-0.4.0.tgz", + "integrity": "sha512-tdg3I1NvzMdPKscWBrHbF0LBf+VWuBBazzGUPtFJjG5Q12VQX6gPY8jLy9hx5CbgAIVc5nfnePwJvAYK6z1rAA==", + "license": "BSD-2-Clause" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/heatmap-tool/frontend/package.json b/heatmap-tool/frontend/package.json index 766ce36b..d12f1c87 100644 --- a/heatmap-tool/frontend/package.json +++ b/heatmap-tool/frontend/package.json @@ -12,9 +12,11 @@ "dependencies": { "axios": "^1.13.4", "leaflet": "^1.9.4", + "leaflet.heat": "^0.2.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "react-leaflet": "^5.0.0" + "react-leaflet": "^5.0.0", + "react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/heatmap-tool/frontend/src/App.tsx b/heatmap-tool/frontend/src/App.tsx index 8b8b6678..a1ec5838 100644 --- a/heatmap-tool/frontend/src/App.tsx +++ b/heatmap-tool/frontend/src/App.tsx @@ -18,12 +18,15 @@ export interface HeatmapPoint { count: number; } +export type MapMode = 'points' | 'heatmap'; + function App() { const [filters, setFilters] = useState({}); const [heatmapData, setHeatmapData] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [radiusMultiplier, setRadiusMultiplier] = useState(1); + const [viewMode, setViewMode] = useState('points'); const handleUploadSuccess = (newFilters: FilterOptions) => { setFilters(newFilters); @@ -73,6 +76,16 @@ function App() { />

Map Settings

+
+ + +
setRadiusMultiplier(parseFloat(e.target.value))} style={{ width: '100%' }} + disabled={viewMode === 'heatmap'} />
{isLoading &&

Loading map data...

} {error &&

{error}

} - +
diff --git a/heatmap-tool/frontend/src/components/MapDisplay.tsx b/heatmap-tool/frontend/src/components/MapDisplay.tsx index 2f0e74ba..71a7c7d1 100644 --- a/heatmap-tool/frontend/src/components/MapDisplay.tsx +++ b/heatmap-tool/frontend/src/components/MapDisplay.tsx @@ -1,68 +1,81 @@ // src/components/MapDisplay.tsx import React from 'react'; import { MapContainer, TileLayer, CircleMarker, Tooltip } from 'react-leaflet'; +import { HeatmapLayer } from 'react-leaflet-heatmap-layer-v3'; import 'leaflet/dist/leaflet.css'; -import type { HeatmapPoint } from '../App'; +import 'leaflet.heat'; +import type { HeatmapPoint, MapMode } from '../App'; interface MapDisplayProps { heatmapData: HeatmapPoint[]; radiusMultiplier: number; + viewMode: MapMode; } -const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier }) => { +const MapDisplay: React.FC = ({ heatmapData, radiusMultiplier, viewMode }) => { const germanyCenter: [number, number] = [51.1657, 10.4515]; + const maxCount = Math.max(...heatmapData.map(p => p.count), 1); - // Simple scaling function for marker radius, now with a multiplier const calculateRadius = (count: number) => { - // Ensure a base radius so even single points are visible - // The multiplier is applied to the dynamic part of the radius return 3 + Math.log(count + 1) * 5 * radiusMultiplier; }; - // Simple color scaling function - const getColor = (count: number, maxCount: number) => { + const getColor = (count: number) => { const ratio = count / maxCount; if (ratio > 0.8) return '#d73027'; // Red if (ratio > 0.5) return '#fdae61'; // Orange if (ratio > 0.2) return '#fee08b'; // Yellow return '#66bd63'; // Green - } + }; - const maxCount = Math.max(...heatmapData.map(p => p.count), 1); + const renderPoints = () => ( + heatmapData.map((point, idx) => ( + + + PLZ: {point.plz}
+ Count: {point.count} +
+
+ )) + ); + const renderHeatmap = () => ( + p.lon} + latitudeExtractor={(p: HeatmapPoint) => p.lat} + intensityExtractor={(p: HeatmapPoint) => p.count} + radius={25} + blur={20} + max={maxCount * 0.1} // Adjust max intensity for better visualization + /> + ); if (heatmapData.length === 0) { - return ( -
-

No data to display on the map.

-

Upload a file and apply filters to see the heatmap.

-
- ); + return ( +
+

No data to display on the map.

+

Upload a file and apply filters to see the heatmap.

+
+ ); } return ( - + - {heatmapData.map((point, idx) => ( - - - PLZ: {point.plz}
- Count: {point.count} -
-
- ))} + {viewMode === 'points' ? renderPoints() : renderHeatmap()}
); };