LoginSignup
3
1

More than 1 year has passed since last update.

django-rest-framework-gisを触ってみよう

Posted at

はじめに

この記事はMIERUNE Advent Calendar 2021 21日目の記事です(すでに年が明けていますが・・・泣)。前回はGeoDjangoを触ってみよう!という記事を書いたので、今回はdjango-rest-framework-gisを触ってみようというテーマにしました。前回の記事はちょっと重めになったので、今回はライトめに書きます。

実際の業務の中では、DjangoはAPIサーバーとして利用することがほとんどで、GISデータを扱う際にはこちらのモジュールを導入することが多い印象です。

django-rest-framework-gisとは

DjangoでREST APIを実装する際には、Django REST framework(DRF)というサードパーティ製のライブラリを使います。そのDRFに地理空間機能を追加したものがdjango-rest-framework-gisになります。

ソースコードはこちらになります。

前提

この記事は、Django公式ドキュメントのGeoDjangoチュートリアルを実行したソースコードに対して、DRFおよびdjango-rest-framework-gisを追加してみて、その機能を試してみようという趣旨で書いています。

対象

  • DRFはある程度触ったことがある
  • django-rest-framework-gisははじめて

バージョン

python_version = "3.8"
django = "==3.2.6"
psycopg2-binary = "==2.9.1"

Dockerのバージョン

$ docker version --format '{{.Server.Version}}'
20.10.11

ソースコード/ 環境

以下に実行したソースコードを載せておきます。前回の記事で紹介したリポジトリに、django-rest-framework-gisを含めたREST APIの機能を追加しています。
https://github.com/selfsryo/GeoDjangoOfficialTutorial
環境はそのままDockerでDjango + PostGIS環境を構築したものを使います。

以下のような構成になっています。

GeoDjangoOfficialTutorial
├── .gitignore
├── docker-compose.yml
├── geodjango
│   ├── .env.sample
│   ├── Dockerfile
│   ├── Pipfile
│   ├── Pipfile.lock
│   ├── entrypoint.sh
│   ├── geodjango
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── manage.py
│   └── world
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── data
│       │   ├── Readme.txt
│       │   ├── TM_WORLD_BORDERS-0.3.dbf
│       │   ├── TM_WORLD_BORDERS-0.3.prj
│       │   ├── TM_WORLD_BORDERS-0.3.shp
│       │   └──  TM_WORLD_BORDERS-0.3.shx
│       ├── load.py
│       ├── migrations
│       │   ├── 0001_initial.py
│       │   └── __init__.py
│       ├── models.py
│       ├── serializerss.py
│       ├── tests.py
│       └── views.py
└── postgres
    ├── .env.db.sample
    ├── Dockerfile
    └── sql
        └── init.sql

データは前回の記事で用意したShapefileを使います。

やってみよう

準備

Pipenv環境の中にDRF、django-rest-framework-gis、django-filterをインストールします。

$ cd .../GeoDjangoOfficialTutorial/geodjango
$ pipenv install djangorestframework djangorestframework-gis django-filter

その後、コンテナを一度削除します。

$ cd ..
$ docker compose down -v 

settings.pyINSTALLED_APP'django.contrib.gis'と、先ほど作成したworldを追加します。なお、自分の場合はDockerを使っているので、いくつかの値を環境変数としてgeodjango/.envから読み込むようにしています。DATABASESの設定値もDockerのPostGISに接続するようにしています。以上を踏まえ、settings.pyは以下のようになりました。

import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ.get("SECRET_KEY")

DEBUG = int(os.environ.get("DEBUG", default=0))

ALLOWED_HOSTS = []


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',

    # 追加
    'django_filters',
    'rest_framework',
    'rest_framework_gis',

    # 作成したアプリケーション
    'world',
]

シリアライザ

次にserializers.pyを作成します。アプリケーションの直下の、world/serializers.pyという階層にして作成します。

world
├── data
│   ├── Readme.txt
│   ├── TM_WORLD_BORDERS-0.3.dbf
│   ├── TM_WORLD_BORDERS-0.3.prj
│   ├── TM_WORLD_BORDERS-0.3.shp
│   └── TM_WORLD_BORDERS-0.3.shx
│
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── serializerss.py
├── tests.py
└── views.py

内容は以下のようにします。今回はGeoFeatureModelSerializerというクラスを使いました。(公式のREADMEはこちら

from rest_framework_gis.serializers import GeoFeatureModelSerializer

from world.models import WorldBorder


class WorldBorderSerializer(GeoFeatureModelSerializer):

    class Meta:
        model = WorldBorder
        geo_field = 'mpoly'
        auto_bbox = True
        fields = ('__all__')

GeoFeatureModelSerializerModelSerializerのサブクラスですが、以下のような特徴があります。

  • geo_fieldの定義が必要
    • GeometrySerializerMethodFieldとして地理空間的な処理を行った上で指定することも可能
  • GeoJSON形式でレスポンスを返す
  • id_fieldにてidを含むか含まないか、および別フィールドを指定可能(デフォルトはpk)
  • auto_bboxにてgeo_field(およびbbox_geo_field)から計算されたbboxの値をレスポンスに含むか含まないか指定可能
  • GeoJSONのpropertiesはカスタム可能

今回は、auto_bboxをTrueにしてみました。

ビュー

次にviews.pyは以下のようにしました。ビューはDRF標準のModelViewSetを使います。

from rest_framework import viewsets
from rest_framework_gis.filters import DistanceToPointFilter, InBBoxFilter
from rest_framework_gis.pagination import GeoJsonPagination
from world.serializers import WorldBorderSerializer
from world.models import WorldBorder


class MyPagination(GeoJsonPagination):
    page_size_query_param = 'page_size'

class WorldBorderViewSet(viewsets.ModelViewSet):
    queryset = WorldBorder.objects.all()
    serializer_class = WorldBorderSerializer
    pagination_class = MyPagination
    filter_backends = (DistanceToPointFilter, InBBoxFilter)
    distance_filter_field = 'mpoly'
    bbox_filter_field = 'mpoly'
    bbox_filter_include_overlapping = True

以下django-rest-framework-gisの特徴となる点です。

  • pagination_classにGeoJsonPaginationを指定

    • DRFのページネーション(PageNumberPaginationなど)とはレスポンスの形式が一部異なり、GeoJSON形式になる
  • フィルタリングのクラスを指定できる

    • DistanceToPointFilterの場合、指定した距離に含まれるインスタンスを返す
    • distance_filter_fieldで基準となるフィールドの指定が必要
    • InBBoxFilterの場合、指定されたbbox内に完全に含まれるインスタンスを返す
    • bbox_filter_fieldで基準となるフィールドの指定が必要
    • bbox_filter_include_overlapping = Trueでbbox内に完全に含まれなくとも重なっていればそのインスタンスを返す

URL

次にurls.pyを更新します。今回は以下のようにします。

from django.contrib import admin
from django.urls import include, path

from rest_framework import routers

from world.views import WorldBorderViewSet


router = routers.DefaultRouter()
router.register('border', WorldBorderViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('world/', include(router.urls)),
]

BrowsableAPI

設定ができたのでDRFのBrawsableAPIから確認します。Dockerのコンテナを立ち上げます。

$ docker compose up --build -d

その後データのインサートを行います。前回の記事で作成したスクリプトを実行します。

$ docker container exec -it geodjango python3 manage.py shell -c "from world import load; load.run()"

ブラウザで確認してみます。単体のGETをしてみます。ブラウザから以下にアクセスします。

以下のように、GeoJSONの形式でレスポンスが返ってきていることが確認できます。
1.png

一部省略しましたがpropetiesとbboxも含まれています。
2.png

続いてページングの確認です。以下のURLを開きます。
http://localhost:8000/world/border/?page_size=5
DRFが持つページネーションクラスとは違った形式でレスポンスが返ってきていることが確認できます。
3.png

これに、距離でフィルタをかけてみます。以下にアクセスします。
http://localhost:8000/world/border/?page_size=5&dist=1&point=138.729952,35.359455
座標を富士山に指定したので、日本のインスタンスが取得できます。
4.png

最後にbboxでフィルタをかけてみます。以下にアクセスします。
http://localhost:8000/world/border/?page_size=5&in_bbox=122.935257,24.250832,153.96579,45.486382
日本のインスタンスが持つbboxを指定しましたが、bbox_filter_include_overlapping = Trueにしているので"count": 7となっています。
5.png

このように、アクセスするエンドポイントを変えることで取得する地物をフィルタリングすることができました。

おわりに

基本的な内容でしたが、体系的にdjango-rest-framework-gisを触ることができたので良い勉強になりました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1