前回で管理画面までできました。ようやくメインとなるブラウザ側と連携するところを実装したいと思います。ところでDjangoでGeoJSONってどうやるんだろうか。そんなところから今回は始まります。
GeoJSONとは
そもそもGeoJSONってなんなのよという話ですが、Wikipediaさんによると…
GeoJSONはJavaScript Object Notationを用いて空間データをエンコードし非空間属性を関連付けるファイル形式である。 属性にはポイント(住所や座標)、ライン(各種道路や境界線)、 ポリゴン(国や地域)などが含まれる。 他のGISファイル形式との違いとして、Open Geospatial Consortiumではなく世界各地の開発者達が開発し管理している点で異なる。
要は空間データを扱えるようにしたJSONですね、詳細は以下にフォーマットが定義されています。
The GeoJSON Format Specification
GeoDjangoでGeoJSONを出力する
GeoDjangoの機能を試す
GeoDjangoのドキュメントを調べてみるとそれっぽいメソッドがあったので試してみました。
$ python manage.py shell Python 2.7.6 (default, Mar 22 2014, 22:59:56) Type "copyright", "credits" or "license" for more information. IPython 2.3.1 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: from demo.models import Border In [2]: print(Border.objects.filter(id=73274).geojson()[0].geojson) {"type":"Polygon","coordinates":[[[123.75450033,24.06346143],[123.75468433,24.06348943],[123.75469133,24.06332543],[123.75459233,24.06329143],[123.75450033,24.06346143]]]} In [3]:
あれ? 属性(properties)が出力されませんね。どうも開発版には新しいAPIがあるみたいなんですけど、今はコレしか出来ないみたいですね。ポリゴン情報はコレを使って後は自前でどうにかするしかないのかなと考えていたんですが、念の為プラグインないかなと探してみたらdjango-geojsonというのがあるじゃないですか。
django-geojsonのインストール
pipでインストール。
$ sudo pip install django-geojson Downloading/unpacking django-geojson Downloading django-geojson-2.6.0.zip Running setup.py (path:/tmp/pip_build_root/django-geojson/setup.py) egg_info for package django-geojson Requirement already satisfied (use --upgrade to upgrade): Django in /usr/local/lib/python2.7/dist-packages (from django-geojson) Requirement already satisfied (use --upgrade to upgrade): six in /usr/lib/python2.7/dist-packages (from django-geojson) Installing collected packages: django-geojson Running setup.py install for django-geojson Successfully installed django-geojson Cleaning up...
django-geojsonの有効化
プロジェクト設定を編集してプラグインを追加します。
$ vi geodjango/settings.py
以下の一行をINSTALLED_APPSの末尾に追加します。
INSTALLED_APPS = ( 'django.contrib.gis', 'demo', 'leaflet', 'djgeojson', # add )
では、試してみましょう。
$ python manage.py shell Python 2.7.6 (default, Mar 22 2014, 22:59:56) Type "copyright", "credits" or "license" for more information. IPython 2.3.1 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: from demo.models import Border In [2]: from djgeojson.serializers import Serializer as GeoJSONSerializer In [3]: print(GeoJSONSerializer().serialize(Border.objects.filter(id=73274))) {"crs": {"type": "link", "properties": {"href": "http://spatialreference.org/ref/epsg/4326/", "type": "proj4"}}, "type": "FeatureCollection", "features": [{"geometry": {"type": "Polygon", "coordinates": [[[123.75450032600007, 24.063461431000064], [123.75468432500007, 24.06348943100005], [123.75469132500008, 24.063325431000067], [123.75459232600008, 24.063291431000035], [123.75450032600007, 24.063461431000064]]]}, "type": "Feature", "properties": {"n03_007": "47381", "n03_004": "\u7af9\u5bcc\u753a", "n03_003": "\u516b\u91cd\u5c71\u90e1", "n03_002": "", "n03_001": "\u6c96\u7e04\u770c", "model": "demo.border"}, "id": 73274}]} In [4]:
キタ━(゚∀゚)━! 日本語が文字化けしてるけど構造は問題なさそうなのでコレを使いましょう。
GeoDjangoでGeoJSONを出力するWebAPIを作る
それではさっそく、ブラウザ側にGeoJSONを返すWebAPIを作成してみましょう。
アプリ作成
demoのままでもいいんですが、気持ちを改めて「api」という名前でアプリ作成します。するとアプリ名のディレクトリが作成され、作成したディレクトリ配下に次のようなファイルが自動生成されます。
$ python manage.py startapp api
URLマッピングを定義
vi geodjango/urls.py
from django.conf.urls import patterns, include, url from django.contrib.gis import admin admin.autodiscover() urlpatterns = patterns('', url(r'^admin/', include(admin.site.urls)), url(r'^api/', include('api.urls', namespace='api')), # add )
vi api/urls.py
# -*- coding: utf-8 -*- from django.conf.urls import patterns, url from djgeojson.views import GeoJSONLayerView from demo.models import Border urlpatterns = patterns('', url(r'^v1/borders.geojson$',GeoJSONLayerView.as_view(model=Border), name='borders'), )
なお、通常はapi/views.pyに関数定義するのが一般的なようですが、今回はdjango-geojsonのページのサンプルに従いapi/urls.pyでGeoJSONLayerViewを直接呼んでいます。さて、これで「/api/v1/borders.geojson」へのアクセスで全登録データがGeoJSONで出力されるようになりました。
DjangoでCORSを無効化する
Web-APIのテストでいつもハマるのがCORS(Cross-Origin Resource Sharing)ですね。Django側で全部配置しておけば解決するんでしょうが、最初のうちは分けて試しがちです。とりあえず今回はプラグインで回避しましょう。
django-cors-headersのインストール
pipでインストール。
$ sudo pip install django-cors-headers Downloading/unpacking django-cors-headers Downloading django-cors-headers-0.13.tar.gz Running setup.py (path:/tmp/pip_build_root/django-cors-headers/setup.py) egg_info for package django-cors-headers Installing collected packages: django-cors-headers Running setup.py install for django-cors-headers Successfully installed django-cors-headers Cleaning up...
django-cors-headersの有効化
プロジェクト設定を編集してプラグインを追加します。
$ vi geodjango/settings.py
INSTALLED_APPS = ( 'django.contrib.gis', 'demo', 'leaflet', 'djgeojson', 'corsheaders',# add ) MIDDLEWARE_CLASSES = ( 'corsheaders.middleware.CorsMiddleware', # add 'django.middleware.common.CommonMiddleware', # add ) CORS_ORIGIN_ALLOW_ALL = True # add
さて、いよいよ、クライアント側の作成です。Django側でやるのはわからないので今回は別に作ります。
LeafletとjQueryのダウンロード
以下、サイトからLeafletとjQueryの最新版をダウンロードして任意の場所に展開します。
初めてのLeaflet
ダウンロードしたファイルをこんな形で配置します(お好みですが)。
$ tree -c leaflet-demo/ leaflet-demo/ ├── index.html ├── js │ ├── jquery-2.1.1.min.js │ ├── leaflet.js │ └── leaflet-src.js ├── css │ └── leaflet.css └── images ├── layers-2x.png ├── layers.png ├── marker-icon-2x.png ├── marker-icon.png └── marker-shadow.png
index.htmlをこんな内容で書きましょう
<!DOCTYPE html> <html> <head lang="ja"> <meta charset="UTF-8"> <title>Leafletテスト</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/leaflet.css" /> </head> <body> <div id="map" style="width: 960px; height: 600px"></div> <script src="js/leaflet.js"></script> <script src="js/jquery-2.1.1.min.js"></script> <script> var map = L.map('map'); L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', { maxZoom: 30, attribution: 'Map data © ' + '<a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery © <a href="http://mapbox.com">Mapbox</a>', id: 'examples.map-i875mjb7' }).addTo(map); map.setView([35.85556, 139.65117], 15); </script> </body> </html>
それではブラウザで開いてみましょう。
はい、地図が見えますね。
ベースはこれでOKですのでいよいよ、GeoJSONでデータを取得してみましょう。なお、下記コード中の取得先URLのIPは各自の環境で変更してください。
<!DOCTYPE html> <html> <head lang="ja"> <meta charset="UTF-8"> <title>Leafletテスト</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/leaflet.css" /> </head> <body> <div id="map" style="width: 960px; height: 600px"></div> <script src="js/leaflet.js"></script> <script src="js/jquery-2.1.1.min.js"></script> <script> var map = L.map('map'); L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', { maxZoom: 30, attribution: 'Map data © ' + '<a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery © <a href="http://mapbox.com">Mapbox</a>', id: 'examples.map-i875mjb7' }).addTo(map); map.setView([35.85556, 139.65117], 15); /* GeoJSONレイヤーを追加します */ var layer = L.geoJson(); map.addLayer(layer); $.getJSON("http://x.x.x.x:8000/api/v1/borders.geojson", function (data) { layer.addData(data); }); </script> </body> </html>
それではブラウザで開いてみましょう。え、さっきと変わらないですって?暫くお待ち下さい。そうですね、大体5分位ですかね。マシンが悲鳴をあげなければ表示されます。
はいキタ━(゚∀゚)━!とうとう、ここまで来ましたよ。いやー長かった。もう泣きたい。え? なんだか重い? それは貴方のPCの性能が悪いんじゃないですかね? 性能がいいのにモッサリしている? それはしょうがないじゃないですか、全件データ返すんですから。だって7万件ありますから。
え? そんなの実用的じゃないですって? じゃあTile Layerにしてみちゃいますか? (いよいよ最終回)