🌏
Django (GeoDjango, Docker)で現在地に近い店舗検索できるようになるまでやったこと
最終的なゴールとしてはこんな感じの緯度経度を持ったモデルを作り
from django.contrib.gis.db import models
class Omise(models.Model):
location = models.PointField(null=True, blank=True, srid=4326, verbose_name='Location')
こんな感じでユーザから送ってもらう緯度経度を元に近いものを返すところをゴールとします。
res = (
Omise.objects.filter(location__distance_lte=(ref_location, distance))
.annotate(distance=Distance("location", ref_location))
.order_by("distance")
)
動作確認したバージョン
python: 3.9.12
Django: 4.0.4
GDAL: 3.2.2
GDAL を導入する
まず地点のデータを保存できるようにするために GDAL というものをインストールする必要があります。
今回は Docker を使っていたので次のように gdal 関連のもののインストールの記述を追加しました。
RUN apt-get update && apt-get install -y --no-install-recommends \
tzdata gdal-bin libgdal-dev
ARG CPLUS_INCLUDE_PATH=/usr/include/gdal
ARG C_INCLUDE_PATH=/usr/include/gdal
次に pip でインストールをしますが、この時 setuptools のバージョンを下げないと
Complete output (5 lines):
/usr/lib/python3.7/distutils/dist.py:274: UserWarning: Unknown distribution option: 'use_2to3_fixers'
warnings.warn(msg)
/usr/lib/python3.7/distutils/dist.py:274: UserWarning: Unknown distribution option: 'use_2to3_exclude_fixers'
warnings.warn(msg)
error in GDAL setup command: use_2to3 is invalid.
みたいな感じのエラーが出て途方に暮れます。なのでバージョンを下げてインストールします。
また、上記でインストールした GDAL のバージョンと pip でインストールするもののバージョンを揃える必要があるため GDAL==$(gdal-config --version)
という風にバージョンを指定してあげます。
# setuptools のバージョンを下げないと GDAL のインストールが失敗する
# https://qiita.com/nkmr_RL/items/85edc2ee68c01ec5582e
RUN pip install setuptools==57.4.0
RUN pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal"
Django の設定
次に settings.py の INSTALLED_APPS に GIS を加えます。
INSTALLED_APPS = [
...
"django.contrib.gis",
]
また、データエースのエンジンも変えておきます。
下記は MySQL の例ですが、ここは使っている DB に応じて適宜変えてください。
DATABASES = {
"default": {
"ENGINE": 'django.contrib.gis.db.backends.mysql',
...
}
}
モデルに地理情報を追加
PointField という緯度経度を Tuple で保存できるものでカラムを作ります。
from django.contrib.gis.db import models
class Omise(models.Model):
location = models.PointField(null=True, blank=True, srid=4326, verbose_name='Location')
ちなみに SRID というのは空間参照情報のことを指します(よく分かっていない)
実際にクエリを実行
最後にこんな感じで現在地の検索を行います。
お疲れ様でした。
from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.measure import D
# メートルを degree unit に変換する関数
# 参考: https://gis.stackexchange.com/questions/94645/geodjango-error-only-numeric-values-of-degree-units-are-allowed-on-geographic-d
def distance_to_decimal_degrees(distance, latitude):
lat_radians = latitude * (math.pi / 180)
# 1 longitudinal degree at the equator equal 111,319.5m equiv to 111.32km
return math.fabs(distance.m / (111_319.5 * math.cos(lat_radians)))
distance = distance_to_decimal_degrees(D(m=1000), latitude)
ref_location = Point(latitude, longitude)
res = (
Omise.objects.filter(location__distance_lte=(ref_location, distance))
.annotate(distance=Distance("location", ref_location))
.order_by("distance")
)
参考になったページ
Discussion