Leer datos sin procesar en geopandas

14

¿Es posible leer datos sin procesar en a geopandas GeoDataFrame, a la a pandas DataFrame?

Por ejemplo, lo siguiente funciona:

import pandas as pd
import requests
data = requests.get("https://data.cityofnewyork.us/api/geospatial/arq3-7z49?method=export&format=GeoJSON")
pd.read_json(io.BytesIO(r.content))

Lo siguiente no:

import geopandas as gpd
import requests
data = requests.get("https://data.cityofnewyork.us/api/geospatial/arq3-7z49?method=export&format=GeoJSON")
gpd.read_file(io.BytesIO(r.content))

En otras palabras, ¿es posible leer datos geoespaciales que están en la memoria sin guardar primero esos datos en el disco?

Aleksey Bilogur
fuente

Respuestas:

16

Puede pasar el json directamente al constructor GeoDataFrame:

import geopandas as gpd
import requests
data = requests.get("https://data.cityofnewyork.us/api/geospatial/arq3-7z49?method=export&format=GeoJSON")
gdf = gpd.GeoDataFrame(data.json())
gdf.head()

Salidas:

                                            features               type
0  {'type': 'Feature', 'geometry': {'type': 'Poin...  FeatureCollection
1  {'type': 'Feature', 'geometry': {'type': 'Poin...  FeatureCollection
2  {'type': 'Feature', 'geometry': {'type': 'Poin...  FeatureCollection
3  {'type': 'Feature', 'geometry': {'type': 'Poin...  FeatureCollection
4  {'type': 'Feature', 'geometry': {'type': 'Poin...  FeatureCollection

Para formatos de archivo único compatibles o archivos de forma comprimidos, puede usar fiona.BytesCollectiony GeoDataFrame.from_features:

import requests
import fiona
import geopandas as gpd

url = 'http://www.geopackage.org/data/gdal_sample.gpkg'
request = requests.get(url)
b = bytes(request.content)
with fiona.BytesCollection(b) as f:
    crs = f.crs
    gdf = gpd.GeoDataFrame.from_features(f, crs=crs)
    print(gdf.head())
y para archivos de forma comprimidos (admitidos a partir de fiona 1.7.2 )
url = 'https://www2.census.gov/geo/tiger/TIGER2010/STATE/2010/tl_2010_31_state10.zip'
request = requests.get(url)
b = bytes(request.content)
with fiona.BytesCollection(b) as f:
    crs = f.crs
    gdf = gpd.GeoDataFrame.from_features(f, crs=crs)
    print(gdf.head())

Puede averiguar qué formatos admite Fiona utilizando algo como:

import fiona
for name, access in fiona.supported_drivers.items():
    print('{}: {}'.format(name, access))

Y una solución alternativa para leer datos comprimidos en memoria en fiona 1.7.1 o anterior:

import requests
import uuid
import fiona
import geopandas as gpd
from osgeo import gdal

request = requests.get('https://github.com/OSGeo/gdal/blob/trunk/autotest/ogr/data/poly.zip?raw=true')
vsiz = '/vsimem/{}.zip'.format(uuid.uuid4().hex) #gdal/ogr requires a .zip extension

gdal.FileFromMemBuffer(vsiz,bytes(request.content))
with fiona.Collection(vsiz, vsi='zip', layer ='poly') as f:
    gdf = gpd.GeoDataFrame.from_features(f, crs=f.crs)
    print(gdf.head())
usuario2856
fuente
Esto funciona para GeoJSON, que responde a la pregunta. Pero esto no funcionaría para otros formatos de archivos geoespaciales, como shapefiles o KML o KMZ. ¿Conoces una solución para esos casos?
Aleksey Bilogur
Una pequeña aclaración está en orden. GeoPandas y Fiona admiten archivos shape y KML, pero no pueden admitir completamente API únicas como la de la ciudad de Nueva York. Además, BytesCollectionfunciona totalmente, pero probablemente se eliminará en una versión futura a favor de una de las opciones en github.com/Toblerity/Fiona/issues/409 .
sgillies
Gracias. @sgillies, ¿debería abrirse como una solicitud de función geopandaso sería mejor esperar los cambios que menciona aquí ?
Aleksey Bilogur
@sgillies declaras que Fiona admite KML en tu comentario anterior, pero DriverError: unsupported driver: 'KML'aparece cuando intentas abrir KML ya que no está en el supported_driversdict (usando Fiona 1.7.1) y noté un par de problemas. falta de soporte KML (# 23 y # 97). ¿Fiona admite KML?
user2856
Gracias por ver el from_featuresmétodo. Me salvó el día!
jlandercy
3

Dado fiona.BytesCollectionque no parece funcionar TopoJSONaquí, una solución que funciona para todos sin la necesidad de gdal:

import fiona
import geopandas as gpd
import requests

# parse the topojson file into memory
request = requests.get('https://vega.github.io/vega-datasets/data/us-10m.json')
visz = fiona.ogrext.buffer_to_virtual_file(bytes(request.content))

# read the features from a fiona collection into a GeoDataFrame
with fiona.Collection(visz, driver='TopoJSON') as f:
    gdf = gpd.GeoDataFrame.from_features(f, crs=f.crs)
Mattijn
fuente
Con geopandas==0.4.0, Fiona==1.8.4y Python 3, entiendo DriverError: unsupported driver: 'TopoJSON'.
edesz
Tienes razón. Estaba funcionando hasta al menos la versión 1.7.13deFiona
Mattijn
Es lamentable que esto no funcione. Intenté seguir su ejemplo en GitHub para las parcelas de coropletas de Altair, pero eso también arroja exactamente el mismo error en la línea gdf = gpd.read_file(counties, driver='TopoJSON'). Pensé que usar with fiona.Collection...podría funcionar, pero lamentablemente no.
edesz
@edesz esto fue un error y se solucionará en Fiona 1.8.5, consulte: github.com/Toblerity/Fiona/issues/721
Mattijn
2

Cuando se usa Fiona 1.8, esto se puede (¿debe hacer?) Usando el proyecto MemoryFileoZipMemoryFile .

Por ejemplo:

import fiona.io
import geopandas as gpd
import requests

response = requests.get('http://example.com/Some_shapefile.zip')
data_bytes = response.content

with fiona.io.ZipMemoryFile(data_bytes) as zip_memory_file:
    with zip_memory_file.open('Some_shapefile.shp') as collection:
      geodf = gpd.GeoDataFrame.from_features(collection, crs=collection.crs)
esmail
fuente
0

La forma más fácil es ingresar la URL de GeoJSON directamente en gpd.read (). Intenté extraer un shapefile de un zip antes de esto usando BytesIO y zipfile y tuve problemas con gpd (específicamente Fiona) que aceptaba objetos similares a archivos.

import geopandas as gpd
import David.SQL_pull_by_placename as sql
import os

os.environ['PROJ_LIB'] = r'C:\Users\littlexsparkee\Anaconda3\Library\share\proj'

geojson_url = f'https://github.com/loganpowell/census-geojson/blob/master/GeoJSON/500k/2018/{sql.state}/block-group.json?raw=true'
census_tracts_gdf = gpd.read_file(geojson_url)
littlexsparkee
fuente
0

Prefiero el resultado obtenido al usar el indocumentado en GeoDataFrame.from_features()lugar de pasar el GeoJSON al constructor GDF directamente:

import geopandas as gpd
import requests
data = requests.get("https://data.cityofnewyork.us/api/geospatial/arq3-7z49?method=export&format=GeoJSON")
gpd.GeoDataFrame().from_features(data.json())

Salida

                       geometry                         name                                url           line objectid                                              notes
0    POINT (-73.99107 40.73005)                     Astor Pl  http://web.mta.info/nyct/service/  4-6-6 Express        1  4 nights, 6-all times, 6 Express-weekdays AM s...
1    POINT (-74.00019 40.71880)                     Canal St  http://web.mta.info/nyct/service/  4-6-6 Express        2  4 nights, 6-all times, 6 Express-weekdays AM s...
2    POINT (-73.98385 40.76173)                      50th St  http://web.mta.info/nyct/service/            1-2        3                              1-all times, 2-nights
3    POINT (-73.97500 40.68086)                    Bergen St  http://web.mta.info/nyct/service/          2-3-4        4           4-nights, 3-all other times, 2-all times
4    POINT (-73.89489 40.66471)             Pennsylvania Ave  http://web.mta.info/nyct/service/            3-4        5                        4-nights, 3-all other times

El GeoDataFrame resultante tiene la columna de geometría configurada correctamente y todas las columnas como yo esperaría, sin necesidad de anular ninguna FeatureCollections

dericke
fuente