Lendo arquivo KML no R?

42

Estou trabalhando com enormes arquivos .kml (até 10 Gb) e preciso de uma maneira eficiente de lê-los em R. Até agora, tenho convertido-os em shapefiles via QGIS e depois voltado ao R com readShapePoly e readOGR (o último , a propósito, é ~ 1000 mais rápido que o anterior). Idealmente, gostaria de cortar o estágio intermediário do QGIS, pois é complicado e lento.

Como ler arquivos .kml diretamente?

I ver este pode também ser feito com readOGR . Infelizmente, não consigo ver como implementar o exemplo trabalhado (após uma longa preparação do arquivo .kml:) xx <- readOGR(paste(td, "cities.kml", sep="/"), "cities"). Parece que "cidades" aqui é o nome dos objetos espaciais.

Roger Bivand admite que "Como se descobre esse nome não é óbvio, pois o driver KML no OGR precisa dele para acessar o arquivo. Uma possibilidade é:

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

"

Mas isso também não funciona para mim. Aqui está um arquivo .kml de teste para experimentá-lo. Com ele no meu diretório de trabalho, readOGR("x.kml", "id")gera esta mensagem de erro:

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

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

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

, que eu simplesmente não entendo.

Iria getKMLcoordinates{MapTools} ser uma alternativa válida?

Eu também tentei isso:

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

As coordenadas são geradas corretamente, mas minha tentativa de convertê-las novamente em um objeto de polígono falhou com a seguinte mensagem:

Error in SpatialPolygons(tkml, proj4string = CRS("+init=epsg:3857")) : 
  cannot get a slot ("area") from an object of type "double"
RobinLovelace
fonte
11
Você pode obter as camadas no kml usando a função ogrListLayers do rgdal.
Mario Becerra

Respostas:

37

Para ler um KML com o driver OGR, forneça o nome do arquivo e o nome da camada.

O comentário de Roger é que o nome da camada está oculto no arquivo KML e, a menos que você saiba como o KML foi criado, não poderá deduzir o nome da camada no nome do arquivo KML.

Olhando para o seu exemplo de KML, posso ver:

<?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">

O que está me dizendo que o nome da camada é x, não ide assim:

> 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

funciona bem.

Agora, você pode tentar obter o nome ao analisar o KML como XML usando um analisador R XML, ou você pode talvez tentar lê-lo em R como um arquivo de texto até encontrar a tag nome.

A outra abordagem é executar o programa ogrinfo da linha de comando que cospe os nomes das camadas de um arquivo 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)

aqui mostrando que há uma camada de polígono chamada x.

Spacedman
fonte
Obrigado pela sua resposta Spaced - resolveu o problema imediatamente. É uma explicação clara como esta que me faz amar a troca de pilhas! Uma pergunta de "ponto de bônus": eu poderia usar o mesmo comando para ler um subconjunto dos dados (por exemplo, os primeiros 1 milhão de polígonos)? Caso contrário, procurará dividir os enormes kmls com um programa externo.
precisa saber é o seguinte
2
KML sendo XML não é realmente projetado para acesso aleatório. A solução real é colocar seus dados espaciais em um banco de dados espacial e ter alguns índices espaciais de velocidade. Confira o PostGIS.
precisa saber é o seguinte
OK, bom plano - eu disse ao cliente que o PostGIS é o caminho a seguir para esses grandes dados e estou convencido de que é a opção certa para o tipo de coisa que ele deseja fazer. Boa desculpa para eu aprender direito!
RobinLovelace
Há também a extensão espacial para o sqlite , um banco de dados baseado em arquivos, que não requer a instalação de um serviço e requer menos configuração que o PostGIS.
10242 Frank
estranhamente systemem R necessária path.expandem ~para ogrinfotrabalhar, mesmo que funcionou bem no caminho não expandidas na linha de comando (MacOS; Sys.which('ogrinfo')e which ogrinfovoltou os mesmos caminhos)
MichaelChirico
5

Se você deseja fazer a maneira alternativa usando o maptool, isso deve funcionar:

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"))

A chave aqui é que você precisa seguir algumas etapas para criar uma classe de polígono espacial.

Visto
fonte
oi @Seen, eu tentei sua abordagem, mas parece não funcionar? Estou com um erro: Erro no polígono (tkml): as cordas devem ser uma matriz de duas colunas> cabeça (tkml) [[1]] [1] -87.88141 30.49800 e tenho uma lista .. você acha que está ok? lista de coordenadas para matriz? tahnks!
maycca
1

Não sei se isso ainda é um problema para mais alguém, mas eu estava correndo em círculos por um tempo com isso. O que finalmente funcionou para mim está abaixo. Ele usa o XMLpacote para chegar ao xmlValuenó certo. Eu tive que definir o layerparâmetro de readOGRpara o nome de uma das pastas dentro do arquivo kml. Quando eu defino o layerparâmetro como o do arquivo kml, recebo o mesmo erro que RobinLovelace descrito acima.

Abaixo, são mostradas várias linhas de código que mostram apenas como ver os vários níveis de nó do documento kml. Eu acho que isso será um pouco diferente dependendo da fonte do kml. Mas você deve poder usar a mesma lógica para determinar o valor correto do parâmetro.

Além disso, eu criei uma lista de arquivos KML para que pudesse ser facilmente transformado em uma função que poderia ser colocado em um lapply- do.callpar. Isso poderia extrair dados de uma longa lista de arquivos kml. Ou muitas subpastas em um único arquivo kml, como parece, readOGRnão podem lidar com várias subpastas em um arquivo 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)
Argila
fonte
0

Não sei se eu deveria ter modificado minha resposta anterior. Talvez, mas isso cobre algumas coisas que não estão nesta resposta, então decidi deixá-lo.

De qualquer forma, o código abaixo funciona bem para mim. Ele procura por todos os xmlNodes no arquivo kml que são chamados de "Pasta" e, em seguida, define o layerparâmetro readOGRpara isso xmlValue. Testado no diretório de trabalho com cerca de 6 arquivos kml separados. Saída é uma lista de objetos SpatialDataFrames importados. Cada SpatialDataFrame pode ser facilmente subconjunto da lista.

Ainda não trata os arquivos kml com vários nós de pasta. Mas esse recurso pode ser facilmente adicionado com outra applyfunção aninhada .

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)
Argila
fonte