8 votos

Volcar una colección de características GeoJSON desde una consulta de Spatialite con Python

Dada una base de datos Spatialite, que contiene una tabla con un geometry y algunas otras columnas de atributos. Quiero consultar esa tabla en un script de Python y obtener un GeoJSON FeatureCollection que almacena una Función GeoJSON por fila de resultados, estando cada característica compuesta por la geometría del geometry y todas las demás columnas como atributos.

Conectarse a una base de datos SQLite con extensiones Spatialite y hacer una consulta en Python:

import sqlite3

# open database file    
conn = sqlite3.connect('MyDatabase.sqlite')

# load spatialite extensions. make sure that mod_spatialite.dll is somewhere in your system path.
conn.enable_load_extension(True)
conn.execute('SELECT load_extension("mod_spatialite.dll")')

# get a cursor
cursor = conn.cursor()

# compose a query
query = """
SELECT
    geometry,
    firstAttribute,
    secondAttribute,
    thirdAttribute,
FROM
    MyTable
;
"""

# execute query
cursor.execute(query)

# get results
results = cursor.fetchall()

Sin embargo, results es ahora una lista de tuplas (una tupla por fila) llena de cadenas (nunca entendí por qué esto podría ser útil nunca...), es decir

[(b'\x00\x01\xe6\x10\x00\x00\xde\xe4\xb7\xe8d\xa9\x95\xbf\xf9\xf5Cl\xb0\xbcI@\xde\xe4\xb7\xe8d\xa9\x95\xbf\xf9\xf5Cl\xb0\xbcI@|\x01\x00\x00\x00\xde\xe4\xb7\xe8d\xa9\x95\xbf\xf9\xf5Cl\xb0\xbcI@\xfe', 'value1', 'value2', 'value3'), ...]

El geometry se da como binario (como se almacena en la base de datos), sin embargo, obtenerlo como representación GeoJSON sería posible cambiando la consulta de SELECT geometry ... FROM ... a SELECT AsGeoJSON(geometry) ... FROM ... .

Aun así, esto está muy lejos del formato que quiero conseguir al final. ¿Alguna sugerencia de cómo se podría hacer esto de forma elegante sin tener que dividir la tupla en partes más pequeñas y recomponerlas manualmente al formato deseado?

10voto

Bien, lo tengo mientras tanto. Probablemente hay diferentes maneras de hacer esto, pero esta funciona bien. Aparte del acceso a su base de datos Spatialite a través de Python sqlite3 y el módulo Extensión de la espacialidad , necesitará el geojson (basta con instalarlo con pip install geojson ).

En aras de la exhaustividad, vamos a crear primero una nueva base de datos Spatialite y a llenarla con una fila de datos de ejemplo. Los comentarios explican en detalle lo que sucede.

import os
import sqlite3
import geojson

# create a new Spatialite database file (and delete any existing of the same name)
if os.path.exists('MyDatabase.sqlite'):
    os.remove('MyDatabase.sqlite')
conn = sqlite3.connect('MyDatabase.sqlite')

# load spatialite extensions for SQLite.
# on Windows: make sure to have the mod_spatialite.dll somewhere in your system path.
# on Linux: to my knowledge the file is called mod_spatialite.so and also should be located in a directory available in your system path.
conn.enable_load_extension(True)
conn.execute('SELECT load_extension("mod_spatialite.dll")')

# create a new table and fill it with some example values
createTableQuery = """
CREATE TABLE
    MyTable(
            geometry,
            firstAttribute,
            secondAttribute,
            thirdAttribute
            )
;
"""

fillTableQuery = """
INSERT INTO
    MyTable(
            geometry,
            firstAttribute,
            secondAttribute,
            thirdAttribute
            )
VALUES
    (
        GeomFromText('POINT(10 20)', 4326), 15, 'some Text', 'some other Text'
    )
;
"""

conn.execute(createTableQuery)
conn.execute(fillTableQuery)
conn.commit()

Ahora, el siguiente paso es sobrescribir el row_factory de Python sqlite3 para devolver los resultados de la consulta como listas de diccionarios (con un diccionario por fila de resultados) en lugar de listas de tuplas, que es el comportamiento por defecto.

# function that makes query results return lists of dictionaries instead of lists of tuples
def dict_factory(cursor, row):
    d = {}
    for idx,col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d

# apply the function to the sqlite3 engine
conn.row_factory = dict_factory

Ahora, defina su consulta de base de datos deseada, ejecútela y obtenga los resultados como una lista de diccionarios (un diccionario por fila de resultados):

# define your database query
getResultsQuery = """
SELECT
    AsGeoJSON(geometry),
    firstAttribute,
    secondAttribute,
    thirdAttribute
FROM
    MyTable
;
"""

# fetch the results in form of a list of dictionaries
results = conn.execute(getResultsQuery).fetchall()

Ahora iteraremos sobre las filas de resultados, crearemos una única característica GeoJSON para cada fila y la almacenaremos en una lista predefinida. La página web geometry de su tabla de base de datos (o como se llame) puede ser devuelta directamente como una cadena GeoJSON poniéndola como argumento a la función de Spatialite AsGeoJSON en la consulta. Esta cadena GeoJSON forma ahora parte del diccionario devuelto de cada fila y se puede acceder a ella a través del índice AsGeoJSON(geometry) (o como se llame la columna). A continuación, se puede dar a la loads función del geojson que creará una geometría GeoJSON serializada de esa cadena.

Después de borrar el AsGeoJSON(geometry) del diccionario de filas, lo que queda es un diccionario que contiene todos los demás valores de las columnas como pares clave-valor, excepto la geometría. Afortunadamente, el geojson puede crear un GeoJSON Feature pasando un GeoJSON Geometry y un diccionario de propiedades. Cada característica individual se añade a la lista predefinida.

# create a new list which will store the single GeoJSON features
featureCollection = list()

# iterate through the list of result dictionaries
for row in results:

    # create a single GeoJSON geometry from the geometry column which already contains a GeoJSON string
    geom = geojson.loads(row['AsGeoJSON(geometry)'])

    # remove the geometry field from the current's row's dictionary
    row.pop('AsGeoJSON(geometry)')

    # create a new GeoJSON feature and pass the geometry columns as well as all remaining attributes which are stored in the row dictionary
    feature = geojson.Feature(geometry=geom, properties=row)

    # append the current feature to the list of all features
    featureCollection.append(feature)

Por último, el FeatureCollection del constructor del geojson acepta una lista de GeoJSON Features para unirlos en un FeatureCollection :

# when single features for each row from the database table are created, pass the list to the FeatureCollection constructor which will merge them together into one object
featureCollection = geojson.FeatureCollection(featureCollection)

Por último, vuelca el archivo creado FeatureCollection object en una cadena:

# print the FeatureCollection as string
GeoJSONFeatureCollectionAsString = geojson.dumps(featureCollection)
print(GeoJSONFeatureCollectionAsString)

Ya está. Ten en cuenta que la cadena resultante no se imprimirá de forma bonita, lo cual no me importa ya que la estoy pasando a una función Leaflet/JavaScript:

{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"firstAttribute": 15, "thirdAttribute": "some other Text", "secondAttribute": "some Text"}, "geometry": {"type": "Point", "coordinates": [10, 20]}, "id": null}]}

Si quieres que se imprima bonito, pasa el indent argumento a la geojson.dumps método:

# print the FeatureCollection as string
GeoJSONFeatureCollectionAsString = geojson.dumps(featureCollection, indent=2)
print(GeoJSONFeatureCollectionAsString)

que da:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": null,
      "properties": {
        "thirdAttribute": "some other Text",
        "firstAttribute": 15,
        "secondAttribute": "some Text"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          10,
          20
        ]
      }
    }
  ]
}

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X