Come misurare la somiglianza degli oggetti SpatialLines


9

Ho creato due SpatialLinesoggetti in R: figura.

Questi oggetti sono stati creati in questo modo:

library(sp)
xy <- cbind(x,y)
xy.sp = sp::SpatialPoints(xy)
spl1 <- sp::SpatialLines(list(Lines(Line(xy.sp), ID="a")))

Ora voglio in qualche modo concludere che questa è la stessa linea ruotata e capovolta e che la differenza tra loro è uguale a 0 (cioè la forma è uguale).

Per fare ciò, è possibile utilizzare il maptoolspacchetto e ruotare la riga n. 1, ad esempio:

spl180 <- maptools::elide(spl1, rotate=180)

Ogni linea ruotata deve quindi essere controllata rispetto alla linea n. 2 usando il rgeospacchetto, ad esempio:

hdist <- rgeos::gDistance(spl180, spl2, byid=FALSE, hausdorff=TRUE)

Tuttavia, questo è un modo così computazionalmente costoso per abbinare gli SpatialLinesoggetti, specialmente se il numero di oggetti è circa 1000.

C'è un modo intelligente per fare questo lavoro?

PS Inoltre, l'approccio sopra descritto non garantisce tutte le possibili rotazioni e ribaltamenti.

P.S2. Se la linea n. 1 viene ingrandita rispetto alla linea n. 2, la differenza tra la linea n. 1 e n. 2 deve essere comunque uguale a 0.

AGGIORNARE:

inserisci qui la descrizione dell'immagine

Risposte:


9

Qualsiasi metodo efficace veramente generico standardizzerà le rappresentazioni delle forme in modo che non cambino in seguito a rotazione, traduzione, riflessione o cambiamenti banali nella rappresentazione interna.

Un modo per farlo è elencare ogni forma connessa come una sequenza alternata di lunghezze dei bordi e angoli (con segno), a partire da un'estremità. (La forma dovrebbe essere "pulita", nel senso di non avere bordi di lunghezza zero o angoli retti.) Per rendere questo invariante riflesso, annulla tutti gli angoli se il primo diverso da zero è negativo.

(Poiché qualsiasi polilinea connessa di n vertici avrà n -1 bordi separati da n -2 angoli, ho trovato conveniente nel Rcodice seguente utilizzare una struttura di dati composta da due matrici, una per le lunghezze dei bordi $lengthse l'altra per il angoli $angles. Un segmento di linea non avrà alcun angolo, quindi è importante gestire matrici di lunghezza zero in tale struttura di dati.)

Tali rappresentazioni possono essere ordinate lessicograficamente. Dovrebbero essere presi in considerazione alcuni errori in virgola mobile accumulati durante il processo di standardizzazione. Una procedura elegante stimerebbe quegli errori in funzione delle coordinate originali. Nella soluzione seguente, viene utilizzato un metodo più semplice in cui due lunghezze sono considerate uguali quando differiscono di una quantità molto piccola su base relativa. Gli angoli possono differire solo di una quantità molto piccola su base assoluta.

Per renderli invarianti sotto l'inversione dell'orientamento sottostante, scegliere la rappresentazione lessicograficamente più antica tra quella della polilinea e la sua inversione.

Per gestire polilinee in più parti, disporre i loro componenti in ordine lessicografico.

Per trovare le classi di equivalenza sotto le trasformazioni euclidee, quindi,

  • Crea rappresentazioni standardizzate delle forme.

  • Eseguire una sorta di lessicografia delle rappresentazioni standardizzate.

  • Fai un passaggio attraverso l'ordinamento ordinato per identificare sequenze di rappresentazioni uguali.

Il tempo di calcolo è proporzionale a O (n * log (n) * N) dove n è il numero di feature e N è il maggior numero di vertici in qualsiasi feature. Questo è efficiente.

Vale probabilmente la pena ricordare che un raggruppamento preliminare basato su proprietà geometriche invarianti facilmente calcolabili, come la lunghezza della polilinea, il centro e i momenti attorno a quel centro, spesso può essere applicato per semplificare l'intero processo. Basta trovare sottogruppi di caratteristiche congruenti all'interno di ciascuno di questi gruppi preliminari. Il metodo completo fornito qui sarebbe necessario per forme che altrimenti sarebbero così notevolmente simili che tali semplici invarianti non li distinguerebbero ancora. Funzionalità semplici basate su dati raster potrebbero avere tali caratteristiche, ad esempio. Tuttavia, poiché la soluzione qui fornita è comunque così efficiente, che se uno si impegnerà a implementarla, potrebbe funzionare perfettamente da solo.


Esempio

La figura a sinistra mostra cinque polilinee più altre 15 che sono state ottenute da quelle tramite traslazione, rotazione, riflessione e inversione casuali dell'orientamento interno (che non è visibile). La figura della mano destra li colora secondo la loro classe di equivalenza euclidea: tutte le figure di un colore comune sono congruenti; colori diversi non sono congruenti.

figura

Rsegue il codice. Quando gli input sono stati aggiornati a 500 forme, 500 forme extra (congruenti), con una media di 100 vertici per forma, il tempo di esecuzione su questa macchina era di 3 secondi.

Questo codice è incompleto: poiché Rnon ha un ordinamento lessicografico nativo e non avevo voglia di codificarne uno da zero, eseguo semplicemente l'ordinamento sulla prima coordinata di ogni forma standardizzata. Ciò andrà bene per le forme casuali create qui, ma per il lavoro di produzione dovrebbe essere implementato un tipo lessicografico completo. La funzione order.shapesarebbe l'unica interessata da questo cambiamento. Il suo input è un elenco di forme standardizzate se il suo output è la sequenza di indici sche lo ordinerebbe.

#
# Create random shapes.
#
n.shapes <- 5      # Unique shapes, up to congruence
n.shapes.new <- 15 # Additional congruent shapes to generate
p.mean <- 5        # Expected number of vertices per shape
set.seed(17)       # Create a reproducible starting point
shape.random <- function(n) matrix(rnorm(2*n), nrow=2, ncol=n)
shapes <- lapply(2+rpois(n.shapes, p.mean-2), shape.random)
#
# Randomly move them around.
#
move.random <- function(xy) {
  a <- runif(1, 0, 2*pi)
  reflection <- sign(runif(1, -1, 1))
  translation <- runif(2, -8, 8)
  m <- matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2) %*%
    matrix(c(reflection, 0, 0, 1), 2, 2)
  m <- m %*% xy + translation
  if (runif(1, -1, 0) < 0) m <- m[ ,dim(m)[2]:1]
  return (m)
}
i <- sample(length(shapes), n.shapes.new, replace=TRUE)
shapes <- c(shapes, lapply(i, function(j) move.random(shapes[[j]])))
#
# Plot the shapes.
#
range.shapes <- c(min(sapply(shapes, min)), max(sapply(shapes, max)))
palette(gray.colors(length(shapes)))
par(mfrow=c(1,2))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(shapes), function(i) lines(t(shapes[[i]]), col=i, lwd=2)))
#
# Standardize the shape description.
#
standardize <- function(xy) {
  n <- dim(xy)[2]
  vectors <- xy[ ,-1, drop=FALSE] - xy[ ,-n, drop=FALSE]
  lengths <- sqrt(colSums(vectors^2))
  if (which.min(lengths - rev(lengths))*2 < n) {
    lengths <- rev(lengths)
    vectors <- vectors[, (n-1):1]
  }
  if (n > 2) {
    vectors <- vectors / rbind(lengths, lengths)
    perps <- rbind(-vectors[2, ], vectors[1, ])
    angles <- sapply(1:(n-2), function(i) {
      cosine <- sum(vectors[, i+1] * vectors[, i])
      sine <- sum(perps[, i+1] * vectors[, i])
      atan2(sine, cosine)
    })
    i <- min(which(angles != 0))
    angles <- sign(angles[i]) * angles
  } else angles <- numeric(0)
  list(lengths=lengths, angles=angles)
}
shapes.std <- lapply(shapes, standardize)
#
# Sort lexicographically.  (Not implemented: see the text.)
#
order.shape <- function(s) {
  order(sapply(s, function(s) s$lengths[1]))
}
i <- order.shape(shapes.std)
#
# Group.
#
equal.shape <- function(s.0, s.1) {
  same.length <- function(a,b) abs(a-b) <= (a+b) * 1e-8
  same.angle <- function(a,b) min(abs(a-b), abs(a-b)-2*pi) < 1e-11
  r <- function(u) {
    a <- u$angles
    if (length(a) > 0) {
      a <- rev(u$angles)
      i <- min(which(a != 0))
      a <- sign(a[i]) * a
    }
    list(lengths=rev(u$lengths), angles=a)
  }
  e <- function(u, v) {
    if (length(u$lengths) != length(v$lengths)) return (FALSE)
    all(mapply(same.length, u$lengths, v$lengths)) &&
      all(mapply(same.angle, u$angles, v$angles))
    }
  e(s.0, s.1) || e(r(s.0), s.1)
}
g <- rep(1, length(shapes.std))
for (j in 2:length(i)) {
  i.0 <- i[j-1]
  i.1 <- i[j]
  if (equal.shape(shapes.std[[i.0]], shapes.std[[i.1]])) 
    g[j] <- g[j-1] else g[j] <- g[j-1]+1
}
palette(rainbow(max(g)))
plot(range.shapes, range.shapes, type="n",asp=1, bty="n", xlab="", ylab="")
invisible(lapply(1:length(i), function(j) lines(t(shapes[[i[j]]]), col=g[j], lwd=2)))

Quando si includono dilatazioni (o "isotità") arbitrarie nel gruppo di trasformazioni, le classi di equivalenza sono le classi di congruenza della geometria affine . Questa complicazione è facilmente gestibile: standardizzare tutte le polilinee per avere una lunghezza totale dell'unità, ad esempio.
whuber

Grazie mille. Solo una domanda: le forme devono essere rappresentate come SpatialLines o SpatialPolygons?
Klausos Klausos,

I poligoni creano un'altra complicazione: i loro confini non hanno endpoint definiti. Esistono molti modi per gestirlo, come standardizzare la rappresentazione per iniziare (diciamo) il vertice che ordina per primo nell'ordine lessicografico xy e procedendo in senso antiorario attorno al poligono. (Un poligono collegato topologicamente "pulito" avrà solo uno di questi vertici.) Il fatto che una forma sia considerata un poligono o una polilinea dipende dal tipo di caratteristica che rappresenta: non esiste un modo intrinseco per dire di un elenco chiuso di punti se è destinato ad essere una polilinea o un poligono.
whuber

Ci scusiamo per una semplice domanda, ma dovrei chiedergli di capire il tuo esempio. Il tuo oggetto shapes.std ha sia $ length che $ angles. Se, tuttavia, eseguo questo codice sui miei dati xy (ad es. [1,] 3093,5 -2987,8 [2,] 3072,7 -2991,0 ecc.), Non stima gli angoli, né disegna forme. Se eseguo trama (forme [[1]]), allora posso vedere chiaramente la mia polilinea. Quindi, come dovrei salvare le polilinee in R per poter testare il tuo codice sui miei dati?
Klausos Klausos,

Ho iniziato con la stessa struttura di dati che hai fatto: una matrice di coordinate (x, y). Le mie matrici mettono queste coordinate in colonne (come se tu avessi usato al rbind(x,y)posto di cbind(x,y)). Questo è tutto ciò che serve: la splibreria non viene utilizzata. Se volete seguire ciò che viene fatto in dettaglio, vi suggerisco di iniziare fuori con, diciamo, n.shapes <- 2, n.shapes.new <- 3, e p.mean <- 1. Quindi shapes, shapes.stdecc. Sono tutti abbastanza piccoli da essere facilmente ispezionabili. Il modo elegante e "giusto" per affrontare tutto ciò sarebbe quello di creare una classe di rappresentazioni di caratteristiche standardizzate.
whuber

1

Stai chiedendo molto con rotazione e dilatazione arbitrarie! Non sono sicuro di quanto utile sarebbe la distanza di Hausdorff, ma dai un'occhiata. Il mio approccio consisterebbe nel ridurre il numero di casi da verificare tramite dati economici. Ad esempio, è possibile saltare costosi confronti se la lunghezza delle due stringhe di linea non è un rapporto intero ( presupponendo il ridimensionamento intero / graduato ). Allo stesso modo è possibile verificare se l'area del riquadro di delimitazione o le relative aree dello scafo convesse sono in buone proporzioni. Sono sicuro che ci sono molti controlli economici che potresti fare contro il centroide, come distanze o angoli dall'inizio / fine.

Solo allora, se rilevi il ridimensionamento, annullalo ed esegui controlli davvero costosi.

Chiarimento: non conosco i pacchetti che stai usando. Per rapporto intero intendevo che dovresti dividere entrambe le distanze, controllare se il risultato è un numero intero, in caso contrario, invertire quel valore (potresti essere scelto nell'ordine sbagliato) e ricontrollare. Se ottieni un numero intero o sei abbastanza vicino, puoi dedurre che forse stava avvenendo il ridimensionamento. O potrebbe essere solo due forme diverse.

Per quanto riguarda il rettangolo di selezione, probabilmente hai i punti opposti del rettangolo che lo rappresenta, quindi estrarre l'area da loro è una semplice aritmetica. Il principio alla base del confronto dei rapporti è lo stesso, solo che il risultato sarebbe al quadrato. Non preoccuparti degli scafi convessi se non riesci a tirarli fuori da quel pacchetto R, era solo un'idea (probabilmente non abbastanza economica comunque).


Molte grazie. Potresti spiegare come rilevare se la lunghezza delle due stringhe di linea non è un rapporto intero? Inoltre, apprezzo molto se puoi dare un esempio di controllo "se l'area del riquadro di delimitazione o le aree dello scafo convesse sono in
buone

Ad esempio, se estraggo il riquadro di delimitazione spaziale dai dati spaziali, ricevo solo due punti: spl <- sp :: SpatialLines (list (Lines (Line (xy.sp), ID = i))) b <- bbox ( spl)
Klausos Klausos il

Esteso il post principale.
lynxlynxlynx,

"Se ottieni un numero intero o sei abbastanza vicino, puoi dedurre che forse stava avvenendo il ridimensionamento." Un utente non avrebbe potuto applicare una scala di 1,4 o giù di lì?
Germán Carrillo,

Certo, ma la mia ipotesi è stata chiarita, soprattutto con le modifiche successive. Stavo immaginando lo zoom in stile webmap, dove uno è ben limitato.
lynxlynxlynx,

1

Un buon metodo per confrontare queste polilinee sarebbe fare affidamento su una rappresentazione come una sequenza di (distanze, angoli di virata) su ciascun vertice: per una linea composta da punti P1, P2, ..., PN, tale sequenza sarebbe:

(distanza (P1P2), angolo (P1, P2, P3), distanza (P2P3), ..., angolo (P (N-2), P (N-1), PN), distanza (P (N-1 ) PN)).

Secondo le vostre esigenze, due linee sono uguali se e solo se le loro sequenze corrispondenti sono uguali (modulo l'ordine e la direzione dell'angolo). Il confronto di sequenze numeriche è banale.

Calcolando ogni sequenza di polilinea una sola volta e, come suggerito da Lynxlynxlynx, testando la somiglianza di sequenza solo per polilinea con le stesse caratteristiche banali (lunghezza, numero di vertici ...), il calcolo dovrebbe essere molto veloce!


Questa è l'idea giusta Affinché funzioni, tuttavia, è necessario affrontare molti dettagli, come la gestione delle riflessioni, l'orientamento interno, la possibilità di più componenti collegati e l'errore di arrotondamento in virgola mobile. Sono discussi nella soluzione che ho fornito.
whuber

Sì, ho solo descritto l'idea principale. La tua risposta è notevolmente più completa (come spesso :-)
luglio
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.