SVGMapはラスタとベクタを良い感じに使い分ける面白い地図ライブラリだそうです


[SVGMap](https://www.svgmap.org/)という面白い地図描写フレームワークを教えてもらったので試す。



# 概要

SVGMapは、次の2点が非常に特徴的です。何より、15年も前にそれを作っていたという点については驚きを隠せません。

- 分散的に配置されたレイヤをカプセル化・クライアントサイドで合成する[Decentralized Web Mapping](https://satakagi.github.io/mapsForWebWS2020-docs/De-centralizedWebMapping.html)の戦略
- [Quad Tree Composite Tiling](https://satakagi.github.io/mapsForWebWS2020-docs/QuadTreeCompositeTilingAndVectorTileStandard.html)という作者が提唱したタイリング手法で、データの密度に応じてラスタタイルとベクタタイルを切り替える

>![Propose an encapsulated web app that handles layers](https://satakagi.github.io/mapsForWebWS2020-docs/imgs/dcwm_lawa.png)

>![Quad Tree Composite Tiling](https://satakagi.github.io/mapsForWebWS2020-docs/imgs/tile_qtct.png)

…

マイナーなようで、検索に引っかかる情報量が少ない一方、wikiの情報量が多いので、NotebookLMの様なサービスに[wiki - 解説書](https://www.svgmap.org/wiki/index.php?title=%E8%A7%A3%E8%AA%AC%E6%9B%B8)、[wiki -  内部機構解説](https://www.svgmap.org/wiki/index.php?title=%E5%86%85%E9%83%A8%E6%A9%9F%E6%A7%8B%E8%A7%A3%E8%AA%AC)を読ませて情報を容易に引き出せる様にすると良いかもしれません。


[こちら](https://svgmap.org/devinfo/devkddi/lvl0.1/demos/demo0.html)にデモが用意されており、どのようなものか試すことができます。

例えば、令和2年の医療機関データ（国土数値情報から）は18万行あります。

デモでそれに対応する(何年のデータか確認していませんが)「病院」、「診療所（一般）」、「歯科診療」をすべて表示しても軽快に動く事が分かります。

その他、このライブラリを使った実例として[マイナビニュース - SVGMap」とは？ ハイパーレイヤリングを使った災害情報表示システム ](https://news.mynavi.jp/article/20240621-2970589/)がなどもあるようです。

…

「ラスタライズすることによって軽量化を図る」という例はもちろん他にあり、Holoviz社の[Datashader](https://datashader.org/)は有名でしょう。

（蛇: [Qiita - 大量のデータを画像化して描写するDatashader使ってみる](https://qiita.com/matsu3365/items/298f2b228f5d0a850e4d)）

これの凄まじさは[dash-world-cell-towers](https://dash.gallery/dash-world-cell-towers/)([github](https://github.com/plotly/dash-world-cell-towers))の様なダッシュボードを見ればわかります。

ただ、サーバーサイドでのラスタライズが走るため、少々もたつきを感じ、また大規模になればなるほどサーバー側の負荷増大も考えられます。（先にタイル作っておけば良いともいう？？）
（world cell towerでは「描写領域にいくつのデータがあるか」でラスタライズするか決定している、そもそも方針が若干違うともいう）

そのため、SVGMapはうまく使えばとても有用なのかな？と思っています。

## チュートリアルのセットアップ（ほどんど唯一の使い方に関しての情報源）

https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB

だいたいの使い方が網羅されているようです。

細々した内容は、[解説書](https://www.svgmap.org/wiki/index.php?title=%E8%A7%A3%E8%AA%AC%E6%9B%B8)と実際のコードを突き合わせ見ていくほかないようです。

**ファイルのダウンロード**

面倒なので一気にやってしまう。

~~~bash
for i in `seq 5` ;
do 
    wget -r "https://svgmap.org/devinfo/devkddi/tutorials/tutorial${i}"
done

for n in geojson1 tiling1 mesh1 mesh2 mesh2b mesh3 mesh3b wms1 wms2 geojson2 vectorService1
do
    wget -r "https://svgmap.org/devinfo/devkddi/tutorials/${n}"
done
~~~

**webサーバー建てる**

ファイルに直でアクセスすると[`Reason: CORS request not HTTP`](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default)
が発生するため、適当なwebサーバー噛ませる。

~~~bash 
cd svgmap.org/devinfo/devkddi/tutorials
docker run --rm -d -p 8080:80 -v `pwd`:'/usr/share/nginx/html' nginx
~~~
とか
~~~bash
cd svgmap.org/devinfo/devkddi/tutorials
python3 -m  http.server 8080
~~~

<http://localhost:8080/tutorial1/tutorial1.html>の様にアクセス。


直下のindex.htmlに各チュートリアルをリストアップしておくと便利（お好みで）
(index.html消して`autoindex on;`指定しても良いし…)

~~~bash
# お好みで.
# 以下の様にリストアップしています（順番がめちゃくちゃ）
# find -mindepth 2 -maxdepth 2  -name "*.html" | grep -v dynamicOSM_r11 | xargs -I{} echo '<li><a href="{}">{}</a></li>'
cat << EOF > index.html
<!doctype html>
<html>
<head>
 <meta charset="utf-8">
 <title>Tutorials page</title>
</head>
 
<body>
 <h1>This is tutorials dir</h1>
    <ul>
        <li><a href="./mesh3/mesh3.html">./mesh3/mesh3.html</a></li>
        <li><a href="./tutorial1/tutorial1.html">./tutorial1/tutorial1.html</a></li>
        <li><a href="./wms1/wms1.html">./wms1/wms1.html</a></li>
        <li><a href="./tiling1/tiling1.html">./tiling1/tiling1.html</a></li>
        <li><a href="./tiling1/simpleTiling.html">./tiling1/simpleTiling.html</a></li>
        <li><a href="./tutorial5/tutorial5.html">./tutorial5/tutorial5.html</a></li>
        <li><a href="./geojson1/geojson1.html">./geojson1/geojson1.html</a></li>
        <li><a href="./geojson1/geoJsonExample1.html">./geojson1/geoJsonExample1.html</a></li>
        <li><a href="./mesh2b/mesh2b.html">./mesh2b/mesh2b.html</a></li>
        <li><a href="./mesh2b/meshTileViewerB.html">./mesh2b/meshTileViewerB.html</a></li>
        <li><a href="./vectorService1/vectorService1.html">./vectorService1/vectorService1.html</a></li>
        <li><a href="./vectorService1/CanadianGeoNames.html">./vectorService1/CanadianGeoNames.html</a></li>
        <li><a href="./geojson2/geojson2.html">./geojson2/geojson2.html</a></li>
        <li><a href="./geojson2/geoJsonExample2.html">./geojson2/geoJsonExample2.html</a></li>
        <li><a href="./mesh1/ArrayMesh.html">./mesh1/ArrayMesh.html</a></li>
        <li><a href="./mesh1/mesh1.html">./mesh1/mesh1.html</a></li>
        <li><a href="./mesh2/mesh2.html">./mesh2/mesh2.html</a></li>
        <li><a href="./mesh2/meshTileViewer.html">./mesh2/meshTileViewer.html</a></li>
        <li><a href="./wms2/wms2.html">./wms2/wms2.html</a></li>
        <li><a href="./wms2/wmsController.html">./wms2/wmsController.html</a></li>
        <li><a href="./mesh3b/mesh3b.html">./mesh3b/mesh3b.html</a></li>
        <li><a href="./mesh3b/rasterMeshI.html">./mesh3b/rasterMeshI.html</a></li>
        <li><a href="./tutorial3/tutorial3.html">./tutorial3/tutorial3.html</a></li>
        <li><a href="./tutorial4/tutorial4.html">./tutorial4/tutorial4.html</a></li>
        <li><a href="./tutorial2/tutorial2.html">./tutorial2/tutorial2.html</a></li>
    </ul>    
</body>
</html>
EOF
~~~

# データ作成ツール

shapefileなどから拡張されたSVGファイルを作成するツール

これを使わず、ライブラリにgeojsonなどを読ませることもできる。（[開発者向けチュートリアル６](https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB6)参照）

また、チュートリアルではメッシュやポリゴンはほとんどsvg以外で読ませていたが、このツールを使っても作成できる様子。

[github - svgMapTools](https://github.com/svgmap/svgMapTools)

ツールビルド

~~~bash
git clone https://github.com/svgmap/svgMapTools.git
cd svgMapTools/
docker run \
    --rm --name svgmaptool-build \
    -v `pwd`:/TARGET \
    -w /TARGET \
    --user 1000:1000 \
    maven:3.9.8-amazoncorretto-17 \
    mvn package

~~~

実行


~~~bash
$ docker run \
    --rm --name svgmaptool-build \
    -v `pwd`:/TARGET \
    -w /TARGET/tools  \
    maven:3.9.8-amazoncorretto-17 ./Shape2SVGMap.sh -h
Shape2SVGMap: ShapeをSVGMapに変換します。
Copyright 2007-2023 by Satoru Takagi @ KDDI All Rights Reserved.
----------
java Shape2SVGMap [Options] (input.shp|input.csv) [output.svg]
input.(shp|csv|json) : ソースファイル指定。csvに関しては-csvschena説明参照。json:geoJson
output.svg : 変換先明示。無い場合拡張子除きソースと同じパスで変換

Options   : -optionName (value)
-proj     : 正距方位図法の標準緯線を指定
            center:地図中央を基準緯線に
            値:基準緯線値を指定値に
            デフォルト:0 = 赤道
-height   : SVG高さの指定
            数値：SVGの高さの値を指定。
            数値x：地理座標(緯度経度)×[数値]倍
            デフォルト:100x :地理座標の100倍値(SVGTで10cmまで表現可)
-xy       : XY座標系(日本測地系)の指定
            1～19：XY系のときに、その番号を指定[m]単位 
            デフォルト:0 = XY系ではなく緯度経度として扱う
            SPECIAL:-1～-19：[mm]単位
-color    : 図形の色設定 (ラインの場合は線色、ポリゴンの場合は塗色)
            #000000~#ffffff:色を指定 , (noneで色無し)
            属性名:指定した属性の値で可変する。属性の型により自動色設定
                  文字列: 文字列に応じたランダムな塗り
                  数値  : 数値の大きさに応じた塗り(赤：ff：max,00:min)
            属性番号:指定した属性番号の値で可変する。属性の型により自動
              どんな属性があるかは、-showheadオプションで起動すればわかります。
            デフォルト:#00FF00(green) 
...etc...

~~~


# 参考

- [SVGMap homepage](https://www.svgmap.org/)
- [SVGMap wiki](https://www.svgmap.org/wiki/index.php?title=%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)
  - [wiki - 解説](https://www.svgmap.org/wiki/index.php?title=%E8%A7%A3%E8%AA%AC%E6%9B%B8)
  - [wiki - 内部機構解説](https://www.svgmap.org/wiki/index.php?title=%E5%86%85%E9%83%A8%E6%A9%9F%E6%A7%8B%E8%A7%A3%E8%AA%AC)
  - [wiki - 開発者向け チュートリアル](https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB)
- [W3C - SVG](https://www.w3.org/Graphics/SVG/)
- [Holoviz - Datashader](https://datashader.org/)


# MEMO

申し訳程度の自分用備忘録として

チュートリアルのおさらい(簡単な前提)

- SVGMapフレームワークはwindow全体に地図コンテンツが占有される形で表示する設計
- Containers.svgに読み込むsvgを定義する
- WebApp Layerは、svgがhtmlを参照し、そのhtmlはコントロールのための要素定義とJSを呼びを行う



## チュートリアル6　geojson1のコードのdataPaths辞書に適当なデータを追加する。

[国土数値情報ダウンロードサイト - 用途地域データ](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A29-2019.html)を描写してみます。

（データの選定に意味はありません）

**geoJsonExample1.js**

~~~javascript
var dataPaths = {
	"A34b-180316.geojson":"世界遺産構成資産範囲ポリゴン（代表点）",
	"A34a-180316.geojson":"世界遺産構成資産範囲ポリゴンデータ",
	"A34d-180316.geojson":"世界遺産構成資産範囲ライン（代表点）",
    // ..etc..
	"A29-19_23202.geojson":"用途地域" // add
};
~~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472791/ff67d94d-bff9-506f-7a4e-baa46fdf50ec.png)

geojsonは、描写タイミングで塗りの色をCSSカラーで指定している。
他に、geojsonのproperties以下に`marker-color`など指定することによっても色指定できる様子（つまりfeaturesごとに色を変えれる）

---

## 上のチュートリアル6を適当にiframeで埋める。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472791/3d5dfd80-6643-a70c-79ee-6be1d296c133.png)


<details>
<summary>適当に生成させたHTML</summary>

~~~html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bootstrap Card Layout Example</title>
    <!-- Bootstrap CSS from Bootswatch (Solar Theme) -->
    <link href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css" rel="stylesheet">
    <style>
        html, body {
            height: 100%;
            margin: 0;
        }
        .container-fluid {
            height: 100%;
            display: flex;
            flex-direction: column;
        }
        .row.flex-grow-1 {
            flex-grow: 1;
            display: flex;
            margin-top: 2%;
        }
        .card {
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .card-body {
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="#">Title</a>
    </nav>
    <div class="container-fluid">
        <div class="row flex-grow-1">
            <div class="col-6">
                <div class="card">
                    <div class="card-body">
                        <iframe src="http://localhost:8080/geojson1/geojson1.html" width="100%" height="100%"></iframe>
                    </div>
                </div>
            </div>
            <div class="col-6">
                <div class="card">
                    <div class="card-body">Graph</div>
                </div>
            </div>i/tutorials/${n}"
        </div>
    </div>

    <!-- Bootstrap JS and dependencies -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
~~~

</details>

~~iframe使わず、カード内にdiv設置してべタ書きすると、描写範囲はカード内になるが、マップの中央とかレイヤ設定ウィンドウがカードサイズを無視してページ全体になる？？なにか間違えた？~~

→間違えた。

> SVGMapの埋め込み(SVGMapFrame)
実験的機能段階 :
SVGMapフレームワークはwindow全体に地図コンテンツが占有される形で表示する設計です。ウェブページはwebAppsとしてカプセル化されたレイヤーをsvgMapオブジェクトに差し込む形でアプリケーションが構築されます。すなわち、基本的にwindowグローバル空間にはsvgMapオブジェクトだけが単独で存在し、その他のアプリケーションをwindow上に実装する場合は、その開発者がsvgMapオブジェクトとのグローバル空間でのスパゲッティ化に注意を払いながら開発していく必要があります。
それに対して、SVGMapLv0.1_Frame*.jsが提供する機能は、window内の指定した領域にsvgMapによる地図を埋め込むことを可能にします。さらにこの埋め込まれたsvgMapオブジェクトを　外部からより簡単に操作できるようカプセル化されたインターフェースを提供します。 
https://www.svgmap.org/wiki/index.php?title=%E8%A7%A3%E8%AA%AC%E6%9B%B8#SVGMap.E3.81.AE.E5.9F.8B.E3.82.81.E8.BE.BC.E3.81.BF.28SVGMapFrame.29

wiki->解説書->SVGMapの埋め込み(SVGMapFrame)から svg-map要素を参照

## svg-mapタグ使ってみる

svg-mapタグでの埋め込みは、親文書にsvgmapのエトセトラをぶちまけなくてすみ、かつ各svgmapのレイヤーに親文書からlayerタグでデータを受け渡せるのがメリットのようだが、受け渡す必要がなければ不要かも。
shadow domとして動いているらしい。boostrapのカードに埋めたりすると大きさがおかしくなる。height="100%"とかが効かない？？pixel単位での指定は可能。

https://svgmap.org/devinfo/devkddi/lvl0.1/svgMapComponent/tests/mapFrame_r4.html
にサンプルがある。
この辺がコードのようだが、これをそのまま持ってきてローカルで動くようにはなっていない様子。（色々足りない。上のサンプルをwget -r + 404してるファイルを取得すれば動きはする）
[github - svgMapLv0.1/experimental/webComponent/mapFrame_r4.html](https://github.com/svgmap/svgMapLv0.1/blob/master/experimental/webComponent/mapFrame_r4.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472791/bc1a50de-60da-a3a1-33c8-bf13b4457834.png)


## フィルタ処理を書く

チュートリアル6に対し、以下の様な追記を行ってフィルター処理の様なことをする

十分軽快なのでする必要がないといえばないが…

geoJsonExample1.html

~~~html
<select id="hogeSelect" onchange="changeHoge()"></select>
~~~

geoJsonExample1.js

~~~js
pref = [
  '奈良県',
  '兵庫県', 
  '滋賀県、京都府',
  //...etc...
  '福岡県'
]
//...etc...
function changeHoge(){
	var path = dataSelect.options[dataSelect.selectedIndex].value;
	var hoge = hogeSelect.options[hogeSelect.selectedIndex].value;
	loadAndDrawGeoJson(path,hoge);

}
async function loadAndDrawGeoJson(dataPath,hoge=''){
	var gjs = await loadJSON(dataPath);
	if (hoge != ''){
		gjs.features = gjs.features.filter((i) => i.properties.A34b_005 == hoge)
	}
  //...etc...
}
function buildDataSelect(){
//...etc...
  for (const a of pref){
		hogeSelect.insertAdjacentHTML('beforeend', '<option value="' + a +'" >'+a+'</option>');
	}
}
~~~


![svgmap.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472791/b39b93fe-c372-cc18-01a3-e7c2f14e0f04.gif)

## そのほか雑多なメモ

SVGMap.captureGISgeometries((arg)=>{hogehoge(arg)})とデータを吐けるが、？？？

…

>refreshScreen()：画面の再描画を実行する。SVGMap0.1*.jsでは、再描画は伸縮スクロール時以外には基本的に行われません。（通常のブラウザのDOMの描画と大きく異なる点のひとつ。）webAppsなどで動的にSVGMapコンテンツのDOMを変更し、伸縮スクロールを待たずに再描画を行いたい場合は、明示的にrefreshScreen関数を呼び出す必要があります。 

…

webappとしてカプセル化するという話は[wiki - チュートリアル8 WebApp Layer メッシュデータ](https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB8),[チュートリアル14 WebApp Layer ベクトル地理情報サービスの結合](https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB14)を見ると分かりやすい。

hoge.svgが読みこむhoge.htmlを定義。さらに、hoge.htmlに埋め込まれるhoge.jsを定義しどういう処理をするのか定義してる。

[チュートリアル14](https://www.svgmap.org/wiki/index.php?title=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB14)の例では、読み込んだgeojsonのプロパティ読んで`marker-color`要素を追加している。
この要素が、geojsonを描写する関数で参照されている様子。

~~~js

function setMagColors(features){ // [[解説書#drawGeoJson]]のスタイリング仕様を使い、マグニチュードに応じた色を付ける
	features.sort(function(a,b){ //マグニチュード昇順でソート
		return(a.properties.mag - b.properties.mag);
	});
	
	for ( var feature of features){
		var cmag = feature.properties.mag;
		// マグニチュード3...7でクリッピング
		cmag = Math.max(3,cmag);
		cmag = Math.min(7,cmag);
		// 色相(hue)に変換し、そこからRGBカラーを生成
		var hue = (7-cmag)/(4)*240;
		var rgb = svgMapGIStool.hsv2rgb(hue,100,100);
		console.log(rgb);
		if ( rgb){
			feature.properties["marker-color"]=`#${rgb.r.toString(16).padStart(2, '0')}${rgb.g.toString(16).padStart(2, '0')}${rgb.b.toString(16).padStart(2, '0')}`;
		}
	}
	console.log(features);
}
~~~

ドキュメントには書いてなかった気がするが、feature.properties.marker-colorがそれらしい

コード中に下のような記述はあった。



~~~js

		function putPolygon(
			coordinates,
			svgImage,
			crs,
			fillColor,
			metadata,
			parentElm,
			metaDictionary
		) {
///...etc...
			if (!fillColor) {
				fillColor = "orange";
			}

			if (metastyle.styles.fill) {
				fillColor = metastyle.styles.fill;
			}
//...etc...
		// geoJsonのpropertyに以下の予約語が入っていたらスタイルと見做す(mapboxのgeojson拡張Simplestyleをベース)
		// See https://github.com/mapbox/simplestyle-spec
		// この実装では、opacity追加、"marker-size"の実装をどうしようか考え中です・・
		var styleDict = {
			title: 0,
			description: 1,
			"marker-size": 2,
			"marker-symbol": 3,
			"marker-color": 4,
			stroke: 5,
			"stroke-width": 6,
			fill: 7,
			opacity: 8,
		};

~~~

~~~js

		function putPoint(
			coordinates,
			svgImage,
			crs,
			POIiconId,
			poiTitle,
			metadata,
			parentElm,
			metaDictionary
		) {

//..etc...
			if (metastyle.styles.fill) {
				fill = metastyle.styles.fill;
			}
			if (metastyle.styles["marker-color"]) {
				fill = metastyle.styles["marker-color"];
			}
~~~

...

チュートリアルで点と線以外はcsvやgeojsonなどからデータをインポートしていて、？？？となった。（svgでhtml参照し、参照されているhtmlはjs読んで、そのjsはcsv読んでいるという…）
が、読めないわけでも作れないわけでもなく、そのほうが扱いやすいからの様子。


当たり前といえば当たり前だがキャッシュは止めておいたほうが良い

https://zenn.dev/mindwood/articles/ba4594c53a93b0