Pythonで「GeoDjango」を触ってみる

#### 自己紹介

・福岡県福岡市在住
・実家はぶどう園
・趣味はギター、ピアノ
・経歴
　　コルセン→運用・保守→インフラ→開発→マネジメント系を浅く広く・・・
・今年３月から参画させていただいてます！！

#### Goal設定

・GISの学習（過去にArcGISで森林資源管理システムをいじったことがあったので思い出しながら）を通して、GISの活用事例を模索する。
・Python、GeoDjangoを学んだ結果をアウトプット（使えそうか、他の代替フレームワークはあるかなど）

#### GISとは何か？？

GIS（Geometric Information System:地理情報システムの略）、緯度軽度などの地理情報の空間データを利用・加工・可視化して分析することで、水害・台風といった防災、公共施設やランドマークといった都市計画などなど、幅広く活用される情報、またそれを利用したシステムのこと

#### GIS用語

・ベクターデータ
点（ポイント）、線（ライン）、ポリゴン（面）の要素からなる、現実世界に存在する地物を表現するデータのこと、拡大しても画質が劣化しない。
![スクリーンショット 2023-06-12 17.37.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/4139af3c-1cc0-7182-d43f-9a28cbe9309f.png)

　それぞれが何を示すか代表例を幾つか
　ポイント　・・・　電柱、ATM、信号など
　ライン ・・・ 道路、河川、鉄道路線など
　ポリゴン ・・・ 建物、行政界など

・ラスターデータ
行と列の格子状（グリッド）に並んだセル（ピクセル）で表されるデータのこと。それぞれのマス目に色を持たせて表現していて、航空写真、人工分布、降水量など時間による変化を示すデータとして使われることが多い。
![スクリーンショット 2023-06-12 17.59.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/97f7f153-3c44-6a28-2d66-36dbd6777aa2.png)

・シェープファイル
GISデータのフォーマットの一つ。上記のベクターデータといった属性情報を格納するデータ。
色々な機関からシェープファイル形式のデータが販売・提供されている。

シェープファイルは以下の主なファイル形式で構成される。
　①.shp　・・・　対象の地形や図形情報を持つメインファイル（道路のラインなど）
　②.dbf　・・・　メインファイルの属性情報を持つ属性ファイル（その道路がどんなものか、高速道路、一般道など）
　③.shx　・・・　メインファイルと属性ファイルを繋げるインデックスファイル
　
・GeoJSONファイル ・・・シェープファイルで取り扱う地理空間情報などをjson形式で取り扱うことができるようにしたもの、Webアプリではこちらが主流

参照：GIS基礎解説（esriジャパン)
https://www.esrij.com/getting-started/gis-guide/

#### GeoDjangoとは
> GeoDjangoはDjangoに標準で含まれている地理空間データ用のモジュールのこと。
GeoDjangoモジュールを使うことで地理情報システム (GIS) のWebアプリケーションの作成が可能になります。

参照：GeoDjangoではじめる地理空間情報（GitBook）
https://homata.gitbook.io/geodjango/geodjango/geodjango

#### 環境周りの確認

GeoDjangoアプリ構築のため以下のライブラリ群が必要です。
詳細は割愛しますが、GISデータを変換したり処理するのに必要で、参照するDBエンジンによって利用ライブラリが違うようです。※画像参照
・GDAL
・GEOS
・PROJ4
・PostGIS
![スクリーンショット 2023-06-12 18.23.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/fd62b4f0-a584-651c-1a11-953955666517.png)

#### バージョン
環境は以下の通り
・Python 3.9.6
・Django 4.2.2
・PostgreSQL 14.8
・GDAL 3.6.4
・GEOS 3.11.2
・PROJ4 9.2.1
・PostGIS 3.3.3

#### Django起動

terminalで以下を叩く

```
% django-admin startproject mapapp
% cd mapapp
% ./manage.py runserver 
```


http://127.0.0.1:8000/ でローカルにアクセス、以下の表示が出ればOK。

![スクリーンショット 2023-06-13 10.14.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/e2b83869-df78-cb0f-34ad-1f1f1fcdba01.png)


DBを準備していく

```
% createdb -U postgres mapdb
% createdb -U postgres postgisdb #PostGIS拡張用
% psql -U postgres postgisdb
```

ユーザ（一旦）作成と権限付与

```
postgisdb=# CREATE USER dbuser WITH PASSWORD 'passwords';
CREATE ROLE
postgisdb=# GRANT ALL PRIVILEGES ON DATABASE postgisdb TO dbuser;
GRANT
```

setting.pyを編集

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',         # GIS機能を追加
    'mapapp',                    # 作成したアプリを追加
]
```

DB向先変更
```
DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'HOST': 'localhost',
        'NAME': 'postgisdb',
        'USER': 'dbuser',
        'PASSWORD': 'passwords',
    }
}
```

他（文字コード、時刻）

```
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
```

マイグレーション可能か確認
```
% ./manage.py makemigrations
% ./manage.py migrate
```

PostGISの拡張エラーが出たので、PostgreSQL接続して拡張機能の有効化を実施

```
postgisdb=# CREATE EXTENSION postgis;
CREATE EXTENSION
postgisdb=# CREATE EXTENSION postgis_topology;
CREATE EXTENSION
postgisdb=# CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION
postgisdb=# CREATE EXTENSION postgis_tiger_geocoder;
```

うまくいった様子

```
% ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
　　　　〜略
  Applying sessions.0001_initial... OK

```

Djangoの管理ユーザを作成
```
 % ./manage.py createsuperuser 
ユーザー名 (leave blank to use 'wtanaka2'): tanakamasa
メールアドレス: m_tanaka@dohous.co.jp
Password: 
Password (again): 
```

以下サイトからレイヤーファイルをとってくる（今回はgeojson形式で）
URL：https://nlftp.mlit.go.jp/

今回はランキング見て行政区域がTop1だったのでダウンロードする（福岡県を取得する）
![スクリーンショット 2023-06-13 12.13.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/cf6233c0-2dd3-3b60-cba8-08a4f7ae92b6.png)

中身はこんな感じです。上記で説明した.shpとか.dbf形式のファイルが入ってます。
![スクリーンショット 2023-06-13 12.15.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/64a0cde3-9470-079c-a2a9-26a51e7be2c3.png)

今回はこの中の.shpをdjangoプロジェクト直下に移動して、以下GeoDangoのmodel確認コマンドを叩いてみます。

```
% ./manage.py ogrinspect --srid=4326 N03-23_40_230101.geojson fukuoka
class fukuoka(models.Model):
    n03_001 = models.CharField(max_length=0)
    n03_002 = models.CharField(max_length=0)
    n03_003 = models.CharField(max_length=0)
    n03_004 = models.CharField(max_length=0)
    n03_007 = models.CharField(max_length=0)
    geom = models.PolygonField(srid=4326)
```

それぞれ先述のサイトで定義がわかります。
![スクリーンショット 2023-06-13 12.28.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/d5b0afd2-fafd-0329-a93f-fbcf219342ff.png)


定義通りにmodel.pyを作成
```
from django.contrib.auth import get_user_model
from django.contrib.gis.db import models

class Border(models.Model):
    id      = models.AutoField(primary_key=True)
    n03_001 = models.CharField(verbose_name='都道府県名', null=True, max_length=10)
    n03_002 = models.CharField(verbose_name='支庁名', null=True,max_length=20, blank=True)
    n03_003 = models.CharField(verbose_name='群・政令市名', null=True,max_length=20, blank=True)
    n03_004 = models.CharField(verbose_name='市区町村名', null=True,max_length=20, blank=True)
    n03_007 = models.CharField(verbose_name='行政区域コード', null=True,max_length=5)
    geom = models.PolygonField(srid=4612)
    objects = models.Manager()

    def __str__(self):
        return "%s_%s_%s" % (self.n03_001,self.n03_003,self.n03_004)

    class Meta:
        verbose_name = "行政区域"
        verbose_name_plural = "行政区域一覧"
```

続いてmigrate

```
% ./manage.py makemigrations mapapp
% ./manage.py migrate 
```

geojsonファイル（行政区域レイヤ）をDBに取り込むためのローダーを作成する。

```
import os
from django.contrib.gis.utils import LayerMapping
from testmap.models import Border

# ModelとGeojsonをマッピングする
mapping = {
    'n03_001' : 'N03_001',
    'n03_002' : 'N03_002',
    'n03_003' : 'N03_003',
    'n03_004' : 'N03_004',
    'n03_007' : 'N03_007',
    'geom' : 'POLYGON',
}

# geojsonファイルを参照して
geojson_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'N03-23_40_230101.geojson'))

# 実行処理を記述
def run(verbose=True):
    lm = LayerMapping(Border, geojson_file, mapping, transform=False, encoding='UTF-8')
    lm.save(strict=True, verbose=verbose)
```

ローダーを実行
```
% ./manage.py shell
>>> from . import load_fukuoka
>>> load_fukuoka.run()
```

管理画面にアクセスする準備をして

```
from django.contrib import admin                                                                                                         
from django.contrib.gis import admin as geoadmin                                                                                         
from . import models
# Border
admin.site.register(models.Border, geoadmin.OSMGeoAdmin)
```

ローカルSRV起動
```
% ./manage.py runserver
```

ログイン画面が出るので、作成した管理ユーザでアクセス
![スクリーンショット 2023-06-13 14.36.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/aabb7e6c-844b-ab80-7eb2-b1dbea2aabf8.png)

ログインするとMAPAPP - 行政区域一覧ができている
![スクリーンショット 2023-06-13 14.37.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/d96feebe-e545-0b7f-c31d-fbde9df54ac6.png)

福岡県の市区町村が表示される
![スクリーンショット 2023-06-13 14.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/25dee843-5fb1-af1f-337e-bd794c288c0b.png)

福岡県_福岡市_中央区を表示してみる
![スクリーンショット 2023-06-13 14.38.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/6f961af0-05ba-03da-a532-5986172d3487.png)

図形を変更→保存することができる（本当は行政区域なので公式にいじったりはできませんが・・）
![スクリーンショット 2023-06-13 14.39.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/d2bb5570-8c87-9ce8-29f8-5c117152e07d.png)

再アクセスすると変更後の区域で表示される
![スクリーンショット 2023-06-13 14.40.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476147/533fc7ab-b848-e06d-bc7c-5b4bb29cf5c1.png)

#### 終わりに
環境周り（主に依存関係）で躓くことが多かったですが、何とかなりました。
上記のようにベースマップに対して、レイヤーマップを重ねて、オリジナルのマップも作成できそうな感じでした。
GISデータには国勢調査データもあるみたいなので、マーケティング戦略などにも使えるかもしれません、⚪︎代男性子ありが多い福岡県の地域を可視化して表示とか。

あとは一般ユーザ向けにバス停マップ、ピンポイントな嗜好マップ（釣り、食べ物、サバゲー）なども作れそうです。アイディア次第では色んなことができそうでした。

GeoDjango構築してみて、環境周りはさておき割とすんなり構築できた気がします。
GISをすぐ動かしてみたい人で無料でやりたい人はおすすめです（ArcGISとかQGISという有料系もあります）。
FlaskでもGISやってみた記事はありましたが、情報は少なかったです。

ライブラリでは「GeoPandas」、「Folium」とかが有名なようなので機会があればもう少し見てみたいです。
今後は発展させて、もう少し凝ったアプリをトライしてみたいなと思いました。

以上、「PythonでGeoDjangoを触ってみる」でした。

参考元記事：
[MacOS上にGeodjango+PostGIS環境を構築](https://qiita.com/yasushi00/items/3c944a4f63b132054b41)
[HomebrewでPostGISをインストール、利用するまで](https://qiita.com/Mitsunori_Tsukada/items/4b62562930f5865bdcf5)
[【解説編】GISで扱うデータのファイルフォーマット（シェープファイル）](https://note.com/kinari_iro/n/n93c85c88b77d)
[GeoDjangoチュートリアル](https://homata.gitbook.io/geodjango/geodjango/tutorial)