5.6. Leitura/Escrita de Dados Vetoriais
Nesta seção iremos discutir como realizar a leitura e escrita de dados geoespaciais representados na forma de uma coleção de feições (feature collection). Para isso, começaremos construindo um pequeno programa que irá abrir um arquivo de dados vetorial no formato ESRI Shapefile contendo os limites das Unidades da Federação do Brasil, referentes ao ano de 2018 (Figura 5.6). Portanto, faça o download do arquivo uf-2018.zip
, que contém esses dados.
O conjunto de dados no formato ESRI Shapefile é formado pelos seguintes componentes:
uf_2018.dbf
: o arquivo com extensão.dbf
contém os atributos alfanuméricos de cada feição geográfica.uf_2018.shp
: o arquivo com extensão.shp
contém os limites das Unidades Federativas, representados por geometrias do tipoMultiPolygon
.uf_2018.shx
: o arquivo com extensão.shx
contém um índice posicional para rápido acesso às geometrias do conjunto de dados.uf_2018.cpg
: o arquivo com extensão.cpg
contém a codificação de caracteres dos dados contidos no arquivo.dbf
. Neste caso, a codificação utilizada é aUTF-8
.uf_2018.prj
: o arquivo com extensão.prj
contém a descrição do sistema de referência espacial das coordenadas das geometrias. Neste caso, as coordenadas encontram-se na projeção geográfica LAT/LONG e no sistema geodésico de referência SIRGAS 2000.
Nota
Para maiores detalhes do formato ESRI Shapefile, consulte [87].
5.6.1. A Biblioteca Fiona
A biblioteca Fiona oferece funcionalidades para leitura e escrita de dados geoespaciais codificados nos mais diversos formatos. Ela oferece uma API nos mesmos moldes dos protocolos de Entrada e Saída (E/S) da API de arquivos da linguagem Python (Python IO).
5.6.1.1. Instalação
Para realizar a instalação da Fiona com o gerenciador de pacotes da Anaconda, ative o ambiente virtual no qual deseja instalar a biblioteca e, em seguida, instale o pacote com o comando conda install
, como mostrado abaixo:
conda install fiona
Para verificar se sua instalação foi realizada com sucesso, importe essa biblioteca através do seguinte comando:
In [1]: import fiona
Nota
Para verificar a versão instalada da sua biblioteca, utilize o seguinte comando:
In [5]: fiona.__version__
Out[5]: '1.8.18'
5.6.1.2. Leitura de Dados
O diagrama da Figura 5.7 apresenta um fluxograma geral das operações necessárias em um programa para realização da leitura de dados a partir de um arquivo qualquer. Em geral, o primeiro passo para leitura de dados consiste na utilização de um comando da linguagem que indique a abertura do arquivo, em modo leitura. Desta maneira, um identificador (ou variável) no programa é associado à estrutura que irá de fato manipular o arquivo no sistema operacional. Depois de aberto o arquivo, pode-se utilizar um ou mais comandos para recuperar os dados armazenados nesse arquivo. No caso de arquivos com uma estrutura bem definida, como é o caso do formato ESRI Shapefile, há a possibilidade de leitura dos metadados que descrevem a própria estrutura dos dados no arquivos, isto é, seu esquema. Por exemplo, os nomes dos atributos das feições das Unidades Federativas e os tipos de dados associados aos valores desses atributos podem ser recuperados. Por último, quando o arquivo não é mais necessário no programa, utiliza-se um comando para indicar o encerramento de seu uso (fechamento) e, consequentemente, a liberação de eventuais recursos associados a ele (como buffers de memória).
No caso da biblioteca Fiona, a abertura de um arquivo é realizada através da operação fiona.open
. Esta operação recebe como argumento o nome do arquivo a ser manipulado. O nome do arquivo pode incluir o seu caminho completo ou relativo. O Trecho de Código 5.6 mostra como realizar a abertura do arquivo uf_2018.shp
, localizado no diretório data/uf
, e, em seguida, escreve na saída padrão o número de feições (ou registros) contidos nessa coleção (27 feições).
1In [1]: with fiona.open("data/uf/uf_2018.shp", "r") as ufs:
2 ...: num_feicoes = len(ufs)
3 ...: print(f"Número de feições: {num_feicoes}.")
4 ...:
5Número de feições: 27.
O Trecho de Código 5.6 introduz também uma nova construção em Python, o uso do comando with
. O comando with
define um bloco de comandos que deva inicializar algum recurso no início da sua execução e, ao final da sua execução tenha que finalizar (ou liberar) esse recurso. Nesse caso, o recurso é o arquivo uf_2018.shp
que ficará associado ao identificador ufs
. Ao término do último comando do bloco with
, neste caso o comando print
na linha 03, o arquivo será automaticamente fechado, sem que seja necessário o uso explícito do comando ufs.close()
.
Para acessar cada uma das feições armazenadas no arquivo, pode ser utilizado um laço do tipo for
, como mostrado no Trecho de Código 5.7.
1In [1]: with fiona.open("data/uf/uf_2018.shp", "r") as ufs:
2 ...: for uf in ufs:
3 ...: print(uf["properties"])
4 ...:
5OrderedDict([('ID', 1), ('NOME', 'SERGIPE'), ('SIGLA', 'SE'), ('GEOCODIGO', '28'), ('REGIAO', 'NORDESTE'), ('REGIAO_SIG', 'NE')])
6OrderedDict([('ID', 2), ('NOME', 'MARANHÃO'), ('SIGLA', 'MA'), ('GEOCODIGO', '21'), ('REGIAO', 'NORDESTE'), ('REGIAO_SIG', 'NE')])
7...
8OrderedDict([('ID', 27), ('NOME', 'TOCANTINS'), ('SIGLA', 'TO'), ('GEOCODIGO', '17'), ('REGIAO', 'NORTE'), ('REGIAO_SIG', 'N')])
A linha 03 do Trecho de Código 5.7 escreve os nomes e valores dos atributos (ou propriedades) alfanuméricos de cada feição. Cada feição possui dois atributos especiais: properties
e geometry
. Utilizando a notação do operador[]
, podemos acessar tanto as propriedades alfanuméricas quanto a geométrica.
Nota
No Trecho de Código 5.6, para acessar apenas a primeira feição (ou registro) do arquivo, é possível utilizar a seguinte construção dentro do bloco de comandos with
:
uf = next(iter(ufs))
print(type(uf))
print(uf)
Vamos escrever um programa que compute o centróide para cada uma das geometrias das Unidades Federativas e, em seguida, escreva na saída padrão a localização do centróide e o nome da unidade. Como será necessária a realização de uma operação espacial para geração do centróide, devemos carregar a biblioteca Shapely, como indicado no Trecho de Código 5.8.
1In [1]: from shapely.geometry import shape
2
3In [2]: with fiona.open("data/uf/uf_2018.shp", "r") as ufs:
4 ...: for uf in ufs:
5 ...: geom = shape(uf["geometry"])
6 ...: centroide = geom.centroid
7 ...: nome = uf["properties"]["NOME"]
8 ...: print(f"Nome: {nome}; Localização: {centroide.wkt}")
9 ...:
10Nome: SERGIPE; Localização: POINT (-37.44374619643403 -10.58460970352795)
11Nome: MARANHÃO; Localização: POINT (-45.28788579867823 -5.072310251937679)
12...
13Nome: TOCANTINS; Localização: POINT (-48.32922962739212 -10.15031487315235)
Para finalizar, vamos discutir o código apresentado no Trecho de Código 5.9.
1In [1]: import fiona
2
3In [2]: from shapely.geometry import shape
4In [3]: from shapely.geometry import mapping
5
6In [4]: with fiona.open("data/uf/uf_2018.shp", "r") as ufs:
7 ...:
8 ...: # Número de feições
9 ...: num_feicoes = len(ufs)
10 ...:
11 ...: print(f"Número de feições: {num_feicoes}\n")
12 ...:
13 ...: # Sistema de Referência Espacial
14 ...: crs = ufs.crs
15 ...:
16 ...: print(f"CRS: {crs}\n")
17 ...:
18 ...: # Extensão da coleção de feições
19 ...: mbr = ufs.bounds
20 ...:
21 ...: print(f"xmin: {mbr[0]}, xmax: {mbr[2]}")
22 ...: print(f"ymin: {mbr[1]}, ymax: {mbr[3]}\n")
23 ...:
24 ...: # Esquema das feições
25 ...: schema = ufs.schema
26 ...:
27 ...: # Propriedades alfanuméricas
28 ...: propriedades = schema["properties"]
29 ...:
30 ...: for a, t in propriedades.items():
31 ...: print(f"Atributo: {a}, Tipo: {t}")
32 ...:
33 ...: # Propriedade geométrica
34 ...: tipo_geometrico = schema["geometry"]
35 ...:
36 ...: print(f"\nTipo do atributo geométrico: {tipo_geometrico}\n")
37 ...:
38 ...: # Acessando cada uma das Unidades Federativas (feições)
39 ...: for uf in ufs:
40 ...: # obtendo a geometria associada a feição
41 ...: geom = shape(uf["geometry"])
42 ...:
43 ...: # computando o centróide da geometria recuperada
44 ...: centroide = geom.centroid
45 ...:
46 ...: # obtendo o atributo NOME associado à feição
47 ...: nome = uf["properties"]["NOME"]
48 ...:
49 ...: print(f"Nome: {nome}, Localização: {centroide.wkt}")
50 ...:
51Número de feições: 27
52
53CRS: {'init': 'epsg:4674'}
54
55xmin: -73.99044996899994, xmax: -28.847639913999956
56ymin: -33.75117799399993, ymax: 5.271841077000033
57
58Atributo: ID, Tipo: int:11
59Atributo: NOME, Tipo: str:50
60...
61Atributo: REGIAO_SIG, Tipo: str:2
62
63Tipo do atributo geométrico: Polygon
64
65Nome: SERGIPE, Localização: POINT (-37.44374619643403 -10.58460970352795)
66Nome: MARANHÃO, Localização: POINT (-45.28788579867823 -5.072310251937679)
67...
68Nome: TOCANTINS, Localização: POINT (-48.32922962739212 -10.15031487315235)
5.6.1.3. Escrita de Dados
O objetivo dessa seção é criar um pequeno programa que leia as feições das Unidades Federativas e, para cada feição desse arquivo calcule o seu centróide, gravando em um arquivo de saída denominado uf_centroides.shp
, no mesmo diretório do dado de entrada. Para criar este novo arquivo com a biblioteca Fiona, será preciso definir:
A estrutura dos dados, isto é, o esquema da coleção de feições, que inclui o nome das propriedades (ou atributos), tipos de dados das propriedades e o tipo geométrico.
O sistema de referência espacial dos dados (
CRS
).O tipo do driver que será usado para escrita dos dados.
A coleção de saída, uf_centroides.shp
, terá duas propriedades alfanuméricas: (1) o identificador, que será um valor do tipo inteiro e (2) o nome da Unidade Federativa, uma string. O tipo geométrico será Point
. O CRS
será o mesmo das geometrias do dado de entrada, EPSG:4674
. Como o objetivo é criar um novo arquivo ESRI Shapefile, usaremos o driver que possui este nome.
Vamos começar criando um dicionário com o esquema da coleção:
In [1]: schema_centroides = {
...: "geometry": "Point",
...: "properties": [
...: ("ID", "int"),
...: ("NOME", "str:50")
...: ]
...: }
Aviso
A chave properties
pode ser uma lista ou um OrderedDict
. O importante é utilizar uma estrutura que garanta a ordem dos elementos. Lembre-se que o dict
pode não garantir isso dependendo das versões da linguagem Python.
Em seguida, vamos utilizar o comando fiona.open
duas vezes. A primeira, para criar o novo arquivo, chamado uf_centroides.shp
no diretório data/uf
, com o esquema definido acima (schema_centroides
), o driver ESRI Shapefile
, e o CRS EPSG:4674
. E o segundo uso do comando fiona.open
irá abrir o arquivo uf_2018.shp
para leitura.
In [1]: from collections import OrderedDict
In [1]: import fiona
In [2]: from shapely.geometry import shape
In [3]: from shapely.geometry import mapping
In [4]: with fiona.open("data/uf/uf_centroides.shp", "w",
...: driver="ESRI Shapefile",
...: crs="EPSG:4674",
...: schema=schema_centroides) as centroides, \
...: fiona.open("data/uf/uf_2018.shp", "r") as ufs:
...:
...: for i, uf in enumerate(ufs):
...: geom = shape(uf["geometry"])
...: centroide = geom.centroid
...:
...: nome = uf["properties"]["NOME"]
...:
...: feature = dict(
...: type="Feature",
...: id=i,
...: properties=OrderedDict (
...: ID=uf["properties"]["ID"],
...: NOME=uf["properties"]["NOME"]
...: ),
...: geometry=mapping(centroide)
...: )
...:
...: centroides.write(feature)
...:
5.6.1.4. Acessando Arquivos via HTTP/HTTPS
A Fiona oferece a possibilidade de acessar arquivos ESRI Shapefile através dos protocolos HTTP ou HTTPS. O Trecho de Código 5.10 mostra como ler as amostras contidas no arquivo bdc_paper_samples.shp
disponível na seguinte URL: https://brazildatacube.dpi.inpe.br/geo-knowledge-hub/bdc-article/training-samples/shp/bdc_paper_samples.shp.
1In [1]: url = "https://brazildatacube.dpi.inpe.br/geo-knowledge-hub/bdc-article/training-samples/shp/bdc_paper_samples.shp"
2
3In [2]: with fiona.open(url, "r") as samples:
4 ...: for s in samples:
5 ...: print(s)
6 ...:
5.6.2. A biblioteca GDAL/OGR
A parte da biblioteca GDAL
voltada para manipulação de dados vetoriais é conhecida por OGR
. A Figura 5.8 apresenta as principais classes que compõem esta API.
A classe DataSource
representa uma coleção de camadas de informação ou layers. Um objeto DataSource
pode ser usado para acessar um único arquivo, um conjunto de arquivos, um conjunto de tabelas de um banco de dados, ou coleções de feições em um serviço Web do tipo OGC Web Feature Service (WFS).
A classe Layer
representa uma camada de informação contida em um DataSource
. Essa classe contém operações que possibilitam a leitura e escrita de feições (features), isto é, um objeto geográfico. Neste documento também usaremos o termo coleção de feições (ou feature collection) quando nos referirmos a uma camada de informação.
Todas as feições de uma mesma camada possuem a mesma estrutura, isto é, o mesmo conjunto de atributos. Denominamos esta estrutura da camada de informação de esquema. A classe FeatureDefn
é usada para descrever o esquema de uma camada de informação, isto é, ela fornece uma lista com o nome dos atributos e seus respectivos tipos de dados. As feições pertencentes a uma mesma camada de informação irão compartilhar esta estrutura.
A classe Feature
representa os dados de uma feição, que podem ser valores alfanuméricos e geométricos.
Para entender melhor esta estrutura (Figura 5.8), vamos considerar o arquivo uf_2018.shp
(Figura 5.6) introduzido no início dessa seção (Seção 5.6). Um objeto do tipo DataSource
(fonte de dados) irá representar esse conjunto de arquivos. A partir dele, teremos acesso ao conjunto de feições (municípios) através de um objeto do tipo Layer
(camada de informação). Esse objeto por sua vez irá possibilitar acessar os dados de cada município, através de objetos do tipo Feature
(feição geográfica). A partir de um objeto do tipo Feature
podemos obter tanto os atributos alfanuméricos, como o código e nome da Unidade Federativa, e a geometria usada para representar seus limites (coleções de polígonos).
Ainda completam o diagrama da Figura 5.8, a classe FieldDefn
, que representa as informações de um dado atributo do esquema da camada de informação; e, a classe Geometry
, que representa os tipos geométricos suportados pela OGR
, como pontos, linhas e polígonos, e as coleções homogêneas desses elementos geométricos.
5.6.2.1. Instalando a biblioteca GDAL/OGR
Para instalar a GDAL/OGR
, ative seu ambiente de trabalho da Anaconda e instale o pacote através do conda
:
conda install gdal=3
Nota
No meu ambiente, foi instalada a versão 3.1.4
.
5.6.2.2. Carregando a Biblioteca GDAL/OGR
Uma boa prática ao trabalhar com bibliotecas que não fazem parte da distribuição padrão do Python consiste na verificação de sua carga logo após as instruções import
. O trecho de código abaixo mostra como realizar essa verificação logo no início do seu script, para certificar que a biblioteca GDAL
tenha sido devidamente carregada antes de prosseguir com as próximas instruções do programa. Neste script, utilizamos um bloco try-except
para verificar se o módulo osgeo
e as APIs ogr
e gdal
foram devidamente carregados (linhas 3-6
). Na linha 9
utilizamos a função VersionInfo
da API GDAL
para obter uma string com a versão e data de build da GDAL
.
1import sys
2
3try:
4 from osgeo import gdal, ogr
5except:
6 sys.exit('ERRO: módulo GDAL/OGR não encontrado!')
7
8# versão da biblioteca GDAL/OGR
9print(gdal.VersionInfo('VERSION'))
5.6.2.3. Leitura de Dados
Vamos abrir o arquivo uf_2018.shp
:
shp = ogr.Open('data/uf/uf_2018.shp')
Veja que a operação ogr.Open
cria um objeto chamado shp
da classe DataSource
:
type(shp)
A partir de um objeto do tipo DataSource
podemos recuperar a camada de informação contida nessa fonte atravé do método GetLayer
:
layer = shp.GetLayer('uf_2018')
type(layer)
De um objeto do tipo Layer
podemos recuperar algumas informações básicas, como mostrado abaixo:
nome_layer = layer.GetName()
print(f'Layer: {nome_layer}')
bbox = layer.GetExtent()
print(f'\tExtensão.......: {bbox}')
crs = layer.GetSpatialRef().ExportToWkt()
print(f'\tSRS............: {crs}')
tipo_geometrias = layer.GetGeomType()
print(f'\tTipo Geométrico: {tipo_geometrias}')
print('\tPolígonos? ', tipo_geometrias == ogr.wkbMultiPolygon)
num_features = layer.GetFeatureCount()
print(f'\t#Feições.......: {num_features}')
Podemos também obter o esquema das feições da camada:
layer_def = layer.GetLayerDefn()
print("Name - Type Width Precision")
for i in range(layer_def.GetFieldCount()):
field_name = layer_def.GetFieldDefn(i).GetName()
field_type_code = layer_def.GetFieldDefn(i).GetType()
field_type = layer_def.GetFieldDefn(i).GetFieldTypeName(field_type_code)
field_width = layer_def.GetFieldDefn(i).GetWidth()
field_precision = layer_def.GetFieldDefn(i).GetPrecision()
print(field_name.ljust(10) + " - " + \
field_type.ljust(10) + " " + \
str(field_width).ljust(6) + " " + \
str(field_precision))
Para acessar os elementos da camada podemos utilizar um laço do tipo for
como o mostrado abaixo:
for feature in layer:
nome = feature.GetField("NOME")
geom = feature.GetGeometryRef()
centroide = geom.Centroid()
print(f"Nome: {nome}, Localização: {centroide.ExportToWkt()}")