# 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 を保持出来ます。

`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 # ここ
```
