Fix Web Mercator distance distortion in stop clustering
Clustering was computing distances in EPSG:3857 (Web Mercator), which inflates distances by ~78% at Copenhagen's latitude (55.7°N). This made the 100m threshold actually merge stops within only 56m, and all reported distances were inflated. Now reprojects to EPSG:25832 (UTM 32N) for accurate meter distances, clusters there, converts centroids back to EPSG:3857 for plotting.
This commit is contained in:
@@ -169,8 +169,16 @@ def classify_station(r):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
DISTANCE_CRS = "EPSG:25832" # UTM 32N — accurate meters for Copenhagen
|
||||||
|
|
||||||
|
|
||||||
def cluster_stops(coords, threshold_m):
|
def cluster_stops(coords, threshold_m):
|
||||||
"""Cluster (x, y) coordinates within threshold_m; return list of centroids."""
|
"""Cluster (x, y) coordinates in EPSG:3857 within threshold_m (real meters).
|
||||||
|
|
||||||
|
Reprojects to UTM 32N for accurate distance computation, clusters, then
|
||||||
|
returns centroids in EPSG:3857 for plotting.
|
||||||
|
"""
|
||||||
|
from pyproj import Transformer
|
||||||
from scipy.cluster.hierarchy import fcluster, linkage
|
from scipy.cluster.hierarchy import fcluster, linkage
|
||||||
from scipy.spatial.distance import pdist
|
from scipy.spatial.distance import pdist
|
||||||
|
|
||||||
@@ -178,14 +186,21 @@ def cluster_stops(coords, threshold_m):
|
|||||||
if len(coords) <= 1:
|
if len(coords) <= 1:
|
||||||
return coords.tolist() if len(coords) else []
|
return coords.tolist() if len(coords) else []
|
||||||
|
|
||||||
dists = pdist(coords)
|
# reproject to UTM for accurate distances
|
||||||
|
to_utm = Transformer.from_crs(TARGET_CRS, DISTANCE_CRS, always_xy=True)
|
||||||
|
back = Transformer.from_crs(DISTANCE_CRS, TARGET_CRS, always_xy=True)
|
||||||
|
utm = np.array([to_utm.transform(x, y) for x, y in coords])
|
||||||
|
|
||||||
|
dists = pdist(utm)
|
||||||
links = linkage(dists, method="single")
|
links = linkage(dists, method="single")
|
||||||
labels = fcluster(links, t=threshold_m, criterion="distance")
|
labels = fcluster(links, t=threshold_m, criterion="distance")
|
||||||
|
|
||||||
centroids = []
|
centroids = []
|
||||||
for label in set(labels):
|
for label in set(labels):
|
||||||
members = coords[labels == label]
|
members = utm[labels == label]
|
||||||
centroids.append(members.mean(axis=0).tolist())
|
ux, uy = members.mean(axis=0)
|
||||||
|
cx, cy = back.transform(ux, uy)
|
||||||
|
centroids.append((cx, cy))
|
||||||
return centroids
|
return centroids
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user