Tengo un conjunto de 150 sitios a nivel mundial, y quiero mapearlos y colorearlos según una variable dada. Desafortunadamente, están distribuidos de manera muy no uniforme, con muchos de ellos en pequeños grupos de solo unos pocos metros. Esto significa que con marcadores de un tamaño razonable en un mapa, los marcadores se superponen mucho y muchos de ellos no se pueden ver. Me gustaría encontrar una manera de dispersar los marcadores (con líneas que los conecten a su posición original), para que cada marcador pueda ser visto sin superposición. La posición exacta no es súper importante aquí, pero sí la posición global aproximada.
Estoy utilizando python con basemap/matplotlib, y he probado un enfoque tipo fuerza dirigida que funciona más o menos, pero no muy bien. Aquí tienes un ejemplo:
Como se puede ver, los puntos europeos no se separan muy bien.
El código para esa dispersión es:
def spread_points(lat_lon, d=5, max_iter=300):
new_lat_lon = lat_lon.copy(deep=True)
dists = scipy.spatial.distance_matrix(new_lat_lon, new_lat_lon)
too_close = (0 < dists) & (dists < d)
itr = 0
while too_close.any() and itr < max_iter:
itr += 1
neighbour_means = []
for i in range(len(new_lat_lon)):
# para cada punto, encontrar todos los puntos "demasiado cerca" y tomar su promedio.
group_ind = too_close[i]
if not group_ind.any():
# no hay vecinos demasiado cercanos, usar el propio.
neighbour_means.append(new_lat_lon.iloc[i])
continue
group_ind[i] = True
neighbour_means.append(new_lat_lon[group_ind].mean())
max_diff = 0
for i in range(len(new_lat_lon)):
dir_grp_mean = (new_lat_lon.iloc[i] - neighbour_means[i])
grp_dist = norm(dir_grp_mean)
if grp_dist == 0:
dir_grp_unit = 0
grp_force = 0
else:
dir_grp_unit = dir_grp_mean / grp_dist
grp_force = np.sqrt(d / grp_dist)
# mover cada punto lejos del promedio y hacia su posición original y hacer temblar un poco
new_loc = new_lat_lon.iloc[i] \
+ grp_force * dir_grp_unit \
- (new_lat_lon.iloc[i] - lat_lon.iloc[i]) / d \
+ np.random.randn(2) * 0.01 * d
if not np.isfinite(new_loc).all():
import ipdb
ipdb.set_trace()
max_diff = max(max_diff, norm(new_loc - new_lat_lon.iloc[i]))
new_lat_lon.iloc[i] = new_loc
if max_diff < d / 100:
logger.info("Salida después de {i} iteraciones - las diferencias son pequeñas")
break
dists = scipy.spatial.distance_matrix(new_lat_lon, new_lat_lon)
too_close = (0 < dists) & (dists < d)
if itr == max_iter:
logger.info("alcanzó max_iter")
return new_lat_lon
Hay otro algoritmo en Inkscape llamado removeoverlaps
, que parece que podría funcionar, pero estoy teniendo dificultades para entender cómo funciona. Edición: Los resultados realmente no son ideales de todos modos, porque mueve los puntos horizontalmente primero y luego verticalmente, lo que resulta en un diseño extraño:
Sería mucho mejor si los puntos se dispersaran radialmente...
¿Hay otros algoritmos para este tipo de operación que ya existan o que serían fáciles de implementar en python?