feat([2fd88f42]): add marker clustering to points view

This commit is contained in:
2026-02-04 13:31:11 +00:00
parent 0f0d5a4f34
commit 46dbe0b3aa
4 changed files with 76 additions and 22 deletions

View File

@@ -14,11 +14,13 @@
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-leaflet-cluster": "^4.0.0",
"react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1" "react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@types/leaflet": "^1.9.21", "@types/leaflet": "^1.9.21",
"@types/leaflet.markercluster": "^1.5.6",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
@@ -1459,6 +1461,16 @@
"@types/geojson": "*" "@types/geojson": "*"
} }
}, },
"node_modules/@types/leaflet.markercluster": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.6.tgz",
"integrity": "sha512-I7hZjO2+isVXGYWzKxBp8PsCzAYCJBc29qBdFpquOCkS7zFDqUsUvkEOyQHedsk/Cy5tocQzf+Ndorm5W9YKTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/leaflet": "^1.9"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.10.10", "version": "24.10.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz",
@@ -2855,6 +2867,15 @@
"resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz", "resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz",
"integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ==" "integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ=="
}, },
"node_modules/leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
"license": "MIT",
"peerDependencies": {
"leaflet": "^1.3.1"
}
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3178,6 +3199,22 @@
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
} }
}, },
"node_modules/react-leaflet-cluster": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-leaflet-cluster/-/react-leaflet-cluster-4.0.0.tgz",
"integrity": "sha512-Lu75+KOu2ruGyAx8LoCQvlHuw+3CLLJQGEoSk01ymsDN/YnCiRV6ChkpsvaruVyYBPzUHwiskFw4Jo7WHj5qNw==",
"license": "SEE LICENSE IN <LICENSE>",
"dependencies": {
"leaflet.markercluster": "^1.5.3"
},
"peerDependencies": {
"@react-leaflet/core": "^3.0.0",
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-leaflet": "^5.0.0"
}
},
"node_modules/react-leaflet-heatmap-layer-v3": { "node_modules/react-leaflet-heatmap-layer-v3": {
"version": "3.0.3-beta-1", "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", "resolved": "https://registry.npmjs.org/react-leaflet-heatmap-layer-v3/-/react-leaflet-heatmap-layer-v3-3.0.3-beta-1.tgz",

View File

@@ -16,11 +16,13 @@
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-leaflet-cluster": "^4.0.0",
"react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1" "react-leaflet-heatmap-layer-v3": "^3.0.3-beta-1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@types/leaflet": "^1.9.21", "@types/leaflet": "^1.9.21",
"@types/leaflet.markercluster": "^1.5.6",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",

View File

@@ -1,6 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import axios from 'axios'; import axios from 'axios';
import './App.css'; import './App.css';
import 'react-leaflet-cluster/dist/assets/MarkerCluster.css';
import 'react-leaflet-cluster/dist/assets/MarkerCluster.Default.css';
import FileUpload from './components/FileUpload'; import FileUpload from './components/FileUpload';
import FilterPanel from './components/FilterPanel'; import FilterPanel from './components/FilterPanel';
import MapDisplay from './components/MapDisplay'; import MapDisplay from './components/MapDisplay';

View File

@@ -28,31 +28,44 @@ const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier,
return '#66bd63'; // Green return '#66bd63'; // Green
}; };
import MarkerClusterGroup from 'react-leaflet-cluster';
// ... (imports and interface definitions)
const MapDisplay: React.FC<MapDisplayProps> = ({ heatmapData, radiusMultiplier, viewMode }) => {
// ... (helper functions and state)
const renderPoints = () => ( const renderPoints = () => (
heatmapData.map((point, idx) => ( <MarkerClusterGroup>
<CircleMarker {heatmapData.map((point, idx) => (
key={idx} <CircleMarker
center={[point.lat, point.lon]} key={idx}
radius={calculateRadius(point.count)} center={[point.lat, point.lon]}
pathOptions={{ radius={calculateRadius(point.count)}
color: getColor(point.count), pathOptions={{
fillColor: getColor(point.count), color: getColor(point.count),
fillOpacity: 0.7 fillColor: getColor(point.count),
}} fillOpacity: 0.7
> }}
<Tooltip> >
PLZ: {point.plz} <br /> <Tooltip>
Count: {point.count} PLZ: {point.plz} <br />
{point.attributes_summary && Object.entries(point.attributes_summary).map(([attr, values]) => ( Count: {point.count}
<div key={attr}> {point.attributes_summary && Object.entries(point.attributes_summary).map(([attr, values]) => (
<strong>{attr}:</strong> {values.join(', ')} <div key={attr}>
</div> <strong>{attr}:</strong> {values.join(', ')}
))} </div>
</Tooltip> ))}
</CircleMarker> </Tooltip>
)) </CircleMarker>
))}
</MarkerClusterGroup>
); );
// ... (rest of the component)
};
const renderHeatmap = () => ( const renderHeatmap = () => (
<HeatmapLayer <HeatmapLayer
points={heatmapData} points={heatmapData}