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は当然空間検索ができるのでそういうった機能と組み合わせていくと楽しいかなと思います。それではみなさんよいクリスマスをお過ごしください。

PostGISとGeoDjangoを使ってLeafletでGeoJSON Tile Layerを表示してみる(3) – GeoDjangoの管理画面をカスタマイズ –

前回でGeoDjangoへのデータ登録が完了しましたので、ようやく管理画面から登録したデータを表示させてみましょう。Djangoと言えば、モデル定義だけすれば管理画面でCRUD操作ができてしまうというデータメンテなんて管理画面だけで充分だという人には嬉しい機能があります。それが地図オブジェクトでもできちゃうのだぜというのが今回の記事です。

Continue reading →

PostGISとGeoDjangoを使ってLeafletでGeoJSON Tile Layerを表示してみる(2) – GeoDjango経由でShapefileをインポートする –

前回でようやくPostGISとGeoDjangoのインストールが完了しましたので、それでは早速Djangoを使ってみましょう。ただ、今回はDjangoと言っても、GeoDjangoなので、できればデータがあったほうがいいですよね。ということで、国民の味方、国土交通省の国土数値情報を使わせて頂くことにしましょう。

Continue reading →

PostGISとGeoDjangoを使ってLeafletでGeoJSON Tile Layerを表示してみる(1) – インストール編 –

いつのまにやらOpenLayersじゃなくてLeafletとかでてきたり、DjangoでPostGISが使えるとか、知らないことが多すぎたので時代に追いつけ追い越せということで試してみました。なお、そこそこの長編になりましたので分割してお送りします。

Continue reading →