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
]
}
}
]
}