Skip to content

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

  1. add the files to the shape_files directory. There are different file extensions but the key one is the .shp file for Epilepsy12
  2. 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 returns

    Layer 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.

  3. Use ogrinspect to convert the .shp file to a LayerMap 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',
    }
    
  4. 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
    ```
    
  5. Create a migration for the new models:

python manage.py makemigrations

  1. Create a new custom migration for the shape mapping / data import process:

python manage.py makemigrations epilepsy12 --name jersey_shape_file_mapping --empty

  1. 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),
        ]
    
  2. 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