Leggendo il file KML in R?


42

Sto lavorando con enormi file .kml (fino a 10 Gb) e ho bisogno di un modo efficiente per leggerli in R. Fino ad ora li ho convertiti in file di forma tramite QGIS e poi di nuovo in R con readShapePoly e readOGR (quest'ultimo , a proposito, è ~ 1000 più veloce del primo). Idealmente, vorrei tagliare la fase intermedia di QGIS in quanto ingombrante e lenta.

Come leggere i file .kml direttamente?

Io vedo questo può essere anche essere fatto con readOGR . Sfortunatamente, non riesco a vedere come implementare l'esempio funzionato (dopo una lunga preparazione del file .kml:) xx <- readOGR(paste(td, "cities.kml", sep="/"), "cities"). Sembra che "città" qui sia il nome degli oggetti spaziali.

Roger Bivand ammette che "Come si scopre questo nome non è ovvio, poiché il driver KML in OGR ne ha bisogno per accedere al file. Una possibilità è:

system(paste("ogrinfo", paste(td, "cities.kml", sep="/")), intern=TRUE)

"

Ma questo non funziona neanche per me. Ecco un file .kml di prova per provarlo. Con esso nella mia directory di lavoro, readOGR("x.kml", "id")genera questo messaggio di errore:

Error in ogrInfo(dsn = dsn, layer = layer, encoding = encoding, use_iconv = use_iconv) : 
  Cannot open layer . 

E system(paste("ogrinfo", "x.kml"), intern=TRUE)genera:

[1] "Had to open data source read-only."   "INFO: Open of `x.kml'"               
[3] "      using driver `KML' successful." "1: x (3D Polygon)"  

, che semplicemente non capisco.

Sarebbero getKMLcoordinates{} MapTools essere una valida alternativa?

Ho anche provato questo:

tkml <- getKMLcoordinates(kmlfile="x.kml", ignoreAltitude=T)
head(tkml[[1]])
tkml <- SpatialPolygons(tkml, 
                        proj4string=CRS("+init=epsg:3857"))

Le coordinate sono generate correttamente, ma il mio tentativo di riconvertirle in un oggetto poligono non è riuscito con il seguente messaggio:

Error in SpatialPolygons(tkml, proj4string = CRS("+init=epsg:3857")) : 
  cannot get a slot ("area") from an object of type "double"

1
Puoi ottenere i layer nel kml usando la funzione ogListLayers di rgdal.
Mario Becerra,

Risposte:


37

Per leggere un KML con il driver OGR, devi assegnargli il nome del file e il nome del layer.

Il commento di Roger è che il nome del livello è nascosto nel file KML e, a meno che non si sappia come è stato creato il KML, non è possibile dedurre il nome del livello dal nome del file KML.

Guardando il tuo esempio KML, posso vedere:

<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document><Folder><name>x</name>
<Schema name="x" id="x">

Il che mi sta dicendo che il nome del layer è x, no id, e quindi:

> foo = readOGR("/tmp/x.kml", "x")
OGR data source with driver: KML 
Source: "/tmp/x.kml", layer: "x"
with 1 features and 2 fields
Feature type: wkbPolygon with 2 dimensions

funziona bene.

Ora, si può cercare di ottenere il nome con l'analisi del file KML come XML utilizzando un parser XML R, oppure si può forse provare a leggerlo in R come file di testo fino a trovare il tag nome.

L'altro approccio consiste nell'eseguire il programma ogrinfo da riga di comando che sputa i nomi dei layer di un file KML:

$ ogrinfo /tmp/x.kml 
Had to open data source read-only.
INFO: Open of `/tmp/x.kml'
      using driver `KML' successful.
1: x (Polygon)

qui mostra che c'è uno strato poligonale chiamato x.


Grazie per la risposta Spaced: risolto il problema immediatamente. È una spiegazione chiara come questa che mi fa amare lo scambio di stack! Una domanda "bonus point": potrei usare lo stesso comando per leggere in un sottoinsieme dei dati (ad esempio il primo 1 milione di poligoni)? Altrimenti cercherà di dividere gli enormi kml con un programma esterno.
RobinLovelace,

2
KML essendo XML non è realmente progettato per l'accesso casuale. La vera soluzione è mettere i tuoi dati spaziali in un database spaziale e avere alcuni indici spaziali per la velocità. Dai un'occhiata a PostGIS.
Spacedman

OK, un buon piano - ho detto al cliente che PostGIS è la strada da percorrere per questi big data e sono convinto che sia l'opzione giusta per il tipo di cose che vuole fare. Una buona scusa per impararlo correttamente!
RobinLovelace,

Esiste anche l' estensione spaziale di sqlite , un database basato su file, che non richiederebbe l'installazione di un servizio e richiede meno configurazione di PostGIS.
Frank,

stranamente systemin R necessaria path.expandsu ~per ogrinfoil lavoro, anche se ha funzionato bene sul percorso non espanso sulla riga di comando (MacOS, Sys.which('ogrinfo')e which ogrinforestituiti gli stessi percorsi)
MichaelChirico

5

Se vuoi fare il modo alternativo usando maptool, questo dovrebbe funzionare:

tkml <- getKMLcoordinates(kmlfile="yourkml.kml", ignoreAltitude=T)
#make polygon
p1 = Polygon(tkml)
#make Polygon class
p2 = Polygons(list(p1), ID = "drivetime")
#make spatial polygons class
p3= SpatialPolygons(list(p2),proj4string=CRS("+init=epsg:4326"))

La chiave qui è che devi seguire un paio di passaggi per creare una classe poligonale spaziale.


ciao @Seen, ho provato il tuo approccio ma sembra non funzionare? Ho un errore: Errore nel poligono (tkml): coords deve essere una matrice a due colonne> head (tkml) [[1]] [1] -87.88141 30.49800 e ho un elenco ... pensi che sia ok convertire elenco di coordinate a matrice? tahnks!
Maycca,

1

Non so se questo è ancora un problema per chiunque altro, ma stavo correndo in cerchio per un po 'con questo. Quello che alla fine ha funzionato per me è al di sotto. Usa il XMLpacchetto per ottenere xmlValueil nodo giusto. Ho dovuto impostare il layerparametro readOGRsul nome di una delle cartelle all'interno del file kml. Quando imposto il layerparametro sul file kml, viene visualizzato lo stesso errore descritto in precedenza da RobinLovelace.

Di seguito sono mostrate molte righe di codice che mostrano solo come vedere i vari livelli di nodo del documento kml. Penso che questo sarà leggermente diverso a seconda della fonte del kml. Ma dovresti essere in grado di utilizzare la stessa logica per determinare il valore del parametro corretto.

Inoltre, ho creato un elenco di file KML in modo che possa essere facilmente trasformato in una funzione che potrebbe essere messo in un lapply- do.callcoppia. Questo potrebbe quindi estrarre un dato da un lungo elenco di file kml. Oppure, molte sottocartelle all'interno di un singolo file kml a quanto pare readOGRnon possono gestire più sottocartelle in un file kml.

library(rgdal); library(XML)

# SET WORKING DIRECTORY FIRST!!
dir <- getwd()

kmlfilelist <- list.files(dir, pattern =".kml$", full.names=TRUE, recursive=FALSE)

doc0 <- xmlTreeParse(kmlfilelist[2], useInternal = TRUE)
rootNode0 <- xmlRoot(doc0)
rootName0 <- xmlName(rootNode0)
element1Name0 <- names(rootNode0)

nodeNames <- names(rootNode0[1][[1]])

# entire rootNode - kml Document level
rootNode0[[1]]

# 1st element of rootNode - kml file name
rootNode0[[1]][[1]] 

# 2nd element of rootNode - kml Style Map 
rootNode0[[1]][[2]] 

# 3rd element of rootNode - Style
rootNode0[[1]][[3]]

# 4th element of rootNode - Style
rootNode0[[1]][[4]] 

# 5th element of rootNode - kml Folder with data in it.
rootNode0[[1]][[5]] 

# 5th element 1st subelement of rootNode - kml Folder name with data in it. 
#  What to set readOGR() layer parameter to.
rootNode0[[1]][[5]][[1]] 

kmlfoldername <- xmlValue(rootNode0[[1]][[5]][[1]]) # Folder name to set = layer.

readOGR(dsn=kmlfilelist[2], layer =  kmlfoldername)

0

Non so se avrei dovuto modificare la mia risposta precedente. Forse, ma questo riguarda alcune cose non contenute in questa risposta, quindi ho deciso di lasciarlo.

Comunque, il codice qui sotto funziona bene per me. Cerca tutti i xmlNodes nel file kml chiamati "Cartella" e quindi imposta il layerparametro su readOGRquello xmlValue. Testato su directory di lavoro con circa 6 file kml separati. L'output è un elenco di oggetti SpatialDataFrames importati. Ogni SpatialDataFrame può essere facilmente un sottoinsieme dall'elenco.

Non risolve ancora i file kml con più nodi Cartella. Ma quella funzione potrebbe essere facilmente aggiunta con un'altra applyfunzione nidificata .

library(rgdal); library(XML)

# SET WORKING DIRECTORY FIRST!!
dir <- getwd()

kmlfilelist <- list.files(dir, pattern =".kml$", full.names=TRUE, recursive=FALSE)

ImportKml <- function (kmlfile) {
  doc0 <- xmlTreeParse(kmlfile, useInternal = TRUE)
  rootNode0 <- xmlRoot(doc0)
  rootName0 <- xmlName(rootNode0)
  element1Name0 <- names(rootNode0)

  kmlNodeNames <- unname(names(rootNode0[1][[1]]))
  kmlFolderNodeNum <- which(kmlNodeNames == "Folder")
  kmlFolderNodeName <- xmlValue(rootNode0[[1]][[kmlFolderNodeNum]][[1]])

  kmlIn <- readOGR(dsn=kmlfile, layer = kmlFolderNodeName)
}
ImportedKmls <- lapply(kmlfilelist, ImportKml)
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.