Уменьшите точность многогранного поля с помощью 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
ссылка на ваше поле геометрии в Meta
class, или остальная структура не будет работать. Это мое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). Возможно, упрощение геометрии - лучшее решение для сохранения качества и экономии места. Надеюсь, это поможет!