Shape Files and Maps
Shape Files¶
GeoDjango and GIS is a package that sits ontop of Django and is accompanied by an additional pack for Postgres. It has additional fields and methods for geographical data, such as longitude, latitude and conversions for mapping different coordinate systems (eg northings and eastings vs longitude and latitude).
Shape files contain the coordinates to map boundaries of regions. Epilepsy12 has boundary files for the countries of the United Kingdom from the UK Office of National Statistics.
In November 2024 support was added for Jersey. The shape files for this were taken from GADM, a well-known resource of geographical data. This is a walk through of this as an example but note this is not how it is implemented actually. It is an example to show how to add a new model and boundary data to it. In reality, no new model was created for Jersey but instead the .shp
file was mapped to existing fields in the Country
model and then a new migration created to add the Organisation in Jersey with all its relationships.
Adding a new .shp file¶
- add the files to the
shape_files
directory. There are different file extensions but the key one is the.shp
file for Epilepsy12 -
Use
ogrinfo
on the command line to inspect the.shp
file field structure. For example:ogrinfo -al -so epilepsy12/shape_files/gadm41_JEY_shp/gadm41_JEY_0.shp
returnsLayer name: gadm41_JEY_0 Metadata: DBF_DATE_LAST_UPDATE=2022-07-18 Geometry: Polygon Feature Count: 1 Extent: (-2.255138, 49.147083) - (-1.924584, 49.292915) Layer SRS WKT: GEOGCRS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84",6378137,298.257223563, LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433]], CS[ellipsoidal,2], AXIS["latitude",north, ORDER[1], ANGLEUNIT["degree",0.0174532925199433]], AXIS["longitude",east, ORDER[2], ANGLEUNIT["degree",0.0174532925199433]], ID["EPSG",4326]] Data axis to CRS axis mapping: 2,1 GID_0: String (10.0) COUNTRY: String (10.0)
This is not an essential step - it just allows you to inspect the file and see the fields.
-
Use
ogrinspect
to convert the.shp
file to aLayerMap
instance
python manage.py ogrinspect epilepsy12/shape_files/gadm41_JEY_shp/gadm41_JEY_0.shp JerseyBoundary --srid=4326 --mapping --multi
This generates the code for the Model (change the name to the Model name you want). Note the flags -
srid
relates to the spatial reference identifier (SRID), a unique identifier associated with a specific coordinate system and resolution. The other flags create the boundary mapping object. This returns:# This is an auto-generated Django model module created by ogrinspect. from django.contrib.gis.db import models class JerseyBoundary(models.Model): gid_0 = models.CharField(max_length=10) country = models.CharField(max_length=10) geom = models.MultiPolygonField(srid=4326) # Auto-generated `LayerMapping` dictionary for JerseyBoundary model jerseyboundary_mapping = { 'gid_0': 'GID_0', 'country': 'COUNTRY', 'geom': 'MULTIPOLYGON', }
-
Add the new model to the
models_folder
. Generally the model with the boundaries in Epilepsy12 is an abstract model and inherited by the parent model which contains more information and the relationships:```python from django.contrib.gis.db import models
class JerseyBoundary(models.Model): gid_0 = models.CharField(max_length=10) country = models.CharField(max_length=10) geom = models.MultiPolygonField(srid=4326)
class Meta: abstract = True
class Jersey(JerseyBoundary): class Meta: indexes = [models.Index(fields=["gid_0"])] verbose_name = "Jersey" verbose_name_plural = "Jersey" ordering = ("country",)
def __str__(self) -> str: return self.country ```
-
Create a migration for the new models:
python manage.py makemigrations
- Create a new custom migration for the shape mapping / data import process:
python manage.py makemigrations epilepsy12 --name jersey_shape_file_mapping --empty
-
Create a function within the migration that creates a new model, creates a new layer map and prescribes the path to the
.shp
file to import the data into the database. It would look like this:# Generated by Django 5.1.2 on 2024-11-23 13:16 # python imports import os # django imports from django.db import migrations # from django.apps import apps as django_apps from django.apps import apps as django_apps from django.contrib.gis.utils import LayerMapping Jersey = django_apps.get_model("epilepsy12", "Jersey") # Auto-generated `LayerMapping` dictionary for JerseyBoundary model jerseyboundary_mapping = { "gid_0": "GID_0", "country": "COUNTRY", "geom": "MULTIPOLYGON", } # Get the path to the shape file app_config = django_apps.get_app_config("epilepsy12") app_path = app_config.path jersey_shp_file_path = os.path.join( app_path, "shape_files", "gadm41_JEY_shp", "gadm41_JEY_0.shp" ) def load_jersey_shape_file_mapping(apps, schema_editor): """ Load the Jersey shape file mapping into the database """ # Load the Jersey shape file mapping into the database lm = LayerMapping( Jersey, jersey_shp_file_path, jerseyboundary_mapping, transform=False, encoding="utf-8", ) lm.save(strict=True, verbose=True) class Migration(migrations.Migration): dependencies = [ ("epilepsy12", "0043_jersey"), ] operations = [ migrations.RunPython(load_jersey_shape_file_mapping), ]
-
Run the migration
python manage.py migrate
root@600d309878ba:/app# python manage.py makemigrations
Migrations for 'epilepsy12':
epilepsy12/migrations/0043_jersey.py
+ Create model Jersey
root@600d309878ba:/app# python manage.py makemigrations epilepsy12 --name jersey_shape_file_mapping --empty
Migrations for 'epilepsy12':
epilepsy12/migrations/0044_jersey_shape_file_mapping.py
root@600d309878ba:/app# python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authtoken, captcha, contenttypes, epilepsy12, otp_email, otp_static, otp_totp, phonenumber, sessions
Running migrations:
Applying epilepsy12.0043_jersey... OK
Applying epilepsy12.0044_jersey_shape_file_mapping...Saved: Jersey