From 40173c269f4eada44da75464f3b5e70669297caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Mon, 5 Jan 2026 14:40:08 +0100 Subject: [PATCH] Heatmap optimized --- .../fitpub/model/entity/UserHeatmapGrid.java | 2 +- .../fitpub/service/HeatmapGridService.java | 12 +-- src/main/resources/static/js/heatmap.js | 87 +++++++++++++++---- src/main/resources/templates/heatmap.html | 9 +- 4 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/operaton/fitpub/model/entity/UserHeatmapGrid.java b/src/main/java/org/operaton/fitpub/model/entity/UserHeatmapGrid.java index 3858c96..c8906ba 100644 --- a/src/main/java/org/operaton/fitpub/model/entity/UserHeatmapGrid.java +++ b/src/main/java/org/operaton/fitpub/model/entity/UserHeatmapGrid.java @@ -40,7 +40,7 @@ public class UserHeatmapGrid { /** * Center point of the grid cell. - * Grid cells are ~100m x 100m (0.001 degrees). + * Grid cells are ~10m x 10m (0.0001 degrees). */ @Column(name = "grid_cell", nullable = false, columnDefinition = "geometry(Point,4326)") private Point gridCell; diff --git a/src/main/java/org/operaton/fitpub/service/HeatmapGridService.java b/src/main/java/org/operaton/fitpub/service/HeatmapGridService.java index e928097..6133d2d 100644 --- a/src/main/java/org/operaton/fitpub/service/HeatmapGridService.java +++ b/src/main/java/org/operaton/fitpub/service/HeatmapGridService.java @@ -49,10 +49,11 @@ public class HeatmapGridService { } /** - * Grid resolution in degrees (~100m at equator). - * 0.001 degrees = ~111 meters + * Grid resolution in degrees (~10m at equator). + * 0.0001 degrees = ~11 meters + * Finer grid provides better granularity when zoomed in. */ - private static final double GRID_SIZE = 0.001; + private static final double GRID_SIZE = 0.0001; /** * SRID for WGS84 coordinate system. @@ -66,9 +67,10 @@ public class HeatmapGridService { /** * Sampling rate for large activities. - * Process every Nth point to avoid overwhelming the grid. + * Process every Nth point to balance detail vs performance. + * Lower value = more detail, higher processing time. */ - private static final int SAMPLING_RATE = 10; + private static final int SAMPLING_RATE = 2; /** * Update heatmap grid for a single activity. diff --git a/src/main/resources/static/js/heatmap.js b/src/main/resources/static/js/heatmap.js index 4814670..8d1822a 100644 --- a/src/main/resources/static/js/heatmap.js +++ b/src/main/resources/static/js/heatmap.js @@ -115,8 +115,11 @@ function renderHeatmap(data) { const lat = feature.geometry.coordinates[1]; const intensity = feature.properties.intensity; - // Normalize intensity to 0-1 range - const normalizedIntensity = Math.min(intensity / data.maxIntensity, 1.0); + // Use logarithmic scaling for better differentiation between low and high values + // log(1 + x) ensures that intensity=1 is still visible + const logMax = Math.log(1 + data.maxIntensity); + const logIntensity = Math.log(1 + intensity); + const normalizedIntensity = Math.min(logIntensity / logMax, 1.0); return [lat, lon, normalizedIntensity]; }); @@ -126,29 +129,81 @@ function renderHeatmap(data) { heatmapMap.removeLayer(heatLayer); } - // Create heat layer with red color scheme + // Get current zoom level for dynamic radius + const currentZoom = heatmapMap.getZoom(); + + // Calculate dynamic radius based on zoom level + // Higher zoom = smaller radius for more detail + // Lower zoom = larger radius for better visibility + const dynamicRadius = calculateDynamicRadius(currentZoom); + const dynamicBlur = Math.max(4, dynamicRadius * 0.4); // Reduced blur for sharper appearance + + // Create heat layer with red color scheme and improved gradient heatLayer = L.heatLayer(heatData, { - radius: 10, // Reduced for more detail - blur: 5, // Reduced for sharper appearance - maxZoom: 17, - max: 0.8, // Reduced from 1.0 to make colors more intense - minOpacity: 0.3, // Minimum opacity for better visibility + radius: dynamicRadius, + blur: dynamicBlur, + maxZoom: 18, + max: 0.75, // Increased to concentrate color at hotspots + minOpacity: 0.25, // Reduced for more transparency over streets gradient: { - 0.0: 'rgba(0, 0, 0, 0)', // Transparent for low values - 0.2: 'rgba(139, 0, 0, 0.5)', // Dark red with transparency - 0.4: 'rgba(178, 34, 34, 0.7)', // Firebrick red - 0.6: 'rgb(220, 20, 60)', // Crimson - 0.75: 'rgb(255, 69, 0)', // Red-orange - 0.85: 'rgb(255, 140, 0)', // Dark orange - 0.95: 'rgb(255, 215, 0)', // Gold - 1.0: 'rgb(255, 255, 0)' // Yellow (highest intensity) + 0.0: 'rgba(0, 0, 0, 0)', // Transparent + 0.1: 'rgba(0, 0, 139, 0.2)', // Dark blue (very low values) - more transparent + 0.2: 'rgba(0, 0, 255, 0.35)', // Blue (low values) - more transparent + 0.3: 'rgba(0, 128, 255, 0.45)', // Light blue - more transparent + 0.4: 'rgba(0, 255, 255, 0.55)', // Cyan - more transparent + 0.5: 'rgba(0, 255, 0, 0.6)', // Green - more transparent + 0.6: 'rgba(255, 255, 0, 0.65)', // Yellow - more transparent + 0.7: 'rgba(255, 165, 0, 0.7)', // Orange - more transparent + 0.85: 'rgba(255, 69, 0, 0.8)', // Red-orange + 1.0: 'rgba(255, 0, 0, 0.85)' // Red (highest intensity) - slightly transparent } }).addTo(heatmapMap); + // Update radius dynamically when zoom changes + heatmapMap.on('zoomend', function() { + if (heatLayer) { + const newZoom = heatmapMap.getZoom(); + const newRadius = calculateDynamicRadius(newZoom); + const newBlur = Math.max(4, newRadius * 0.4); // Reduced blur for sharper appearance + + // Update heat layer options + heatLayer.setOptions({ + radius: newRadius, + blur: newBlur + }); + } + }); + // Fit map bounds to heatmap data fitMapToBounds(data.features); } +/** + * Calculate dynamic radius based on zoom level. + * Higher zoom = smaller radius for more granular detail. + * Lower zoom = larger radius for better visibility. + * + * @param {number} zoom - Current map zoom level (0-18) + * @returns {number} Radius in pixels + */ +function calculateDynamicRadius(zoom) { + // Zoom levels: + // 2-8: World/continent view - large radius + // 9-12: City view - medium radius + // 13-15: Neighborhood view - small radius + // 16-18: Street view - very small radius + + if (zoom <= 8) { + return 25; // Large radius for world view + } else if (zoom <= 12) { + return 20 - (zoom - 8); // 20 -> 16 + } else if (zoom <= 15) { + return 16 - (zoom - 12) * 2; // 16 -> 10 + } else { + return Math.max(6, 10 - (zoom - 15) * 2); // 10 -> 6 (minimum) + } +} + /** * Fit map to show all heatmap data */ diff --git a/src/main/resources/templates/heatmap.html b/src/main/resources/templates/heatmap.html index 3bd9b9c..8e00e0e 100644 --- a/src/main/resources/templates/heatmap.html +++ b/src/main/resources/templates/heatmap.html @@ -121,10 +121,11 @@