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) {
List<Map<String, Object>> trackPoints = parseTrackPoints(activity.getTrackPointsJson());
@ -98,6 +99,10 @@ public class ActivityImageService {
return;
}
// Calculate cumulative distances along the track
double[] cumulativeDistances = calculateCumulativeDistances(trackPoints);
double totalDistance = cumulativeDistances[cumulativeDistances.length - 1];
// Find bounds
double minLat = Double.MAX_VALUE, maxLat = -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 scale = Math.min(scaleX, scaleY);
// Create path
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
// Draw track segments with privacy fade
g2d.setStroke(new BasicStroke(4.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(path);
// Draw start and end markers
if (!trackPoints.isEmpty()) {
Map<String, Object> firstPoint = trackPoints.get(0);
Map<String, Object> lastPoint = trackPoints.get(trackPoints.size() - 1);
final double FADE_DISTANCE = 300.0; // 300 meters fade zone
drawMarker(g2d, firstPoint, minLat, minLon, scale, trackHeight, new Color(0, 255, 0)); // Green start
drawMarker(g2d, lastPoint, minLat, minLon, scale, trackHeight, new Color(255, 0, 0)); // Red end
for (int i = 0; i < trackPoints.size() - 1; i++) {
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,25 +192,50 @@ 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,
double scale, int trackHeight, Color color) {
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;
private double[] calculateCumulativeDistances(List<Map<String, Object>> trackPoints) {
double[] distances = new double[trackPoints.size()];
distances[0] = 0.0;
g2d.setColor(color);
int markerSize = 12;
g2d.fillOval((int) x - markerSize / 2, (int) y - markerSize / 2, markerSize, markerSize);
for (int i = 1; i < trackPoints.size(); i++) {
Map<String, Object> point1 = trackPoints.get(i - 1);
Map<String, Object> point2 = trackPoints.get(i);
// White outline
g2d.setColor(Color.WHITE);
g2d.setStroke(new BasicStroke(2.0f));
g2d.drawOval((int) x - markerSize / 2, (int) y - markerSize / 2, markerSize, markerSize);
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 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;
}
/**

View file

@ -270,71 +270,15 @@ function createActivityMap(containerId, geoJsonData, options = {}) {
/**
* 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} geoJsonData - GeoJSON track data
*/
function addStartFinishMarkers(map, geoJsonData) {
if (!geoJsonData) {
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>');
// Privacy protection: Do not show start/end markers to hide athlete home locations
return;
}
/**