Agrupando líneas no dirigidas

16

Estoy buscando una manera eficiente de agrupar líneas independientes de su dirección. Eso significa que una línea entre Nueva York y Los Ángeles debe estar en el mismo grupo que una línea en la otra dirección entre Los Ángeles y Nueva York. Las ubicaciones de los puntos de inicio / finalización deben ser similares (es decir, San Diego a Long Island deben estar en el mismo grupo que LA-NY pero probablemente no desde San Francisco a Boston) y no hay puntos intermedios. Los datos de entrada serían similares a este ejemplo:

ingrese la descripción de la imagen aquí (Por Cassiopeia sweet en Wikipedia japonesa GFDL o CC-BY-SA-3.0 , a través de Wikimedia Commons)

Anteriormente he tratado de ordenar las líneas por adelantado, por ejemplo, para que todas corran de oeste a este, pero esto no resuelve el problema de las líneas que van de norte a sur y viceversa.

¿Conoces algún algoritmo que aborde este problema? He estado buscando, pero además del algoritmo para calcular la dirección promedio de segmentos no dirigidos, no he encontrado nada remotamente útil, por lo que debo estar usando los términos de búsqueda incorrectos.

bajo oscuro
fuente
1
Calcularía las coordenadas de ambos extremos y usaría STR (set ([x1, y1, x2, y2])) para completar el campo de cadena. Puede resumir este campo para encontrar valores únicos
FelixIP

Respuestas:

10

Si te entiendo bien, quieres agrupar líneas que sean más o menos iguales sin importar la dirección.

Aquí hay una idea que creo que podría funcionar.

  1. dividir las líneas en el punto inicial y final

  2. Agrupe los puntos y obtenga la identificación del clúster

  3. Encuentre líneas con la misma combinación de ID de clúster. Esos son un racimo

Esto debería ser posible en PostGIS (por supuesto :-)) versión 2.3

No he probado la función ST_ClusterDBSCAN, pero debería hacer el trabajo.

Si tiene una tabla de líneas como esta:

CREATE TABLE the_lines
(
   geom geometry(linestring),
   id integer primary key
)

Y desea crear el clúster donde los puntos de inicio y finalización estén separados por un máximo de 10 km. Y debe haber al menos 2 puntos para ser un clúster, entonces la consulta podría ser algo como:

WITH point_id AS
   (SELECT (ST_DumpPoints(geom)).geom, id FROM the_lines),
point_clusters as
   (SELECT ST_ClusterDBSCAN(geom, 10000, 2) cluster_id, id line_id FROM point_id) 
SELECT array_agg(a.line_id), a.cluster_id, b.cluster_id 
FROM point_clusters a 
     INNER JOIN point_clusters b 
     ON a.line_id = b.line_id AND a.cluster_id < b.cluster_id
GROUP BY a.cluster_id, b.cluster_id

Al unirse con a.cluster_id<b.cluster_idusted obtiene una identificación de clúster comparable, independiente de la dirección.

Nicklas Avén
fuente
Gracias Nicklas! Me gusta este enfoque porque no me obliga a mezclar diferentes unidades (es decir, ángulos y distancias) mientras se agrupan.
oscuro
5

¿Realmente desea agruparse únicamente por dirección, sin tener en cuenta el origen o el destino? Si es así, hay algunas formas muy simples. Quizás lo más fácil es calcular la demora de cada línea, duplicarla y trazarla como un punto en un círculo. Dado que los rodamientos hacia adelante y hacia atrás difieren en 180 grados, difieren en 360 grados después de duplicarse y, por lo tanto, se trazan exactamente en el mismo lugar. Ahora agrupa los puntos en el plano usando cualquier método que desees.

Aquí hay un ejemplo de trabajo en R, con su salida que muestra las líneas coloreadas de acuerdo con cada uno de los cuatro grupos. Por supuesto, es probable que use un SIG para calcular los rodamientos: utilicé rodamientos euclidianos por simplicidad.

Figura

cluster.undirected <- function(x, ...) {
  #
  # Compute the bearing and double it.
  #
  theta <- atan2(x[, 4] - x[, 2], x[, 3] - x[, 1]) * 2
  #
  # Convert to a point on the unit circle.
  #
  z <- cbind(cos(theta), sin(theta))
  #
  # Cluster those points.
  #
  kmeans(z, ...)
}
#
# Create some data.
#
n <- 100
set.seed(17)
pts <- matrix(rnorm(4*n, c(-2,0,2,0), sd=1), ncol=4, byrow=TRUE)
colnames(pts) <- c("x.O", "y.O", "x.D", "y.D")
#
# Plot them.
#
plot(rbind(pts[1:n,1:2], pts[1:n,3:4]), pch=19, col="Gray", xlab="X", ylab="Y")
#
# Plot the clustering solution.
#
n.centers <- 4
s <- cluster.undirected(pts, centers=n.centers)
colors <- hsv(seq(1/6, 5/6, length.out=n.centers), 0.8, 0.6, 0.25)
invisible(sapply(1:n, function(i) 
  lines(pts[i, c(1,3)], pts[i, c(2,4)], col=colors[s$cluster[i]], lwd=2))
)
whuber
fuente
¡Gracias! El origen y el destino (O&D) también importan. Intenté insinuarlo con "las ubicaciones de los puntos de inicio / final deberían ser similares", pero no me importa cuál es O y cuál es D. Sin embargo, creo que su explicación podría llevarme más cerca de la solución que estaba buscando, si yo puede descubrir cómo escalar los valores del círculo unitario a las coordenadas del punto antes de ejecutar KMeans.
oscuro
Sospeché que podrías tener eso en mente. Es por eso que sugerí asignar las semi-direcciones a un par de coordenadas (puntos). Puede escalar esos puntos (piense en coordenadas polares) por una segunda variable y / o introducir coordenadas adicionales para orígenes o destinos. Sin conocer el propósito final de la agrupación, es difícil proporcionar más consejos porque los tamaños relativos de las coordenadas adicionales (en comparación con las coordenadas del círculo) determinarán las soluciones de agrupación. Otra solución es explotar la transformación de Hough .
whuber
4

Su aclaración de la pregunta indica que le gustaría que la agrupación se base en los segmentos de línea reales , en el sentido de que dos pares de origen-destino (OD) deben considerarse "cercanos" cuando ambos orígenes están cerca y ambos destinos están cerca , independientemente de qué punto se considere origen o destino .

Esta formulación sugiere que ya tiene una idea de la distancia d entre dos puntos: podría ser la distancia a medida que el avión vuela, la distancia en el mapa, el tiempo de viaje de ida y vuelta o cualquier otra métrica que no cambie cuando O y D son cambiado. La única complicación es que los segmentos no tienen representaciones únicas: corresponden a pares desordenados {O, D} pero deben representarse como pares ordenados , ya sea (O, D) o (D, O). Por lo tanto, podríamos tomar la distancia entre dos pares ordenados (O1, D1) y (O2, D2) como una combinación simétrica de las distancias d (O1, O2) yd (D1, D2), como su suma o el cuadrado raíz de la suma de sus cuadrados. Escribamos esta combinación como

distance((O1,D1), (O2,D2)) = f(d(O1,O2), d(D1,D2)).

Simplemente defina la distancia entre pares desordenados para que sea la menor de las dos distancias posibles:

distance({O1,D1}, {O2,D2}) = min(f(d(O1,O2)), d(D1,D2)), f(d(O1,D2), d(D1,O2))).

En este punto, puede aplicar cualquier técnica de agrupación basada en una matriz de distancia.


Como ejemplo, calculé las 190 distancias punto a punto en el mapa para 20 de las ciudades más pobladas de EE. UU. Y solicité ocho grupos utilizando un método jerárquico. (Para simplificar, utilicé los cálculos de distancia euclidiana y apliqué los métodos predeterminados en el software que estaba usando: en la práctica, querrá elegir distancias y métodos de agrupamiento adecuados para su problema). Aquí está la solución, con grupos indicados por el color de cada segmento de línea. (Los colores se asignaron aleatoriamente a los grupos).

Figura

Aquí está el Rcódigo que produjo este ejemplo. Su entrada es un archivo de texto con los campos "Longitud" y "Latitud" para las ciudades. (Para etiquetar las ciudades en la figura, también incluye un campo "Clave").

#
# Obtain an array of point pairs.
#
X <- read.csv("F:/Research/R/Projects/US_cities.txt", stringsAsFactors=FALSE)
pts <- cbind(X$Longitude, X$Latitude)

# -- This emulates arbitrary choices of origin and destination in each pair
XX <- t(combn(nrow(X), 2, function(i) c(pts[i[1],], pts[i[2],])))
k <- runif(nrow(XX)) < 1/2
XX <- rbind(XX[k, ], XX[!k, c(3,4,1,2)])
#
# Construct 4-D points for clustering.
# This is the combined array of O-D and D-O pairs, one per row.
#
Pairs <- rbind(XX, XX[, c(3,4,1,2)])
#
# Compute a distance matrix for the combined array.
#
D <- dist(Pairs)
#
# Select the smaller of each pair of possible distances and construct a new
# distance matrix for the original {O,D} pairs.
#
m <- attr(D, "Size")
delta <- matrix(NA, m, m)
delta[lower.tri(delta)] <- D
f <- matrix(NA, m/2, m/2)
block <- 1:(m/2)
f <- pmin(delta[block, block], delta[block+m/2, block])
D <- structure(f[lower.tri(f)], Size=nrow(f), Diag=FALSE, Upper=FALSE, 
               method="Euclidean", call=attr(D, "call"), class="dist")
#
# Cluster according to these distances.
#
H <- hclust(D)
n.groups <- 8
members <- cutree(H, k=2*n.groups)
#
# Display the clusters with colors.
#
plot(c(-131, -66), c(28, 44), xlab="Longitude", ylab="Latitude", type="n")
g <- max(members)
colors <- hsv(seq(1/6, 5/6, length.out=g), seq(1, 0.25, length.out=g), 0.6, 0.45)
colors <- colors[sample.int(g)]
invisible(sapply(1:nrow(Pairs), function(i) 
  lines(Pairs[i, c(1,3)], Pairs[i, c(2,4)], col=colors[members[i]], lwd=1))
)
#
# Show the points for reference
#
positions <- round(apply(t(pts) - colMeans(pts), 2, 
                         function(x) atan2(x[2], x[1])) / (pi/2)) %% 4
positions <- c(4, 3, 2, 1)[positions+1]
points(pts, pch=19, col="Gray", xlab="X", ylab="Y")
text(pts, labels=X$Key, pos=positions, cex=0.6)
whuber
fuente
¡Gracias! ¿El cálculo de distancia por pares será un problema para grandes conjuntos de datos OD?
oscuro
Sí, porque con n segmentos de línea hay n (n-1) / 2 cálculos de distancia. Pero no hay ningún problema inherente: todos los algoritmos de agrupación necesitan encontrar distancias o diferencias entre puntos (o entre puntos y centros de agrupación). Este es un problema tan común que muchos algoritmos funcionan con una función de distancia personalizada.
whuber