これは 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
を右クリック > 新規一般接続
に遷移します。
URLには以下を設定します。
http://localhost:8000/api/mvt/?tile={z}/{x}/{y}
このレイヤを表示させてみます。ちゃんと取得できていることがわかります。いい感じですね。
では、データの絞り込みを行ってみましょう。公式ドキュメントにある通り、URLクエリでカラム名を指定して取得するデータを絞ってみます。
今回は、日本のデータだけを取得します。モデルの中にname
フィールドがあるので、&name=Japan
をURLクエリに追加します。
http://localhost:8000/api/mvt/?tile={z}/{x}/{y}&name=Japan
表示できました。ちゃんと日本のデータだけ取得できているようです。
所感
今回はdjangorestframework-mvtを用いて、ベクトルタイルをGeoDjangoから動的に配信してみました。Djangoを使って動的にベクトルタイルを配信できるとなると、いろいろ使いどころがありそうです。
若干動作的にもっさりしている気がしないでもないですが、QGIS起因なのかどうなのか・・?パフォーマンス検証もしてみたいところです。
明日は@yuskesuzki@githubさんの記事です。乞うご期待!