Moar federation

This commit is contained in:
Tim Zöller 2025-12-03 20:06:19 +01:00
parent fe1b5f8be0
commit 889c5336bf
2 changed files with 91 additions and 105 deletions

View file

@ -90,7 +90,8 @@ public class ActivityImageService {
} }
/** /**
* Draw the track outline from high-resolution track points. * Draw the track outline from high-resolution track points with privacy protection.
* Fades in/out the first and last 300 meters to hide start/end locations.
*/ */
private void drawTrack(Graphics2D g2d, Activity activity, int width, int height) { private void drawTrack(Graphics2D g2d, Activity activity, int width, int height) {
List<Map<String, Object>> trackPoints = parseTrackPoints(activity.getTrackPointsJson()); List<Map<String, Object>> trackPoints = parseTrackPoints(activity.getTrackPointsJson());
@ -98,6 +99,10 @@ public class ActivityImageService {
return; return;
} }
// Calculate cumulative distances along the track
double[] cumulativeDistances = calculateCumulativeDistances(trackPoints);
double totalDistance = cumulativeDistances[cumulativeDistances.length - 1];
// Find bounds // Find bounds
double minLat = Double.MAX_VALUE, maxLat = -Double.MAX_VALUE; double minLat = Double.MAX_VALUE, maxLat = -Double.MAX_VALUE;
double minLon = Double.MAX_VALUE, maxLon = -Double.MAX_VALUE; double minLon = Double.MAX_VALUE, maxLon = -Double.MAX_VALUE;
@ -129,38 +134,50 @@ public class ActivityImageService {
double scaleY = trackHeight / (maxLat - minLat); double scaleY = trackHeight / (maxLat - minLat);
double scale = Math.min(scaleX, scaleY); double scale = Math.min(scaleX, scaleY);
// Create path // Draw track segments with privacy fade
Path2D.Double path = new Path2D.Double();
boolean first = true;
for (Map<String, Object> point : trackPoints) {
Double lat = getDouble(point, "latitude");
Double lon = getDouble(point, "longitude");
if (lat != null && lon != null) {
double x = (lon - minLon) * scale;
double y = trackHeight - (lat - minLat) * scale; // Flip Y axis
if (first) {
path.moveTo(x, y);
first = false;
} else {
path.lineTo(x, y);
}
}
}
// Draw track
g2d.setColor(new Color(0, 180, 216)); // Bright blue
g2d.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2d.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(path);
// Draw start and end markers final double FADE_DISTANCE = 300.0; // 300 meters fade zone
if (!trackPoints.isEmpty()) {
Map<String, Object> firstPoint = trackPoints.get(0);
Map<String, Object> lastPoint = trackPoints.get(trackPoints.size() - 1);
drawMarker(g2d, firstPoint, minLat, minLon, scale, trackHeight, new Color(0, 255, 0)); // Green start for (int i = 0; i < trackPoints.size() - 1; i++) {
drawMarker(g2d, lastPoint, minLat, minLon, scale, trackHeight, new Color(255, 0, 0)); // Red end Map<String, Object> point1 = trackPoints.get(i);
Map<String, Object> point2 = trackPoints.get(i + 1);
Double lat1 = getDouble(point1, "latitude");
Double lon1 = getDouble(point1, "longitude");
Double lat2 = getDouble(point2, "latitude");
Double lon2 = getDouble(point2, "longitude");
if (lat1 != null && lon1 != null && lat2 != null && lon2 != null) {
double x1 = (lon1 - minLon) * scale;
double y1 = trackHeight - (lat1 - minLat) * scale;
double x2 = (lon2 - minLon) * scale;
double y2 = trackHeight - (lat2 - minLat) * scale;
// Calculate opacity based on distance from start/end
double distanceFromStart = cumulativeDistances[i];
double distanceFromEnd = totalDistance - cumulativeDistances[i];
// Calculate fade opacity (0.0 to 1.0)
float opacity = 1.0f;
if (distanceFromStart < FADE_DISTANCE) {
// Fade in from start
opacity = Math.min(opacity, (float) (distanceFromStart / FADE_DISTANCE));
}
if (distanceFromEnd < FADE_DISTANCE) {
// Fade out at end
opacity = Math.min(opacity, (float) (distanceFromEnd / FADE_DISTANCE));
}
// Apply opacity to track color
int alpha = Math.max(0, Math.min(255, (int) (opacity * 255)));
g2d.setColor(new Color(0, 180, 216, alpha)); // Bright blue with alpha
// Draw segment
g2d.drawLine((int) x1, (int) y1, (int) x2, (int) y2);
}
} }
} }
@ -175,27 +192,52 @@ public class ActivityImageService {
} }
/** /**
* Draw a circular marker at a track point. * Calculate cumulative distances along the track using Haversine formula.
* Returns an array where each element is the total distance from the start to that point.
*/ */
private void drawMarker(Graphics2D g2d, Map<String, Object> point, double minLat, double minLon, private double[] calculateCumulativeDistances(List<Map<String, Object>> trackPoints) {
double scale, int trackHeight, Color color) { double[] distances = new double[trackPoints.size()];
Double lat = getDouble(point, "latitude"); distances[0] = 0.0;
Double lon = getDouble(point, "longitude");
if (lat != null && lon != null) {
double x = (lon - minLon) * scale;
double y = trackHeight - (lat - minLat) * scale;
g2d.setColor(color); for (int i = 1; i < trackPoints.size(); i++) {
int markerSize = 12; Map<String, Object> point1 = trackPoints.get(i - 1);
g2d.fillOval((int) x - markerSize / 2, (int) y - markerSize / 2, markerSize, markerSize); Map<String, Object> point2 = trackPoints.get(i);
// White outline Double lat1 = getDouble(point1, "latitude");
g2d.setColor(Color.WHITE); Double lon1 = getDouble(point1, "longitude");
g2d.setStroke(new BasicStroke(2.0f)); Double lat2 = getDouble(point2, "latitude");
g2d.drawOval((int) x - markerSize / 2, (int) y - markerSize / 2, markerSize, markerSize); Double lon2 = getDouble(point2, "longitude");
if (lat1 != null && lon1 != null && lat2 != null && lon2 != null) {
double segmentDistance = haversineDistance(lat1, lon1, lat2, lon2);
distances[i] = distances[i - 1] + segmentDistance;
} else {
distances[i] = distances[i - 1];
} }
} }
return distances;
}
/**
* Calculate distance between two GPS coordinates using Haversine formula.
* Returns distance in meters.
*/
private double haversineDistance(double lat1, double lon1, double lat2, double lon2) {
final double EARTH_RADIUS = 6371000.0; // Earth radius in meters
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c;
}
/** /**
* Draw metadata overlay on the right side of the image. * Draw metadata overlay on the right side of the image.
*/ */

View file

@ -270,73 +270,17 @@ function createActivityMap(containerId, geoJsonData, options = {}) {
/** /**
* Add start and finish markers to the map * Add start and finish markers to the map
* PRIVACY: This function is deprecated and does nothing.
* Start/finish markers are no longer displayed to protect athlete privacy.
* *
* @param {Object} map - Leaflet map instance * @param {Object} map - Leaflet map instance
* @param {Object} geoJsonData - GeoJSON track data * @param {Object} geoJsonData - GeoJSON track data
*/ */
function addStartFinishMarkers(map, geoJsonData) { function addStartFinishMarkers(map, geoJsonData) {
if (!geoJsonData) { // Privacy protection: Do not show start/end markers to hide athlete home locations
return; return;
} }
let coordinates;
// Handle both LineString and FeatureCollection
if (geoJsonData.type === 'LineString') {
coordinates = geoJsonData.coordinates;
} else if (geoJsonData.type === 'Feature') {
coordinates = geoJsonData.geometry.coordinates;
} else if (geoJsonData.type === 'FeatureCollection' && geoJsonData.features && geoJsonData.features.length > 0) {
coordinates = geoJsonData.features[0].geometry.coordinates;
}
if (!coordinates || coordinates.length < 2) {
return;
}
// Start marker (green)
const startCoord = coordinates[0];
const startMarker = L.marker([startCoord[1], startCoord[0]], {
icon: L.divIcon({
className: 'start-finish-marker',
html: `<div style="
background-color: #10b981;
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
"></div>`,
iconSize: [24, 24],
iconAnchor: [12, 12]
}),
title: 'Start'
}).addTo(map);
startMarker.bindPopup('<strong>Start</strong>');
// Finish marker (red)
const finishCoord = coordinates[coordinates.length - 1];
const finishMarker = L.marker([finishCoord[1], finishCoord[0]], {
icon: L.divIcon({
className: 'start-finish-marker',
html: `<div style="
background-color: #ef4444;
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
"></div>`,
iconSize: [24, 24],
iconAnchor: [12, 12]
}),
title: 'Finish'
}).addTo(map);
finishMarker.bindPopup('<strong>Finish</strong>');
}
/** /**
* Create an elevation profile chart * Create an elevation profile chart
* *