GeoJSONのエクスポートとWeb地図アプリケーションの作成

はじめに

PostGISに貯めてあるデータを地図アプリで見れるようにしてみます。地図ライブラリにはLeafletを使い、背景地図には地理院タイルを使います。

PostGISに貯めているデータはGeoJSON形式でエクスポートし、jQueryを使ってロードして、地図上に表示します。

データ

今回は、国土数値情報(道の駅データ)を使います。国土数値情報ダウンロードサービスからダウンロードし、シェープファイルのデータをインポートしてみよう コマンドライン編またはシェープファイルのデータをインポートしてみよう GUI編を参考に、PostGISデータベースにインポートして下さい。

テーブル名は、GUIでインポートしたらp35-18_roadside_stationとなったので、そのまま使っています。インポートして生成されたテーブルのテーブル名が違う場合には、ご自身のテーブル名を優先して下さい。

GeoJSONデータの作成

GeoJSON ?

たとえばGeoJSON フォーマット仕様の、最初の「例」を見て下さい。

まず最上位を見てみます。

{  
  "type": "FeatureCollection",  
  "features": [(地物1), (地物2), ...]  
}  

FeatureCollectionという単一のオブジェクトがあり、featuresプロパティに地物の配列が入っています。

地物は次のようになっています。

{  
  "type": "Feature",  
  "geometry": (ジオメトリ),  
  "properties": {(key1): (value1), ...}  
}  

geometryプロパティにはジオメトリを、propertiesプロパティには属性を表現するハッシュを、それぞれ入れます。

ジオメトリはST_AsGeoJSON()で得られる文字列にあたります。

広島県だけにします

どうも全国データをそのまま出すと読み切れないようなので、広島県だけ抽出するようにします。このため、WHERE節でp35_005 LIKE '34%'という条件を設定し、市区町村コードの上2桁(=県コード)が34のものだけを抽出するようにしています。

Featureの部品を表示する

JSON化せずに、type, properties, geometry を出してみます。

SELECT  
  'Feature' AS type,  
  json_object(ARRAY['name', p35_006, 'mcode', p35_005]) as properties,  
  ST_AsGeoJSON(geom)::json AS geometry  
FROM "p35-18_roadside_station"  
WHERE  p35_005 LIKE '34%'  

  type   |                      properties                       |                        geometry                        
---------+-------------------------------------------------------+---------------------------------------------------------  
 Feature | {"name" : "リストアステーション", "mcode" : "34210"}  | {"type":"Point","coordinates":[133.0713142,34.784591]}  
...  

typeは文字列型です。propertiesgeometryは文字列型でなくJSON型です。

FeatureCollectionの部品を表示する

SELECT  
  'FeatureCollection' AS type,  
  json_agg(Q1)::JSON AS features  
FROM (  
  SELECT  
    'Feature' AS type,  
    json_object(ARRAY['name', p35_006, 'mcode', p35_005]) as properties,  
    ST_AsGeoJSON(geom)::json AS geometry  
  FROM "p35-18_roadside_station"  
  WHERE  p35_005 LIKE '34%'  
) AS Q1  

       type        |                                                                          features                    
-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------  
 FeatureCollection | [{"type":"Feature","properties":{"name" : "リストアステーション", "mcode" : "34210"},"geometry":{"type":"Point","coordinates":[133.0713142,34.784591]}},   +  
                   |  {"type":"Feature","properties":{"name" : "遊YOUさろん東城", "mcode" : "34210"},"geometry":{"type":"Point","coordinates":[133.2757124,34.88967]}},         +  
                   |  {"type":"Feature","properties":{"name" : "さんわ182ステーション", "mcode" : "34545"},"geometry":{"type":"Point","coordinates":[133.2998686,34.6734859]}}, +  
                   |  {"type":"Feature","properties":{"name" : "豊平どんぐり村", "mcode" : "34369"},"geometry":{"type":"Point","coordinates":[132.4446982,34.6468579]}},        +  
                   |  {"type":"Feature","properties":{"name" : "来夢とごうち", "mcode" : "34368"},"geometry":{"type":"Point","coordinates":[132.2698295,34.5744153]}},          +  
                   |  {"type":"Feature","properties":{"name" : "よがんす白竜", "mcode" : "34204"},"geometry":{"type":"Point","coordinates":[132.913945,34.5064709]}},           +  
                   |  {"type":"Feature","properties":{"name" : "アリストぬまくま", "mcode" : "34207"},"geometry":{"type":"Point","coordinates":[133.3173083,34.394684]}},       +  
                   |  {"type":"Feature","properties":{"name" : "スパ羅漢", "mcode" : "34213"},"geometry":{"type":"Point","coordinates":[132.1042513,34.389119]}},               +  
                   |  {"type":"Feature","properties":{"name" : "ゆめランド布野", "mcode" : "34209"},"geometry":{"type":"Point","coordinates":[132.7970555,34.8557897]}},        +  
                   |  {"type":"Feature","properties":{"name" : "ふぉレスト君田", "mcode" : "34209"},"geometry":{"type":"Point","coordinates":[132.8494852,34.8990665]}},        +  
                   |  {"type":"Feature","properties":{"name" : "クロスロードみつぎ", "mcode" : "34205"},"geometry":{"type":"Point","coordinates":[133.1446622,34.5115157]}},    +  
                   |  {"type":"Feature","properties":{"name" : "舞ロードIC千代田", "mcode" : "34369"},"geometry":{"type":"Point","coordinates":[132.5412326,34.6745412]}},      +  
                   |  {"type":"Feature","properties":{"name" : "北の関宿安芸高田", "mcode" : "34214"},"geometry":{"type":"Point","coordinates":[132.6810513,34.7210499]}},      +  
                   |  {"type":"Feature","properties":{"name" : "湖畔の里福富", "mcode" : "34212"},"geometry":{"type":"Point","coordinates":[132.7759856,34.5322334]}},          +  
                   |  {"type":"Feature","properties":{"name" : "たけはら", "mcode" : "34203"},"geometry":{"type":"Point","coordinates":[132.9121681,34.3439384]}},              +  
                   |  {"type":"Feature","properties":{"name" : "みはら神明の里", "mcode" : "34204"},"geometry":{"type":"Point","coordinates":[133.1038862,34.3968282]}},        +  
                   |  {"type":"Feature","properties":{"name" : "たかの", "mcode" : "34210"},"geometry":{"type":"Point","coordinates":[132.8815981,35.024058]}},                 +  
                   |  {"type":"Feature","properties":{"name" : "世羅", "mcode" : "34462"},"geometry":{"type":"Point","coordinates":[133.0841,34.59638]}},                       +  
                   |  {"type":"Feature","properties":{"name" : "びんご府中", "mcode" : "34208"},"geometry":{"type":"Point","coordinates":[133.235633,34.56977]}}]  

typeは文字列型です。featuresは文字列型でなくJSON型です。

json_agg()の引数には、Q1を指定しています。これで、typepropertiesgeometryからなる行を個別のJSONオブジェクトにし、全ての行をまとめてJSON配列にしています。

FeatureCollectionを表示する

最終的には、次のクエリでGeoJSONが出力できます。

SELECT to_json(Q) FROM (  
  SELECT 'FeatureCollection' AS type,  
    json_agg(Q1)::JSON AS features  
  FROM (  
    SELECT  
      'Feature' AS type,  
      json_object(ARRAY['name', p35_006, 'mcode', p35_005]) as properties,  
      ST_AsGeoJSON(geom)::json AS geometry  
    FROM "p35-18_roadside_station"  
    WHERE  p35_005 LIKE '34%'  
  ) AS Q1  
) AS Q;  

to_json()の引数にQを指定していますが、上述のjson_agg()のような集約関数でなく、Qの1行をひとつのJSONオブジェクトにします。

結果をUTF-8でファイルに出力する

% psql -t -A -d (データベース名)  
db=# \encoding UTF-8  
db=# \o (出力ファイル名)  
db=# SELECT ...  
db=# \q  

psql -t -A-tは結果行だけを表示するオプションで、-Aは桁そろえ無しで表示するオプションです。

\encoding UTF-8で出力エンコーディングを、Shift JISではなく、UTF-8にしています。

\o data.json等とすると、data.jsonというファイルが作られます。その直後にSELECTを実行すると、結果が、結果行だけ、桁そろえ無しで、UTF-8でファイルに保存されます。

\qで終了します。

スクリプト

Leafletを使って地図表示するスクリプトの例を示します。このスクリプトは、同じフォルダにdata.jsonが用意できていなければなりません。

<!DOCTYPE html>  
<html>  
<head>  
<meta charset="UTF-8">  
<title>Leaflet 地図</title>  
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>  
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css">  
<script src="https://unpkg.com/leaflet@1.2.0/dist/leaflet.js"></script>  
<style>  
html, body, #MAP {  
  margin: 0;  
  padding: 0;  
  width: 100%;  
  height: 100%;  
  position: relative;  
}  
</style>  
<script src="index.js">  
var map;  
// クリックしたときにproperties.nameを表示する  
// https://leafletjs.com/examples/geojson/  
function onEachFeature(feature, layer) {  
  var str = "";  
  if (feature.properties && feature.properties.name) {  
    str = feature.properties.name;  
  }  
  layer.bindPopup(str);  
}  

// $.getJSON()のイベントハンドラ (JSONが読み込まれたら呼ばれる)  
// 引数 data にはオブジェクトが入る  
function Data_OnLoad(data) {  
  L.geoJson(  
    data,  
    {onEachFeature: onEachFeature}  
  ).addTo(map);  
}  

// jQuery読み込み完了イベントハンドラ  
$(document).ready(  
  function() {  
    // Leafletのセットアップ  
    // https://github.com/gsi-cyberjapan/gsitiles-leaflet/blob/gh-pages/index.html  
    map = L.map("MAP");  
    L.tileLayer(  
      "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",  
      {  
        attribution: "<a href=\"https://maps.gsi.go.jp/development/ichiran.html\" target=\"_blank\">地理院タイル</a>"  
      }  
    ).addTo(map);  
    map.setView([35.3622222, 138.7313889], 5);  
    // JSONのロード  
    $.getJSON("data.json", Data_OnLoad)  
  }  
);  
</script>  
</head>  

<body>  
<div id="MAP"></div>  
</body>  
</html>  

$.getJSON()はローカルも読めるがChromeは起動オプションが必要

jQueryの$.getJSON()は、CORSで引っかかります。CORSは"Cross-Origin Resource Sharing"の略です。の説明を参照して下さい。

jQueryの$.getJSON()は、FirefoxやEdge上においては、ローカルファイルを取得できますが、Chrome上においては、CORSで引っかかります。
CORSについてはhttps://developer.mozilla.org/ja/docs/Web/HTTP/CORS 等を参照して下さい。

回避するには、オプションに--allow-file-access-from-filesを指定して起動します。
https://qiita.com/takahiro_itazuri/items/a80a0b3f285d5ada4af7 を参照して下さい。

できた!

こんなかんじになります。

おわりに

PostGISを使ってGeoJSON (FeatureCollection)を生成しようとして、ST_AsGeoJSON()だけでなく、PostgreSQLのJSON関数も併用したクエリを作り、Leafletを使って地図Webアプリとして表示する方法を示しました。

とりあえず動くはずですので、お試しください。

GeoJSON生成クエリのところは、説明が行き届いておりませんが、具体的にどう行き届いていないのか、よく分かっていないという状況です。

出典

この記事では国土政策局「国土数値情報(道の駅データ)」 ( http://nlftp.mlit.go.jp/ksj/ ) を使用しました。

この記事では国土地理院「地理院タイル」 ( https://maps.gsi.go.jp/development/ichiran.html )を使用しました。

本記事のライセンス


この記事は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。