Кластеризация ненаправленных линий

16

Я ищу эффективный способ кластеризации линий независимо от их направления. Это означает, что линия между Нью-Йорком и Лос-Анджелесом должна находиться в том же кластере, что и линия в другом направлении между Лос-Анджелесом и Нью-Йорком. Расположение начальной / конечной точек должно быть аналогичным (т.е. Сан-Диего и Лонг-Айленд должны находиться в том же кластере, что и Лос-Анджелес, но, вероятно, не Сан-Франциско-Бостон), и промежуточных точек не должно быть. Входные данные будут похожи на этот пример:

введите описание изображения здесь (Кассиопея сладкая в японской Википедии GFDL или CC-BY-SA-3.0 , через Викисклад)

Ранее я пытался заранее отсортировать линии, например, чтобы все они проходили с запада на восток, но это не решает проблему линий, идущих с севера на юг и наоборот.

Вы знаете какой-нибудь алгоритм, имеющий дело с этой проблемой? Я искал, но кроме Алгоритма для вычисления среднего направления ненаправленных сегментов, я не нашел ничего отдаленно полезного, поэтому я должен использовать неправильные условия поиска.

Подземье
источник
1
Я бы рассчитал координаты обоих концов и использовал бы STR (set ([x1, y1, x2, y2])) для заполнения строкового поля. Вы можете суммировать это поле, чтобы найти уникальные значения
FelixIP

Ответы:

10

Если я вас правильно понимаю, вы хотите сгруппировать линии, которые примерно одинаковы, независимо от направления.

Вот идея, которая, я думаю, могла бы сработать.

  1. разделить линии в начальной и конечной точках

  2. Кластер точек и получить идентификатор кластера

  3. Найти строки с одинаковой комбинацией идентификатора кластера. Это кластер

Это должно быть возможно в PostGIS (конечно :-)) версии 2.3

Я не тестировал функцию ST_ClusterDBSCAN, но она должна делать эту работу.

Если у вас есть таблица строк, как это:

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

И вы хотите создать кластер, где начальная и конечная точки находятся на расстоянии не более 10 км друг от друга. И для кластера должно быть как минимум 2 точки, тогда запрос может выглядеть примерно так:

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

Присоединяясь к a.cluster_id<b.cluster_idвам, вы получаете сопоставимый идентификатор кластера независимо от направления.

Никлас Авен
источник
Спасибо, Никлас! Мне нравится этот подход, потому что он не заставляет меня смешивать разные единицы (то есть углы и расстояния) при кластеризации.
Подземье
5

Вы действительно хотите кластеризоваться исключительно по направлению, без учета происхождения или назначения? Если это так, есть несколько очень простых способов. Возможно, самый простой - это вычислить направление каждой линии, удвоить его и построить в виде точки на окружности. Поскольку подшипники вперед-назад различаются на 180 градусов, они отличаются на 360 градусов после удвоения и, следовательно, располагаются в одном и том же месте. Теперь сгруппируйте точки на плоскости, используя любой метод, который вам нравится.

Вот рабочий пример Rс выводом линий, окрашенных в соответствии с каждым из четырех кластеров. Конечно, вы, вероятно, будете использовать ГИС для расчета подшипников - я использовал евклидовы подшипники для простоты.

фигура

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
источник
Спасибо! Происхождение и пункт назначения (O & D) также имеют значение. Пытался намекнуть на это: «места начала / конца должны быть одинаковыми», но мне все равно, какой O, а какой D. Тем не менее, я думаю, что ваше объяснение может приблизить меня к решению, которое я искал, если я можно выяснить, как масштабировать значения единичного круга до координат точки перед запуском KMeans.
Подземье
Я подозревал, что вы могли иметь это в виду. Вот почему я предложил сопоставить полуонаправления с парой координат (точек). Вы можете масштабировать эти точки (думать о полярных координатах) по второй переменной и / или вводить дополнительные координаты для исходных или конечных пунктов. Не зная конечной цели кластеризации, сложно дать больше советов, потому что относительные размеры дополнительных координат (по сравнению с координатами окружности) будут определять решения кластеризации. Другое решение - использовать преобразование Хафа .
whuber
4

Ваше разъяснение вопроса указывает на то, что вы хотите, чтобы кластеризация основывалась на фактических отрезках линии , в том смысле, что любые две пары отправления-назначения (OD) следует рассматривать как «близкие», когда оба источника близки, а оба получателя близки. , независимо от того, какой момент считается происхождения или назначения .

Эта формулировка предполагает, что у вас уже есть ощущение расстояния d между двумя точками: это может быть расстояние, когда самолет летит, расстояние на карте, время в пути туда и обратно или любая другая метрика, которая не изменяется, когда O и D переключился. Единственное осложнение состоит в том, что сегменты не имеют уникальных представлений: они соответствуют неупорядоченным парам {O, D}, но должны быть представлены как упорядоченные пары, (O, D) или (D, O). Поэтому мы можем принять расстояние между двумя упорядоченными парами (O1, D1) и (O2, D2) за некоторую симметричную комбинацию расстояний d (O1, O2) и d (D1, D2), например их сумму или квадрат корень суммы их квадратов. Давайте напишем эту комбинацию как

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

Просто определите расстояние между неупорядоченными парами, чтобы оно было меньше из двух возможных расстояний:

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

На данный момент вы можете применить любой метод кластеризации на основе матрицы расстояний.


В качестве примера я вычислил все 190 расстояний между двумя точками на карте для 20 самых густонаселенных городов США и запросил восемь кластеров, используя иерархический метод. (Для простоты я использовал евклидовы вычисления расстояний и применил методы по умолчанию в программном обеспечении, которое я использовал: на практике вы захотите выбрать подходящие расстояния и методы кластеризации для вашей задачи). Вот решение, с кластерами, обозначенными цветом каждого отрезка. (Цвета были случайным образом назначены кластерам.)

фигура

Вот Rкод, который произвел этот пример. Его ввод - текстовый файл с полями «Долгота» и «Широта» для городов. (Для обозначения городов на рисунке также имеется поле «Ключ».)

#
# 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
источник
Благодарность! Будет ли вычисление попарного расстояния проблемой для больших наборов данных OD?
Подземье
Да, потому что с n линейными сегментами есть n (n-1) / 2 вычисления расстояния. Но в этом нет внутренней проблемы: все алгоритмы кластеризации должны находить расстояния или различия между точками (или между точками и центрами кластеров). Это настолько распространенная проблема, что многие алгоритмы работают с пользовательской функцией расстояния.
whuber