Regroupement de lignes non orientées

16

Je cherche un moyen efficace de regrouper les lignes indépendamment de leur direction. Cela signifie qu'une ligne entre New York et Los Angeles devrait être dans le même groupe qu'une ligne dans l'autre sens entre Los Angeles et New York. Les points de départ et d'arrivée devraient être similaires (c'est-à-dire que San Diego à Long Island devrait être dans le même groupe que LA-NY mais probablement pas San Francisco à Boston) et il n'y a pas de points intermédiaires. Les données d'entrée seraient similaires à cet exemple:

entrez la description de l'image ici (Par Cassiopeia sweet sur Wikipedia japonais GFDL ou CC-BY-SA-3.0 , via Wikimedia Commons)

J'ai déjà essayé de trier les lignes à l'avance, par exemple pour les faire toutes courir d'ouest en est, mais cela ne résout pas le problème des lignes allant du nord au sud et inversement.

Connaissez-vous un algorithme traitant ce problème? J'ai cherché mais en plus de l' algorithme pour calculer la direction moyenne des segments non dirigés, je n'ai rien trouvé d'utile à distance, donc je dois utiliser les mauvais termes de recherche.

obscur
la source
1
Je calculerais les coordonnées des deux extrémités et j'utiliserais STR (set ([x1, y1, x2, y2])) pour remplir le champ de chaîne. Vous pouvez résumer ce champ pour trouver des valeurs uniques
FelixIP

Réponses:

10

Si je vous comprends bien, vous voulez regrouper les lignes qui sont à peu près les mêmes sans égard à la direction.

Voici une idée qui, je pense, pourrait fonctionner.

  1. diviser les lignes en point de départ et point de fin

  2. Regroupez les points et obtenez l'ID du cluster

  3. Recherchez des lignes avec la même combinaison d'ID de cluster. Ce sont un cluster

Cela devrait être possible dans PostGIS (bien sûr :-)) version 2.3

Je n'ai pas testé la fonction ST_ClusterDBSCAN, mais elle devrait faire le travail.

Si vous avez un tableau de lignes comme celui-ci:

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

Et vous voulez créer le cluster où les points de départ et d'arrivée sont distants de 10 km maximum. Et il doit y avoir au moins 2 points pour être un cluster alors la requête pourrait être quelque chose comme:

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

En vous joignant à a.cluster_id<b.cluster_idvous obtenez un identifiant de cluster comparable indépendamment de la direction.

Nicklas Avén
la source
Merci Nicklas! J'aime cette approche car elle ne m'oblige pas à mélanger différentes unités (c.-à-d. Angles et distances) lors du regroupement.
underdark
5

Voulez-vous vraiment regrouper uniquement par direction, sans aucune considération d'origine ou de destination? Si oui, il existe des moyens très simples. Le plus simple est peut-être de calculer le relèvement de chaque ligne, de le doubler et de le tracer comme un point sur un cercle. Étant donné que les roulements avant-arrière diffèrent de 180 degrés, ils diffèrent de 360 ​​degrés après avoir doublé et tracent donc exactement au même endroit. Maintenant, regroupez les points dans le plan en utilisant la méthode que vous aimez.

Voici un exemple de travail dans R, avec sa sortie montrant les lignes colorées selon chacun des quatre groupes. Bien sûr, vous utiliseriez probablement un SIG pour calculer les roulements - j'ai utilisé les roulements euclidiens pour plus de simplicité.

Figure

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
la source
Je vous remercie! L'origine et la destination (O&D) sont également importantes. J'ai essayé de faire allusion à cela avec "les emplacements des points de début / fin devraient être similaires" mais je me fiche de savoir qui est O et qui est D. Pourtant, je pense que votre explication pourrait me rapprocher de la solution que je cherchais, si je peut comprendre comment mettre à l'échelle les valeurs des cercles unitaires aux coordonnées du point avant d'exécuter KMeans.
underdark
Je soupçonnais que vous pourriez avoir cela à l'esprit. C'est pourquoi j'ai suggéré de mapper les demi-directions sur une paire de coordonnées (points). Vous pouvez mettre à l'échelle ces points (pensez aux coordonnées polaires) par une deuxième variable et / ou introduire des coordonnées supplémentaires pour les origines ou les destinations. Sans connaître le but ultime du regroupement, il est difficile de fournir plus de conseils car les tailles relatives des coordonnées supplémentaires (par rapport aux coordonnées du cercle) détermineront les solutions de regroupement. Une autre solution consiste à exploiter la transformée de Hough .
whuber
4

Votre clarification de la question indique que vous souhaitez que le clustering soit basé sur les segments de ligne réels , dans le sens où deux paires origine-destination (OD) doivent être considérées comme "proches" lorsque les deux origines sont proches et les deux destinations sont proches , quel que soit le point considéré comme origine ou destination .

Cette formulation suggère que vous avez déjà une idée de la distance d entre deux points: il peut s'agir de la distance lorsque l'avion vole, de la distance sur la carte, du temps de trajet aller-retour ou de toute autre métrique qui ne change pas lorsque O et D sont commuté. La seule complication est que les segments n'ont pas de représentations uniques: ils correspondent à des paires non ordonnées {O, D} mais doivent être représentés comme des paires ordonnées , soit (O, D) ou (D, O). Nous pourrions donc prendre la distance entre deux paires ordonnées (O1, D1) et (O2, D2) pour être une combinaison symétrique des distances d (O1, O2) et d (D1, D2), telles que leur somme ou le carré racine de la somme de leurs carrés. Écrivons cette combinaison comme

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

Définissez simplement la distance entre les paires non ordonnées comme étant la plus petite des deux distances possibles:

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

À ce stade, vous pouvez appliquer n'importe quelle technique de clustering basée sur une matrice de distance.


À titre d'exemple, j'ai calculé les 190 distances point à point sur la carte pour 20 des villes américaines les plus peuplées et j'ai demandé huit grappes à l'aide d'une méthode hiérarchique. (Par souci de simplicité, j'ai utilisé des calculs de distance euclidienne et appliqué les méthodes par défaut dans le logiciel que j'utilisais: en pratique, vous voudrez choisir les distances et les méthodes de regroupement appropriées à votre problème). Voici la solution, avec des clusters indiqués par la couleur de chaque segment de ligne. (Les couleurs ont été assignées au hasard aux grappes.)

Figure

Voici le Rcode qui a produit cet exemple. Son entrée est un fichier texte avec les champs "Longitude" et "Latitude" pour les villes. (Pour étiqueter les villes sur la figure, il comprend également un champ "Clé".)

#
# 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
la source
Merci! Le calcul de distance par paire sera-t-il un problème pour les grands ensembles de données OD?
underdark
Oui, car avec n segments de ligne, il y a n (n-1) / 2 calculs de distance. Mais il n'y a pas de problème inhérent: tous les algorithmes de clustering doivent trouver des distances ou des différences entre les points (ou entre les points et les centres de cluster). Il s'agit d'un problème si courant que de nombreux algorithmes fonctionnent avec une fonction de distance personnalisée.
whuber