One method to cluster would be using the K-Medoids method.
It allows to set the number of cluster k, distances are calculated to the centroids, which are always data points, but there is no such thing as a limit between points and/or centroids.
The scikit-learn-extra implementation supports the haversine metric, which you need for geospatial data.
Be aware; with such limits there might not exists a solution at all.
And if the centroids are given by the user, no clustering is need to be done at all. In that case you simply assign each point to nearest centroid.
The example below generates 5000 points, and assign clusters using K-Medoids with haversine as metric.
!pip install scikit-learn-extra
!pip install folium
import numpy as np
from sklearn_extra.cluster import KMedoids
random_gps = np.random.multivariate_normal([46.9, 7.4], [[2.0, 0.5], [0.5, 2.0]], size=5000)
kmedoids = KMedoids(metric="haversine", n_clusters=20, method='alternate')
clusters = kmedoids.fit(np.radians(random_gps) )
Now clusters.labels_ will contain the assignment of clusters, and clusters.medoid_indices gives the indici of the data points which are centroid/medoids.
You could visually inspect with e.g. folium;
import folium
colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred',
'lightred', 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']
map_osm = folium.Map(location=[np.mean(random_gps.T[0]), np.mean(random_gps.T[1])], zoom_start=6, prefer_canvas=False)
for point, cluster in zip(random_gps, clusters.labels_):
lat, long = point
folium.Circle(
location=[lat, long],
radius=2500,
color=colors[cluster % len(colors)],
fill=True,
fill_color=colors[cluster % len(colors)],
).add_to(map_osm)
for centroid in clusters.medoid_indices_:
lat, long = random_gps[centroid]
folium.Marker([lat, long]).add_to(map_osm)
map_osm
