MacOS上にGeodjango+PostGIS環境を構築

# はじめに
 
MacOS上に、GIS(地理空間情報）を扱えるように、GeoDjango+PostGIS環境を構築した手順のメモです。MacOSのターミナル操作ができ、Django/PostGISはよくわからなくても構築できるレベルで記載。ただし途中でやり直したので、手順のズレや漏れがあるかもしれません。
（追記）バージョンを修正

* 環境
  * MacBook Pro (2020)
  * macOS BigSur 11.4
  * Homebrew 3.1.11
  
* インストールバージョン
  * Python 3.9.6
  * Django 3.2.6
  * PostgreSQL 13.4
  * PostGIS 3.1.2

# 環境構築

## Python

* ターミナルでまず xcode を導入し、python3.9を brew で入れる

```
% sudo rm -r /Library/Developer/CommandLineTools  # 念のため既存のコマンドラインツールを削除
% xcode-select --install    # コマンドラインツールをインストール
% brew install python@3.9       
```

**（注）　MacOSのアップデートにより、xcode-selectの再導入が必要になる場合がある**
（注）　この手順では、公式Python3のpkg版はアンインストールした方が無難
参考：https://code-graffiti.com/how-to-uninstall-official-python3-on-mac/

※ （省略可）pyenvでpythonのバージョンを切り替える場合

```
% brew update && brew upgrade pyenv # 念のためpyenvを更新
% pyenv install --list  # インストールできるバージョンの確認
% pyenv install 3.9.6   # バージョン指定して python をインストール
% pyenv global 3.9.6    # 全体で 3.9.6 を利用するように設定
% python -V             # pythonのバージョン確認
Python 3.9.6
% vi ~/.zprofile 
export PYENV_ROOT="$HOME/.pyenv"      # PATHを追加
export PATH="$PYENV_ROOT/bin:$PATH"   # PATHを追加
eval "$(pyenv init -)"
```

## Venv仮想環境

venvを使い、対象ごとにpython環境を管理

```
% python -m venv test        # 任意の環境名（ここではtest）を作成
% source test/bin/activate   # test環境に切り替え
(test) %
```

参考：　deactivate でもとの環境に戻る

## Django

test環境で、pip自身をアップグレードしてからDjangoを導入

```
(test) % pip install --upgrade pip
(test) % pip install django psycopg2 django-geojson
(test) % python -m django --version   # djangoのバージョン確認
3.2.6
```

## PostgreSQL+PostGIS

PostgraSQLデータベースと、そのGIS拡張のPostGISをインストール。

```
% brew install postgres
% brew install postgis
```

念の為 /usr/local/var/postgres を初期化し、postgresユーザを作成

```
% initdb /usr/local/var/postgres -E utf8
% createuser -s postgres
```

PostgreSQLを起動

```
brew services start postgresql
```

## GDAL

GISライブラリであるGDALをインストール

```
brew install gdal 
```

# 設定

## Django初期プロジェクト

空プロジェクトを作成し、そのまま起動

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

http://localhost:8000 にアクセスし、 ~~"The install worked successfully! Congratulations!"~~ 「インストールは成功しました！おめでとうございます！」もしくは Django administration のログイン画面が表示されれば、Djangoプロジェクトはできている。
確認できればCtrl-Cで終了。再起動時はvenvでpython環境を変えるを忘れずに。

## データベースの作成

通常のPostgreデータベースと、PostGIS拡張データベースを作成

```
% createdb -U postgres testdb     # 通常のデータベースの場合
% createdb -U postgres postgisdb  # DB作成後PostGIS拡張を適用する手順
% psql -U postgres postgisdb
psql (13.3)
Type "help" for help.

postgisdb=# CREATE EXTENSION postgis;
CREATE EXTENSION
postgisdb=# SELECT PostGIS_Version();
            postgis_version            
---------------------------------------
 3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
(1 row)
```

ついでに対象DBのみにアクセス権限を持つユーザも作っておく

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

## Djangoの設定

### testmap/setting.py

作成したプロジェクト配下のsetting.pyの以下の部分を編集

* GIS指定と、作成したプロジェクトのアプリを追加

```
INSTALLED_APPS = [ 
    'django.contrib.admin', 
    'django.contrib.auth',  
    'django.contrib.contenttypes',
    'django.contrib.sessions', 
    'django.contrib.messages', 
    'django.contrib.staticfiles', 
    'django.contrib.gis',         # 追加：GIS機能
    ’testmap',                    # 追加：作成したプロジェクト
]       
```

* データベースの設定

```
DATABASES = {    
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'HOST': 'localhost',    # DBホスト
        'NAME': 'postgisdb',    # データベース名
        'USER': 'dbuser',        # DBユーザ名
        'PASSWORD': 'passwords', # DBパスワード
    }
}
```

* 文字コードと時刻設定（必要に応じて）

```
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'   
```

### データベース設定

* Djangoが利用するテーブルを作成

```
% ./manage.py makemigrations
No changes detected
% ./manage.py migrate
Operations to perform:
    [中略]
  Applying sessions.0001_initial... OK
```

### Django管理ユーザを作成

```
% ./manage.py createsuperuser         
ユーザー名 (leave blank to use 'geojson'): testmap
メールアドレス: testmap@example.com
Password: 
Password (again): 
```

## geojsonデータを表示

### 表示データ

* 準備

サンプルとして、国土数値情報ダウンロードサービス http://nlftp.mlit.go.jp/ksj/index.html から行政境界（ポリゴン）をダウンロード。全国だとサイズが大きいので適当な都道府県（今回は奈良県）を選び、ダウンロードしたN03-20200101_29_GML.zipを展開。
含まれている N03_20_29_200101.geojson を djangoプロジェクト直下に配置

* 形式の確認

どのような要素があるか確認。

```
% ./manage.py ogrinspect --srid=4326 N03-20_29_200101.geojson Border          

# This is an auto-generated Django model module created by ogrinspect.
from django.contrib.gis.db import models

class Border(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)
    _color = models.CharField(max_length=0)
    _opacity = models.IntegerField()
    _weight = models.IntegerField()
    _fillcolor = models.CharField(max_length=0)
    _fillopacity = models.FloatField()
    geom = models.PolygonField(srid=4326)
```

※　データの座標系がJGD2011なので、世界座標系のsrid=4326を指定。一般にGPS測位はこれを利用。

### データモデル

testmap/models.py で行政境界のモデルを定義します。models.pyがなければ作成。
Django 3.2.4 ではワーニングがでるのでidを追加し、__unicode__を __str__ に修正。

```
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='都道府県名', max_length=10)
    n03_002 = models.CharField(verbose_name='支庁名', max_length=20, blank=True)
    n03_003 = models.CharField(verbose_name='群・政令市名', max_length=20, blank=True)
    n03_004 = models.CharField(verbose_name='市区町村名', max_length=20, blank=True)
    n03_007 = models.CharField(verbose_name='行政区域コード', 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 = "行政区域一覧"
```

マイグレーション実行しモデルで定義したテーブルを作成

```
% ./manage.py makemigrations testmap   # アプリを指定してマイグレーション作成
% ./manage.py migrate                  # マイグレーション実行
```

### データインポート

* geojsonファイルをデータベースに登録するスクリプト load_nara.py を作成

どこに作っても良いが、Djangoプロジェクト直下に置く。

```
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-20_29_200101.geojson'))

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

* Djangoシェルで、作成したスクリプトを実行

```
% ./manage.py shell
>>> from . import load_nara
>>> load_nara.run()
Saved: 奈良県__奈良市
   [snip]
>>> ^D
```

### 管理者画面で確認

testmap/admin.py に以下のように記述（なければ作成）。OSMGeoAdminで背景地図はOSMを指定。

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

テストサーバを起動

```
% ./manage.py runserver
```

http://localhost:8000/admin にアクセスし、createsuperuser で作成したユーザID/パスワードでログインします。
正しく設定ができていれば、管理サイトページに表示された行政区域一覧テーブルを選択すると、各市町村のレコードがあり、例えば生駒市を選択すると、下のように地図上に境界が表示される。

![スクリーンショット5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137490/452d4e8d-21ef-1599-c505-ac715ae309eb.png)


以上で環境構築とデータ投入の確認まで完了。

# アプリケーション作成

最低限の環境が構築できたので、この先は、geojsonデータを利用したアプリケーションやWebAPIをDjangoフレームワークを使い作って行きます。

# 参考

* GeoDjangoではじめる地理空間情報 https://homata.gitbook.io/geodjango/hajimeni/readme
* 測地系と座標系 https://homata.gitbook.io/geodjango/hajimeteno/coordinate