Web地図からベクタデータをエクスポートしてOpenOrienteering Mapperにインポートする

↓ の記事でQGISを使って入手していたベクタデータについて、ブラウザだけで入手できるやり方が見えたので、noteにメモを取りながら試していきます。

概要

ベクタデータの入手方法

MapLibre に標準で備わっている querySourceFeatures() 機能を使います。この機能で、地図画面の表示範囲に含まれる、指定したソース・ソースレイヤの地物をGeoJSON形式で取得できます。
ファイルが複数に分かれていると地図ソフトにインポートするとき手作業の手間が発生するので、あらかじめ用意した取得対象リストのデータをすべて取得して結合してGeoJSONファイルでダウンロードするところまでボタン1クリックでできるようにJavaScriptを書きます。

対象ソース

以下の4つのソースからデータを入手します。いずれも出典記載で印刷や再配布可能のデータです(2025年1月時点の情報です。利用の際は各自あらためて利用規約を確認してください)。

OpenOrienteering Mapperへのインポート

今回のソースの組み合わせに合わせて記号の置き換えルールをテキストで書いたCRT(相互参照テーブル)ファイルを作成し、インポート時に自動で記号を割り当てられるようにします。
ターゲットは1:10000のISOM 2017-2とします。
記号ウィンドウにある502 Wide roadを複製して、国土地理院ベクトルタイルに含まれる道路の太さの段階それぞれに割り当てるための記号をあらかじめ用意しておきます。

作業

地物ごとの取得元

とりあえず以下のようにします。

  • 101 主曲線

    • ソース

      • 標高タイル(のラスタ画像から maplibre-contour でベクタに変換されたソース)

    • ソースレイヤ

      • contours

  • 102 計曲線

    • ソース

      • 標高タイル(のラスタ画像から maplibre-contour でベクタに変換されたソース)

    • ソースレイヤ

      • contours

  • 208 岩石地

    • ソース

      • OpenFreeMap(以下ofm)

    • ソースレイヤ

      • landcover

  • 210 礫地

    • ソース

      • 地理院ベクトルタイル(以下gsivt)

    • ソースレイヤ

      • landforma

  • 213 砂地

    • ソース

      • ofm

    • ソースレイヤ

      • landcover

  • 301.1 渡れない水域

    • ソース

      • gsivt

    • ソースレイヤ

      • waterarea

  • 301.4 水涯線

    • ソース

      • gsivt

    • ソースレイヤ

      • coastline

      • river

      • lake

  • 304 渡れる水路

    • ソース

      • gsivt

    • ソースレイヤ

      • river

  • 306 細いもしくは季節的水路

    • ソース

      • gsivt

    • ソースレイヤ

      • river

  • 307.1 渡れない湿地

    • ソース

      • gsivt

    • ソースレイヤ

      • landforma

  • 401 開けた土地 Open land

    • ソース

      • ofm

    • ソースレイヤ

      • landcover

  • 403 開けた土地 Rough open land

    • ソース

      • ofm

    • ソースレイヤ

      • landcover

  • 405 森

    • ソース

      • ofm

    • ソースレイヤ

      • landcover

  • 408 植生 (走行困難)

    • ソース

      • ofm

    • ソースレイヤ

      • landcover

  • 412 耕作地

    • ソース

      • 筆ポリゴン

    • ソースレイヤ

      • farmland

  • 502 広い道路

    • ソース

      • gsivt

    • ソースレイヤ

      • road

  • 503 道路

    • ソース

      • gsivt

    • ソースレイヤ

      • road

  • 505 徒歩道

    • ソース

      • gsivt

    • ソースレイヤ

      • road

  • 509 線路

    • ソース

      • gsivt

    • ソースレイヤ

      • railway

  • 520 立ち入り禁止区域

    • ソース

      • ofm

    • ソースレイヤ

      • landuse

  • 521 建物

    • ソース

      • gsivt

    • ソースレイヤ

      • building

  • 522 天蓋

    • ソース

      • gsivt

    • ソースレイヤ

      • building

  • 532 階段

    • ソース

      • gsivt

    • ソースレイヤ

      • road

取得対象のソース・ソースレイヤの組み合わせ

上に書いたのをソースごとにまとめました。

  • 標高タイル(のラスタ画像から maplibre-contour でベクタに変換されたソース)

    • contours

  • 筆ポリゴン

    • farmland

  • OpenFreeMap

    • landcover

    • landuse

  • 国土地理院ベクトルタイル

    • building

    • coastline

    • lake

    • landforma

    • railway

    • river

    • road

    • waterarea

CRTファイル

とりあえずこんな感じにまとめてみました。

# 等高線
101 ele AND level = 0

# 計曲線
102 ele AND level = 1

# 岩石地
208 class = rock AND subclass = scree

# 砂礫地
210 ftCode = 7403

# 砂地
213 class = sand

# 水域面
301.1 ftCode = 5000

# 水涯線
## 河川
301.4 ftCode = 5201 OR ftCode = 5202 OR ftCode = 5203
## 湖池
301.4 ftCode = 5231 OR ftCode = 5232 OR ftCode = 5233
## 海岸線
301.4 ftCode = 5101 OR ftCode = 5102 OR ftCode = 5103

# 一条河川
304 ftCode = 5301

# 枯れ川
306 ftCode = 5302

# 湿地
307.1 ftCode = 7401

# 開けた土地 Open land
401 class = meadow AND subclass != golf_course AND subclass != scrub AND subclass != park

# 開けた土地 Rough open land
403 class = grassland OR class = heath OR class = logging OR class = grass

# 森
405 class = forest OR class = wood OR class = park OR subclass = park

# 植生(走行困難)
408 subclass = scrub OR wood:density = dense

# 耕作地
412 筆ポリゴン

# 舗装区域
501.1 class = pitch OR class = track

# 広い道路
## 幅員区分5.5m-13m未満。トンネルを除外。
502.55 rnkWidth = 2 AND ftCode != 2704 AND ftCode != 2714
## 幅員区分13m以上19.5m未満。トンネルを除外。
502.13 rnkWidth = 3 AND ftCode != 2704 AND ftCode != 2714
## 幅員区分19.5m以上。トンネルを除外。
502.195 rnkWidth = 4 AND ftCode != 2704 AND ftCode != 2714

# 道路
## 幅員区分 3m未満。徒歩道・石段・トンネルを除外。
503 rnkWidth = 0 AND NOT ftCode ~= 272 AND NOT ftCode ~= 273 AND NOT ftCode ~= 4
## 幅員区分 3m以上5.5m未満。石段・トンネルを除外。
503 rnkWidth = 1 AND NOT ftCode ~= 273 AND NOT ftCode ~= 4

# 徒歩道
505 ftCode = 2721 OR ftCode = 2722 OR ftCode = 2723

# 電車。地下部分・索道を除外
509 ftCode = 8201 AND railState != 2 AND railState != 3 AND NOT rtCode ~= 40206

# 索道・送電線
510 rtCode ~= 40206 OR ftCode = 8202

# 立入禁止
520 class = residential OR class = neighbourhood OR class = suburb OR class = commercial OR class = industrial OR class = cemetery OR class = hospital OR class = school OR class = railway OR class = university OR class = kindergarten OR class = military OR class = retail

# 建物
521.3 ftCode = 3101 OR ftCode = 3102 OR ftCode = 3103

# 無壁舎
522.1 ftCode = 3111 OR ftCode = 3112

# パイプライン
528 ftCode = 5321

# 階段
532 ftCode = 2731 OR ftCode = 2732 OR ftCode = 2733


OpenOrienteering Mapperの記号セットに道路を追加

地理院ベクトルタイルの道路区分ごとに、5.5m、13m、19.5mの幅の道路の記号を作ります。今回1:10000がターゲットなので地図上の寸法はそれぞれ0.55mm, 1.3mm, 1.95mmです。幅5m以下だと記号コード503の道路になります。

## 幅員区分5.5m-13m未満。トンネルを除外。
502.55 rnkWidth = 2 AND ftCode != 2704 AND ftCode != 2714
## 幅員区分13m以上19.5m未満。トンネルを除外。
502.13 rnkWidth = 3 AND ftCode != 2704 AND ftCode != 2714
## 幅員区分19.5m以上。トンネルを除外。
502.195 rnkWidth = 4 AND ftCode != 2704 AND ftCode != 2714


作成した記号セットはOpenOrienteering Mapperのインストールフォルダ > symbol sets > 10000 に置くと新しい地図を作成画面の記号セット一覧に表示されるようになります。

ダウンロード機能の実装

querySourceFeatures 機能を使って実装を書いていきます。

まずは動作確認と練習のために、シンプルに取得したものをコンソールログに出してみます。以下のように書けそうです。

  const features = map.querySourceFeatures(ソース名, {
    sourceLayer: レイヤ名,
  });

  console.log(features);

ボタンクリックで、OpenFreeMapのopenmaptilesソースからwaterレイヤを取得するようにしたデモを貼ります。

ボタンクリックで取得したデータをF12の開発者ツールで確認できます。

querySourceFeaturesについてWeb上に情報が少なく、ChatGPTに聞いても知識が不十分だったので、いろいろ検証していきます。以下のようなことがわかりました。

  • ドキュメントからはquerySourceFeaturesの2つめの引数を省略できるように見えるし実際省略できる(エラーが出ない)が、フィルタをかけずに全データを取得できるわけではなく、空っぽのレスポンスが返ってくる。

  • addSource で地図に追加しただけのソースからは地物を取得できない。そのソースを使ったレイヤが地図に追加されている必要がある。レイヤが1つでも追加されていれば、それとは異なるソースレイヤの地物も取得できる。

  • 一度の実行でソースレイヤは複数指定できない。

  • maplibre-contour でクライアント側で生成した等高線も他と同様に取得できる。取得したjsonのpropertiesのlevel要素で通常の等高線と計曲線を区別できる。

ソースレイヤごとの実行が必要なので、ソースレイヤごとに繰り返して結果を結合する処理を作成します。こういうのはChatGPTにお願いすればあっという間ですね。

const layers = [
  {
    source: "gsivt",
    sourceLayer: [
      "building",
      "coastline",
      "lake",
      "landforma",
      "railway",
      "river",
      "road",
      "waterarea",
    ],
  },
  {
    source: "contour-source",
    sourceLayer: ["contours"],
  },
  {
    source: "fude",
    sourceLayer: ["farmland"],
  },
  {
    source: "ofm",
    sourceLayer: ["landcover", "landuse"],
  },
];


  let allFeatures = [];

  layers.forEach((layer) => {
    layer.sourceLayer.forEach((sourceLayerName) => {
      const features = map.querySourceFeatures(layer.source, {
        sourceLayer: sourceLayerName,
      });

      allFeatures = allFeatures.concat(
        features.map((feature) => feature.toJSON())
      );
    });
  });

という具合に進めて、以下のようにまとまりました。地図の表示部分は ISOMizer を使っています。このデモで「Download GeoJSON」をクリックすると表示範囲内の地物を一括ダウンロードできるので、OpenOrienteering Mapper へのインポートするところの動作確認にも使ってみてください。


動作確認

それでは、作成した GeoJSON Downloader で出力したファイルをOpenOrienteering Mapperにインポートするところを動作確認していきます。

せっかくだから行ったことないところがいいなということで、「郵便番号ガチャ」というものを使ってランダムなエリアで試すことにします。

場所が決まりました。

OriLibre で見に行ってみたところ、等高線がバグってるけど山・市街地・耕作地が含まれていていい感じ。この位置でGeoJSONを出力することにします。ついでにOriLibreの磁北取得機能で地図を回してNOAAのAPIが提供する磁気偏角の値を確認してみたところ「8.8度」でした。国土地理院の計算サイトだと「8.65度」。

ダウンロードしたら、ファイル容量43MB、End Of Fileまで2,038,838行のけっこう大きいGeoJSONがダウンロードされました。
スクショに写っている、列挙されている地理座標の測地系はWGS84(EPSG: 4326)です。

そして、OpenOrienteering Mapper を開き、「新しい地図を作成…」で、先ほど作成した記号セットを選んで開き、ファイルメニューから「インポート」でダウンロードしたGeoJSONを開きます。

ジオリファレンスではとりあえず私の場合、WGS84とJGD2011は同じという前提で日本の平面直角座標系をEPSGで入れています。今回の場合は10系なので、EPSG:6678を指定します。それとさっき確認した磁気偏角を入力します。

新しい記号を割り当て画面では、CRTファイルを開く…で先ほど作成したCRTファイルを開きます。


OpenOrienteering Mapperにインポートできました。耕作地だけ広い範囲に表示されているのは、筆ポリゴンのベクトルタイルがズームレベル13のタイルを拡大して表示しているからで、想定どおりの挙動です。
それと、耕作地の上に表示されている紫色のエリア記号は、OpenFreeMapから取得された耕作地です。耕作地のデータは筆ポリゴンで間に合っているのでこれはスパッと削除します。
等高線がバグっているのと左上の等高線が取得できていないのは標高ゼロの海が近いのと関係あるのだろうか?

で、未割り当ての紫の記号の削除と、最背面を立禁色で塗りつぶし(斜面はすでに森林で白塗りされている) を編集しただけで、こんな感じにO-Mapっぽくなりました。

等高線がバグっていることに気づいた時点で違う範囲に変えればよかったなってことで、もう一箇所。今度はタイムを計測してみます。ダウンロードボタンと同時にタイマースタートで、同じように不要な情報を削除して背面を立禁色で塗りつぶすまでで終了です。

今度は長野。EPSGコードは6676、磁気偏角は8.2のようです。


原図のダウンロードからここまでのタイム計測の結果は、のんびり作業して3分10秒でした。慣れればもっと縮められます。従来のやりかたでのCRTファイルも筆ポリゴンも使わない基盤地図情報からのインポートと比べたら、調査・作図の開始までの下準備の単純作業が数時間単位での時短になりそうです。

いいなと思ったら応援しよう!