PostGISとGeoDjangoを使ってLeafletでGeoJSON Tile Layerを表示してみる(5) – GeoDjangoでTiled GeoJSONを出力する –

前回でようやく、PostGISに登録されているデータをGeoDjango経由でGeoJSONに出力してブラウザ上でLeafletを使って地図上に表示されるところまでできました。ところがどっこいデータ量が多いとモッサリしすぎて使いものにならないときたもんだ。ああ困ってしまった。ということで「それ、Tiled GeoJSONにすればいいじゃん?」という最終回です。

GeoDjangoでTiledなGeoJSONを出力する

実はですね、django-geojsonにはTiledGeoJSONLayerViewがいるんですよ。え? 知ってた? そんなキミの都合は知らんがな、これ使うようにするのにどんだけ苦労したと思ってんだ。いや、あんまりしてないけどね。

URLマッピングの定義変更

Tile用のURLマッピングを追加します。ちなみにこのワイルドカードでの指定、公式のサンプルの形式だと動きません。

vi api/urls.py
# -*- coding: utf-8 -*-
from django.conf.urls import patterns, url
from djgeojson.views import GeoJSONLayerView
from demo.models import Border
from api import views

urlpatterns = patterns('',
    url(r'^v1/borders.geojson$',GeoJSONLayerView.as_view(model=Border), name='borders'),
    url(r'^v2/borders/(\d+)/(\d+)/(\d+).geojson$',
        views.BorderTiledLayer.as_view(model=Border), name='tiledborders'),
)

続いてTiledGeoJSONLayerViewを継承したBorderTiledLayerクラスを定義します。

vi api/view.py
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from demo.models import Border
from djgeojson.views import TiledGeoJSONLayerView

class BorderTiledLayer(TiledGeoJSONLayerView):
    # Options
    srid=4612
    trim_to_boundary=False
    properties=('n03_001','n03_003','n03_004','n03_007')

直接呼んでもいいのですがオプションが多いので継承した方がシンプルです。因みにオプションの「trim_to_boundary」をFalseにしないと動かなかったのです、これも罠です。

さて、今度は、クライアント側のTile対応です。

Leaflet GeoJSON Tile Layerのダウンロード

Djangoだけでなく、Leafletもプラグインが存在しています。そして今回は「Leaflet GeoJSON Tile Layer」というその名の通りのプラグインを使います。

Leaflet GeoJSON Tile Layer

上記サイトから「TileLayer.GeoJSON.js」を取得して以下のように配置します(何度も言いますがお好みでよいのですが)。

$ tree -c leaflet-demo/
leaflet-demo/
├── js
│   ├── jquery-2.1.1.min.js
│   ├── TileLayer.GeoJSON.js
│   ├── leaflet.js
│   └── leaflet-src.js
├── index.html
├── css
│   └── leaflet.css
└── images
    ├── layers-2x.png
    ├── layers.png
    ├── marker-icon-2x.png
    ├── marker-icon.png
    └── marker-shadow.png

index.htmlをこんな内容で改造(「TileLayer.GeoJSON.js」のREADME.mdのまま)

<!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 src="js/TileLayer.GeoJSON.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 &copy; ' +
        '<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);

    var style = {
        "clickable": true,
        "color": "#00D",
        "fillColor": "#00D",
        "weight": 1.0,
        "opacity": 0.3,
        "fillOpacity": 0.2
    };
    var hoverStyle = {
        "fillOpacity": 0.5
    };

    var geojsonURL = 'http://192.168.1.15:8000/api/v2/borders/{z}/{x}/{y}.geojson';
    var geojsonTileLayer = new L.TileLayer.GeoJSON(geojsonURL, {
            clipTiles: true,
            unique: function (feature) {
                return feature.id; 
            }
        }, {
            style: style,
            onEachFeature: function (feature, layer) {
                if (feature.properties) {
                    var popupString = '<div class="popup">';
                    for (var k in feature.properties) {
                        var v = feature.properties[k];
                        popupString += k + ': ' + v + '<br />';
                    }
                    popupString += '</div>';
                    layer.bindPopup(popupString);
                }
                if (!(layer instanceof L.Point)) {
                    layer.on('mouseover', function () {
                        layer.setStyle(hoverStyle);
                    });
                    layer.on('mouseout', function () {
                        layer.setStyle(style);
                    });
                }
            }
        }
    );
    map.addLayer(geojsonTileLayer);
</script>
</body>
</html>
</body>
</html>

それではブラウザで開いてみましょう。

Leaflet_3

はいキタ━(゚∀゚)━! もうサックサクじゃないですかー。ついでにマウスオーバースタイルとかポップアップまでできちゃってますよー。すごい。もう感動しますわ。マジで。
とはいえ、流石にズームを引いていくと取得するTile数が増えるのでサーバ側が悲鳴を上げると思いますし、クライアント側の描画も辛くなっていくと思います。その辺りはまだまだ改善の余地があります。また、GeoDjangoは当然空間検索ができるのでそういうった機能と組み合わせていくと楽しいかなと思います。それではみなさんよいクリスマスをお過ごしください。