# Django Rest Framework GIS ###### tags: `Django` `geo` `DRF` `azarashi` [djangonauts/django-rest-framework-gis: Geographic add-ons for Django REST Framework](https://github.com/djangonauts/django-rest-framework-gis) ## python / django / drf / drf-gis インストール 互換性がキビシイ https://github.com/djangonauts/django-rest-framework-gis#compatibility-with-drf-django-and-python 現在インストールしているもの ``` pip install django==2.1.11 pip install djangorestframework==3.9.4 pip install djangorestframework-gis==0.14 ``` ## [Fields](https://github.com/djangonauts/django-rest-framework-gis#fields) ### GeometryField + GeoDjango geometry フィールド + DRF の `WritableField` のサブクラス + `to_native` と `from_native` メソッドを提供 + このメソッドでGeoJSONのインプットアウトプットを提供 + オプション + `precision`:位置情報の精度をどのくらい要求するか。 + 例: `precision=2`の場合、緯度/経度(lat/lng)`[51.0486, -114.0708]` を`[51.05, -114.07]` に丸める + `remove_duplicates`:poligon から重複しているモノを削除する。たとえば precision を行った場合などよく使う + 注意: + 上記2つはAPIのレスポンスサイズを小さくするためにあるけど、逆に時間かかったりもする。 + 0.9.3以上では自動的に行われる ### GeometrySerializerMethodField + `SerializerMethodField` のサブクラス。 + あとで確認 ## [Serializers](https://github.com/djangonauts/django-rest-framework-gis#serializers) ### GeoModelSerializer (DEPRECATED) + いらない ### [GeoFeatureModelSerializer](https://github.com/djangonauts/django-rest-framework-gis#geofeaturemodelserializer):star: + `rest_framework.ModelSerializer` のサブクラス + GeoJSONに互換性のあるフォーマットを出力する + 使い方 ```python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): """ GeoJSON出力するシリアライザー """ class Meta: model = Location geo_field = "point" # 出力したいフィールド fields = ('id', 'address', 'city', 'state') ``` + 1リソースであれば、 ```python { "id": 1, "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0464], }, }, "properties": { "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon" } } ``` + 複数リソースであれば ```python { "type": "FeatureCollection", "features": [ { "id": 1 "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0464], } }, "properties": { "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon", } } { "id": 2, "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0489], }, }, "properties": { "address": "744 Evergreen Terrace", "city": "Springfield", "state": "Oregon" } } } ``` みたいな出力 ### `GeometrySerializerMethodField` を `geo_field` に渡す + `geo_field`に`GeometrySerializerMethodField`のインスタンスを渡すと、計算した結果をシリアライズできる ```python from django.contrib.gis.geos import Point from rest_framework_gis.serializers import GeoFeatureModelSerializer, GeometrySerializerMethodField class LocationSerializer(GeoFeatureModelSerializer): # geo_fieldに渡す値を含むフィールドのインスタンス other_point = GeometrySerializerMethodField() def get_other_point(self, obj): # おそらくobjがother_point。 return Point(obj.point.lat / 2, obj.point.lon / 2) class Meta: model = Location # インスタンスの変数を文字列で渡す geo_field = 'other_point' ``` `get_other_point`が None を返した場合、null に翻訳されて、geometryフィールドに渡される ### `id_field` + `id` などで渡される pk などを 非表示にするには、Metaの中で`id_field = False`すればよい + または、他の unique を使っても良い ```python from rest_framework_gis.serializers import GeoFeatureModelSerializer # False にする(fieldsに id 指定されているけどここはどう考えればいいのか?) class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = "point" id_field = False fields = ('id', 'address', 'city', 'state') # 他のIDを使う。例えば、 slug というフィールドがあったとして、それに変更する。 class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = 'point' id_field = 'slug' fields = ('slug', 'address', 'city', 'state') ``` ### auto_bbox / bbox_geo_field GeoJSON は、boundingbox呼ばれる feature を保持出来ます。 ![](https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/images/bounding-box.png) `GeoFeatureModelSerializer` は、 1. `geo_field`でBBOXを計算する 2. `bbox_geo_field` で`GeometryField`をモデルに追加してBBOXを計算する のどちらかでBBOXを扱う #### geo_field + Readだけに対応 + `auto_bbox = True` を指定 ```python class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = 'geometry' auto_bbox = True # ← ``` #### bbox_geo_field + Read / Write に対応 + Polygonsに保存される ```python class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = BoxedLocation geo_field = 'geometry' bbox_geo_field = 'bbox_geometry' ``` ### カスタム GeoJSON プロパティ + プロパティは、 modelの `id`, `geometry`, `bbox` 以外のフィールドが、デフォルト。 + これを変更したり、カスタマイズする方法がこちら + わかりづらいから使いたくない ```python # PostgreSQL の HStore フィールド(辞書型データを取得できる】をプロパティとして載せたい # models.py class Link(models.Model): """ Metadata is stored in a PostgreSQL HStore field, which allows us to store arbitrary key-value pairs with a link record. """ metadata = HStoreField(blank=True, null=True, default=dict) geo = models.LineStringField() objects = models.GeoManager() # serializers.py class NetworkGeoSerializer(GeoFeatureModelSerializer): class Meta: model = models.Link geo_field = 'geo' auto_bbox = True def get_properties(self, instance, fields): # This is a PostgreSQL HStore field, which django maps to a dict return instance.metadata def unformat_geojson(self, feature): attrs = { self.Meta.geo_field: feature["geometry"], "metadata": feature["properties"] } if self.Meta.bbox_geo_field and "bbox" in feature: attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"]) return attrs ``` ## Pagination ### [GeoJsonPagination](https://github.com/djangonauts/django-rest-framework-gis#geojsonpagination) + `rest_framework.pagination.PageNumberPagination`がベース + やり方は一緒 ```python from rest_framework_gis.pagination import GeoJsonPagination class GeojsonLocationList(generics.ListCreateAPIView): pagination_class = GeoJsonPagination ``` ### [Filters](https://github.com/djangonauts/django-rest-framework-gis#filters) 基本的には、django-filterを踏襲している。 django-filterについては、 [Django REST framework で django-filter を使う - Qiita](https://qiita.com/okoppe8/items/77f7f91f6878e3f324cc#%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%81%AE%E5%AE%9A%E7%BE%A9) > :boom: django-filter 1.0 でしかテストしてないみたい。 > 手元は、`django-filter==2.2.0`入れてるので確認すべし `GeometryFilter`と`GeoFilterSet`がある。 クエリ文字列か、`GEOSGeometry`がサポートするテキストを渡せばフィルタリングできる。 #### GeometryFilter ```python from rest_framework_gis.filterset import GeoFilterSet from rest_framework_gis.filters import GeometryFilter from django_filters import filters class RegionFilter(GeoFilterSet): slug = filters.CharFilter(name='slug', lookup_expr='istartswith') contains_geom = GeometryFilter(name='geom', lookup_expr='contains') class Meta: model = Region ``` #### GeoFilterSet django_filterの FilterSet と同じ #### InBBoxFilter あとで #### TMSTileFilter あとで #### [DistanceToPointFilter](https://github.com/djangonauts/django-rest-framework-gis#distancetopointfilter) + BaseFilterBackend のサブクラス + 与えられたポイントから、とある距離の範囲をフィルター ```python # views.py: from rest_framework_gis.filters import DistanceToPointFilter class LocationList(ListAPIView): queryset = models.Location.objects.all() serializer_class = serializers.LocationSerializer distance_filter_field = 'geometry' filter_backends = (DistanceToPointFilter, ) bbox_filter_include_overlapping = True # Optional ``` URL で、 ` /location/?dist=4000&point=-122.4862,37.7694&format=json` といった形で渡す。これで、 `point (-122.4862, 37.7694)`から4000m(4km)を取得 dist=4000 は、 各srid で解釈が変わるので注意 --- ### クエリ文字列で条件検索 + ここからはあきよこさんのDRF本P180 + モデルオブジェクト一覧取得の条件検索機能は、URLクエリ文字列での検索にも使える + デフォルトは無効 + 設定: + API全体に対して→ settings.py + 個別 API → view クラス変数で + **フィルタバックエンド**を指定することで実現 + SearchFilter (DRF標準) + DjangoFilterBackend (django-filter パッケージ) #### SearchFilter を使う + (これは非実用的です。DjangoFilterBackendがベター) + `api/v1/books/?search=~~~` といった形で使う + ViewSet クラスに `search_field=` を追加する。 ```python from rest_framework import filters, viewsets from shop.models import Book from .serializers import BookSerializer class BookViewSet(viewsets.ModelViewSet): queryset=Book.objects.all() serializer_class=BookSerializer filter_backend=[filters.SearchFilter] # ここ search_fields = ['title'] # ここ ``` #### django-filter の DjangoFilterBackend を使う + こちらを使う + インストール ``` pip install django-filter==2.2.0 INSTALLED_APP = [ ::: 'django-filters', # 追加するときへ "s" がつくので注意 ] ``` + views.py ```python # filter を作る。つまり # `api/dam/?prefecture=愛知` といったクエリ文字列でフィルタリングする機能をつける from django_filters import rest_framework as filters class DamFilter(filters.FilterSet): # この変数名が、 url のクエリ文字列キーになる # 例: `api/dam/?prefecture=愛知` prefecture = filters.CharFilter(field_name='address', lookup_expr='startswith') # 続けてフィルターを作成出来る river = filters.CharFilter(field_name='river_name', lookup_expr='startswith') water_system = filters.CharFilter(field_name='water_system_name', lookup_expr='startswith') class Meta: model = Dam fields = ("name", "address", ) # この部分は↓の画像の枠の部分 # 次に、Viewを作成 # filterset_class に、 フィルタークラスを与える class DamViewSet(viewsets.ModelViewSet): queryset = Dam.objects.all() serializer_class = DamGeoFeatureModelSerializer #pagination_class = MyPagination#GeojsonLocationList filter_backends = (filters.DjangoFilterBackend,DistanceToPointFilter,) # distance_filter_field = 'geom' distance_filter_convert_meters = True filterset_class = DamFilter # ここ ``` ![](https://i.imgur.com/ZFiClOe.jpg)