LayerMapping en Django
📍 GeoDjango Utilities
¿Qué es LayerMapping?
LayerMapping es una clase que proporciona una forma de mapear el contenido de archivos de datos espaciales vectoriales (por ejemplo, shapefiles) en modelos GeoDjango.
🔍 El contexto
En un proyecto en el que estoy trabajando estoy haciendo uso de archivos de datos espaciales vectoriales, específicamente shapefiles. El primer reto que se me presentó no fue tan complejo, ya que está en la documentación oficial de Django, básicamente quería guardar la información de un archivo .shp en un modelo en Django.
Esto fue sencillo de solucionar, teniendo el siguiente modelo:
from django.contrib.gis.db import modelsclass TestGeo(models.Model):
name = models.CharField(max_length=25)
poly = models.PolygonField(srid=4269) def __str__(self):
return 'Name: %s' % self.name
Podemos realizar el mapeo de las variables, mediante la creación de un diccionario mapping, que se pasa como parámetro en la función LayerMapping, y agregando los demás parámetros como el modelo y el archivo .shp, después de creada esa instancia podemos utilizar la función save que nos permite hacer el guardado del archivo .shp al modelo de la base de datos.
>>> from django.contrib.gis.utils import LayerMapping
>>> from geoapp.models import TestGeo
>>> mapping = {'name' : 'str',
'poly' : 'POLYGON',
}
>>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
>>> lm.save(verbose=True)
Hasta aquí todo iba bien y el proceso es relativamente sencillo, de ejecutar.
❓El problema
El problema se presentó cuando completé el modelo y adicioné una ForeignKey, ya que el archivo .shp no contenía ese valor, por lo tanto lo tenia que extraer de otro sector del código y pasarlo como un valor estático y adicionarlo de alguna manera antes para que la función save de la clase LayerMapping pudiera guardar toda la información de manera correcta y no saliera un error de que la información no se guardaba porque estaba enviando la llave foránea vacía.
El modelo completo quedó así:
from django.contrib.gis.db import modelsclass TestGeo(models.Model):
name = models.CharField(max_length=25)
poly = models.PolygonField(srid=4269)
city = models.ForeignKey(model_city, on_delete=models.CASCADE) def __str__(self):
return 'Name: %s' % self.name
⚡ La solución
Después de investigar encontré la solución que finalmente me ayudó a dar la solución a mi problema y la cual decidí documentar en español por si alguien la llega a necesitar en el futuro, ya que estos temas son tan específicos que existen muy pocos ejemplos en español y en inglés.
La solución consiste en sobreescribir parte del método feature_kwargs de la clase LayerMapping y adicionar un diccionario de campos estáticos, custom. Esto de realiza de la siguiente manera:
class CustomLayerMapping(LayerMapping):
def __init__(self, *args, **kwargs):
self.custom = kwargs.pop('custom', {})
super(CustomLayerMapping, self).__init__(*args, **kwargs) def feature_kwargs(self, feature):
kwargs = super(CustomLayerMapping, self).feature_kwargs(feature)
kwargs.update(self.custom)
return kwargs
Y después hacemos uso de esta clase customizada en vez de la clase original. En mi caso la variable city era una constante que podía extraer desde una request, se debe tener en cuenta que al pasar el valor el modelo comprueba si la relación es correcta o no, por lo tanto si se envía un número que no existe en la tabla relacionada en este caso , si enviamos un id = 10 y la ciudad 10 no existe en la tabla de ciudades , arrojará un error.
>>> from geoapp.models import TestGeo
>>> mapping = {'name' : 'str',
'poly' : 'POLYGON',
}
>>> custom = {'city': city}
>>> lm = CustomLayerMapping(TestGeo, 'test_poly.shp', mapping, custom=custom)
>>> lm.save(verbose=True)
Referencias
- LayerMapping data import utility https://docs.djangoproject.com/en/3.1/ref/contrib/gis/layermapping/
- Is Django’s LayerMapping capable of putting a static value into all features? https://gis.stackexchange.com/questions/20782/is-djangos-layermapping-capable-of-putting-a-static-value-into-all-features