Уменьшите точность многогранного поля с помощью django rest framwork gis

Я использую django rest gis для загрузки листовок, и на верхнем уровне своего приложения я смотрю на карту мира. Базовая карта от Mapbox. Я звоню в мои rest-api и возвращаю схему всех отдельных стран, которые включены в приложение. В настоящее время файл GeoJSON возвращается размером 1,1 МБ, и мне нужно добавить больше стран, поэтому я хотел бы уменьшить его размер, чтобы повысить производительность.

Вот пример содержимого:

{"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],

Размер файла зависит от количества точек и точности этих точек. Я думал, что наиболее целесообразным способом уменьшения размера при сохранении моих исходных данных было бы снижение точности геометрических точек. Но я немного растерялся, как это сделать. Я просмотрел документацию по github и не нашел никаких подсказок.

Есть ли опция поля, чтобы уменьшить точность возвращаемого GeoJSON? Или есть другой способ добиться того, что я пытаюсь сделать?

Большое спасибо.

1 ответ

В итоге я упростил геометрию с помощью PostGIS и затем передал этот набор запросов сериализатору. Я начал с создания необработанного запроса в менеджере моделей.

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()

Вид довольно прост:

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

И сериализатор:

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

Я остановился на функции PostGIS ST_SimplifyVW() после запуска некоторых тестов.

В моем наборе данных 20 стран с геометрией, предоставленной Natural Earth. Без оптимизации размер файла geojson составил 1,2 МБ, выполнение запроса заняло 17 мс, а загрузка в моем браузере - 1,15 с. Конечно, качество представленного контура было отличным. Затем я попробовал функции ST_Simplify() и ST_SimplifyVW() с разными параметрами. Из этих очень грубых тестов я выбрал 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

Моя установка была Postgres 9.4 и PostGIS 2.2. ST_SimplifyVW не включен в PostGIS 2.1, поэтому вы должны использовать 2.2.

Вы можете сэкономить место, установив точность с помощью GeometryFieldво время сериализации. Это отрывок из моего кода для моделирования того жеWorldBorderмодель, определенная в учебнике по геоджанго ГИС. Заserializers.py:

from rest_framework_gis.serializers import (
    GeoFeatureModelSerializer, GeometryField)

from .models import WorldBorder


class WorldBorderSerializer(GeoFeatureModelSerializer):
    # set a custom precision for the geometry field
    mpoly = GeometryField(precision=2, remove_duplicates=True)

    class Meta:
        model = WorldBorder
        geo_field = "mpoly"
        fields = (
            "id", "name", "area", "pop2005", "fips", "iso2", "iso3",
            "un", "region", "subregion", "lon", "lat",
        )

Четкое определение точности с помощью mpoly = GeometryField(precision=2)сделает свое дело. Вremove_duplicates=Trueудалит идентичные точки, созданные путем усечения чисел. Вам нужно сохранитьgeo_field ссылка на ваше поле геометрии в Metaclass, или остальная структура не будет работать. Это моеviews.pyкод, чтобы увидеть объект GeoJSON, используяViewSet:

from rest_framework import viewsets, permissions

from .models import WorldBorder
from .serializers import WorldBorderSerializer


class WorldBorderViewSet(viewsets.ModelViewSet):
    queryset = WorldBorder.objects.all()
    serializer_class = WorldBorderSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )

Однако наиболее эффективным улучшением экономии места является упрощение геометрии, как описано geoAndrew. Здесь я вычисляю на лету упрощение геометрии с помощью сериализаторов:

from rest_framework_gis.serializers import (
    GeoFeatureModelSerializer, GeometrySerializerMethodField)

from .models import WorldBorder


class WorldBorderSerializer(GeoFeatureModelSerializer):
    # in order to simplify poligons on the fly
    simplified_mpoly = GeometrySerializerMethodField()

    def get_simplified_mpoly(self, obj):
        # Returns a new GEOSGeometry, simplified to the specified tolerance
        # using the Douglas-Peucker algorithm. A higher tolerance value implies
        # fewer points in the output. If no tolerance is provided, it
        # defaults to 0.
        return obj.mpoly.simplify(tolerance=0.01, preserve_topology=True)

    class Meta:
        model = WorldBorder
        geo_field = "simplified_mpoly"
        fields = (
            "id", "name", "area", "pop2005", "fips", "iso2", "iso3",
            "un", "region", "subregion", "lon", "lat",
        )

Эти два решения различны и не могут быть объединены (см., Как реализован rest_framework.gis.fields). Возможно, упрощение геометрии - лучшее решение для сохранения качества и экономии места. Надеюсь, это поможет!

Другие вопросы по тегам