Search code examples
django-rest-frameworkgeojsondjango-rest-framework-gis

Reduce precision of multi polygon field with django rest framwork gis


I'm using django rest gis to load up leaflet maps, and at the top level of my app I'm looking at a map of the world. The basemap is from Mapbox. I make a call to my rest-api and return an outline of all of the individual countries that are included in the app. Currently, the GeoJSON file that is returned in 1.1MB in size and I have more countries to add so I'd like to reduce the size to improve performance.

Here is an example of the contents:

{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-64.54916992187498,-54.71621093749998],[-64.43881835937495,-54.739355468749984],[-64.22050781249999,-54.721972656249996],[-64.10532226562495,-54.72167968750003],[-64.054931640625,-54.72988281250001],[-64.03242187499995,-54.74238281249998],[-63.881933593750006,-54.72294921875002],[-63.81542968749997,-54.725097656250014],[-63.83256835937499,-54.76796874999995],[-63.97124023437499,-54.810644531250034],[-64.0283203125,-54.79257812499999],[-64.32290039062497,-54.79648437499999],[-64.45327148437497,-54.84033203124995],[-64.50869140625,-54.83994140624996],[-64.637353515625,-54.90253906250001],

The size of the file is a function the number of points and the precision of those points. I was thinking that the most expedient way to reduce the size, while preserving my original data, would be to reduce the precision of the geom points. But, I'm at a bit of a loss as to how to do this. I've looked through the documentation on github and haven't found any clues.

Is there a field option to reduce the precision of the GeoJSON returned? Or, is there another way to achieve what I'm try to do?

Many thanks.


Solution

  • I ended up simplifying the geometry using PostGIS and then passing that queryset to the serializer. I started with creating a raw query in the model manager.

    class RegionQueryset(models.query.QuerySet):
        def simplified(self):
            return self.raw(
                "SELECT region_code, country_code, name, slug, ST_SimplifyVW(geom, 0.01) as geom FROM regions_region "
                "WHERE active=TRUE AND region_type = 'Country'"
            )
    
    class RegionsManager (models.GeoManager):
        def get_queryset(self):
            return RegionQueryset(self.model, using=self._db)
    
        def simplified(self):
            return self.get_queryset().simplified()
    

    The view is quite simple:

    class CountryApiGeoListView(ListAPIView):
        queryset = Region.objects.simplified()
        serializer_class = CountryGeoSerializer
    

    And the serializer:

    class CountryGeoSerializer(GeoFeatureModelSerializer):
        class Meta:
            model = Region
            geo_field = 'geom'
            queryset = Region.objects.filter(active=True)
            fields = ('name', 'slug', 'region_code', 'geom')
    

    I ended up settling on the PostGIS function ST_SimplifyVW() after running some tests.

    My dataset has 20 countries with geometry provided by Natural Earth. Without optimizing, the geojson file was 1.2MB in size, the query took 17ms to run and 1.15 seconds to load in my browser. Of course, the quality of the rendered outline was great. I then tried the ST_Simplify() and ST_SimplifyVW() functions with different parameters. From these very rough tests, I decided on ST_SimplifyVW(geom, 0.01)

    **Function                 Size   Query time   Load time   Appearance**
    None                       1.2MB  17ms         1.15s       Great
    ST_Simplify(geom, 0.1)     240K   15.94ms      371ms    Barely Acceptable
    ST_Simplify(geom, 0.01)    935k   22.45ms      840ms       Good
    ST_SimplifyVW(geom, 0.01)  409K   25.92ms      628ms       Good
    

    My setup was Postgres 9.4 and PostGIS 2.2. ST_SimplifyVW is not included in PostGIS 2.1, so you must use 2.2.