LoginSignup
5
0

More than 1 year has passed since last update.

GeoDjangoでベクトルタイルを動的に取得してみる

Posted at

これは MIERUNE AdventCalendar 2022 9日目の記事です。
昨日は @bordoray さんによる 星図を作ってみた⭐ でした。

MIERUNEでバックエンドを担当しております@selfsryoといいます。
去年のAdventCalendarでは、GeoDjangoを触ってみよう!およびdjango-rest-framework-gisを触ってみようというGeoDjango関連の記事を書きましたが、今回もGeoDjangoにまつわる記事を書きます。

概要

ベクトルタイル

GISに限ったことではありませんが、最近の動向としてGISデータの巨大化がますます進み、データを保持するのも配信するのも以前より難しくなってきている印象です。データフォーマットの軽量化 / 最適化のニーズが高まっていますが、その先駆け的存在であり、すでに枯れた技術の一つにベクトルタイルがあります。

このベクトルタイル、とても軽量で良いのですが、ベクトルを変換してしかるべき場所に格納して配信する、という手間があります。サイズが大きい場合、この作業にそれなりのマシンスペックが求められます。かつ静的ファイルであるので、データの更新が発生するたびに作成し直す必要があります。このように、ベクトルタイルの作成および管理には、少なくないコストが発生します。

ST_AsMVTとGeoDjango

ベクトルタイルを動的に取得する方法として、DBから直接取得するというアプローチがあります。PostGISの場合、ST_AsMVTという空間関数が用意されており、これを実行することでDBから直接ベクトルタイルを取得することができます。

サーバーからGISデータを配信するとき、弊社ではGeoDjango + PostGISの組み合わせが鉄板となっています。このST_AsMTVをGeoDjangoから実行できればよさそうなのですが、GeoDjangoではST_AsMVTのORMは用意されておらず、かつ直接SQLを叩いて実行するのも大変・・。

と思っていた時に、便利そうなライブラリをみつけました。

結構前からあるライブラリのようですが、全然気付かなかった・・。Django REST frameworkの拡張ライブラリで、裏側でST_AsMVTなどのSQLを実行してくれるようです。URLクエリから、動的にフィルタしてベクトルタイルを取得することができるようです。

超絶前置きが長くなりましたが、今回はこのdjangorestframework-mvtを触ってみた、というテーマの記事となります。

ソースコード / データ

以下のリポジトリで試しました。以前の記事GeoDjango公式チュートリアルを実施してみたのですが、その時のコードをインポートしたものです。

データはGeoDjangoの公式チュートリアルで使われている国境データのShapefile(クリックするとダウンロードされます)を使いました。

手順

準備

公式ドキュメントに従って実行していきます。

まずはインストールします。pipenvを使っているので、以下を実行しました。

pipenv install djangorestframework-mvt

続いてモデルにManagerを追加します。MVTManagerの中ではgeo_colとしてジオメトリのフィールド名を指定する必要があるので、geo_col="mpoly"とします。

from django.contrib.gis.db import models
# 以下追加
from rest_framework_mvt.managers import MVTManager

class WorldBorder(models.Model):
    name = models.CharField(max_length=50)
    area = models.IntegerField()
    pop2005 = models.IntegerField('Population 2005')
    fips = models.CharField('FIPS Code', max_length=2, null=True)
    iso2 = models.CharField('2 Digit ISO', max_length=2)
    iso3 = models.CharField('3 Digit ISO', max_length=3)
    un = models.IntegerField('United Nations Code')
    region = models.IntegerField('Region Code')
    subregion = models.IntegerField('Sub-Region Code')
    lon = models.FloatField()
    lat = models.FloatField()

    mpoly = models.MultiPolygonField()

    # 以下2行追加
    objects = models.Manager()
    vector_tiles = MVTManager(geo_col="mpoly")

    def __str__(self):
        return self.name

最後に、urls.pyにパスを追加します。mvt_view_factoryの中では、ジオメトリのフィールド名をgeom_colとして指定します。微妙な表記揺れが気になるところですが、なにか理由はあるのでしょうか。

from django.contrib import admin
from django.urls import include, path
from rest_framework import routers
# 追加
from rest_framework_mvt.views import mvt_view_factory
from world.models import WorldBorder
from world.views import WorldBorderViewSet

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('world/', include(router.urls)),
    # 追加
    path("api/mvt/", mvt_view_factory(WorldBorder, geom_col="mpoly")),
]

この状態でDockerを起動し、データをインサートします。これで準備完了です。

QGISから表示確認

デスクトップGISアプリケーションであるQGISから配信を確認してみます。
今回はMac用QGISの以下のバージョンを使用しました。
3.26.3-Buenos Aires

QGISを起動し、Vector Tilesを右クリック > 新規一般接続に遷移します。
スクリーンショット 2022-12-08 21.50.23.png

URLには以下を設定します。
http://localhost:8000/api/mvt/?tile={z}/{x}/{y}
スクリーンショット 2022-12-08 23.21.26.png

このレイヤを表示させてみます。ちゃんと取得できていることがわかります。いい感じですね。
スクリーンショット 2022-12-08 23.23.29.png

属性情報も確認できます。
スクリーンショット 2022-12-08 23.25.19.png

では、データの絞り込みを行ってみましょう。公式ドキュメントにある通り、URLクエリでカラム名を指定して取得するデータを絞ってみます。

今回は、日本のデータだけを取得します。モデルの中にnameフィールドがあるので、&name=JapanをURLクエリに追加します。
http://localhost:8000/api/mvt/?tile={z}/{x}/{y}&name=Japan
スクリーンショット 2022-12-08 23.40.16.png

表示できました。ちゃんと日本のデータだけ取得できているようです。
スクリーンショット 2022-12-08 23.43.00.png

所感

今回はdjangorestframework-mvtを用いて、ベクトルタイルをGeoDjangoから動的に配信してみました。Djangoを使って動的にベクトルタイルを配信できるとなると、いろいろ使いどころがありそうです。

若干動作的にもっさりしている気がしないでもないですが、QGIS起因なのかどうなのか・・?パフォーマンス検証もしてみたいところです。

明日は@yuskesuzki@githubさんの記事です。乞うご期待!

5
0
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
5
0