はじめに
htmlファイルを加工することで、自分の家や職場の回りを簡単に見られる地図を作りたいと思いましたので実験しました。下図地図のようなものを作成しました。各工程をメモします。
オンライン環境を前提にhtmlファイルを見ることにしました。
- ライブラリや地図データは外部のものを参照
- スタイルはなるべくhtml内に記述
- 任意の点をhtml内に記述して地図に表示(geojson形式で記載)
環境
作業環境
Windows PC で行いました。パソコンの調子がいまいちなので、Google ChromeでGithubページにアクセスし、Githubの開発モードで作業しました。
- Google Chrome
- Github Repository
使ったタイル
- 地理院タイル(標準地図) https://maps.gsi.go.jp/development/ichiran.html#std
この地理院タイルは基本測量成果(名称:電子地形図(タイル))なので、利用にあたっては、「国土地理院の地図の利用手続」に則した対応が必要になります。
追記: 『地理院タイルをウェブサイトやソフトウェア、アプリケーション上でリアルタイムに読み込んで利用する場合、地理院タイルは出典の明示のみで申請不要でご利用いただけます。』と説明があり、今回は出典の明示のみで利用出来そうです。
- 色別標高図 https://maps.gsi.go.jp/development/ichiran.html#relief
色別標高図は「基本測量成果以外で出典の記載のみで利用可能なもの」であるようです。
手順
ここでは実験した順に試作1~試作4を書いていますが、最終結果だけ試したい人は試作4に行ってください。
試作1: 簡単なウェブ地図
まずは二つのラスタタイルを重ねる地図を作ります。上に標高地図、下に地理院タイルをしいた地図を作成します。以前の記事(こちら)でも紹介した方法でプラグインを使っていますが、スタイルをhtmlファイル内に記載したりしています。htmlファイルだけで編集したいので、style.jsonを外部参照させたくなかったからです。このくらいのレイヤ数ならhtmlファイルに書いても問題ないなぁと思いました。
- 地図スタイルをhtmlファイル中で定義します。
- レイヤの切り替え(標高レイヤのオン・オフ)のため、MapLibreのプラグイン(watergisによるlegendプラグイン)を利用しました。
実際のコード
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Test- 標高地図01 </title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css' rel='stylesheet' />
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.js'></script>
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.js'></script>
<style>
body { margin:0; padding:0; }
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body onload="popupFunction()">
<div id='map'></div>
<script>
function popupFunction() {
alert("Test")
}
var map = new maplibregl.Map({
container: 'map',
attributionControl: false, //あとでaddControlの中で指定する
hash: true,
//style: 'https://gsi-cyberjapan.github.io/optimal_bvmap/style/std.json', // スタイルファイルを外部ファイルで与える場合の例
style: {
version: 8,
sources: {
std: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png'],
attribution: '国土地理院-地理院タイル(標準地図)',
maxzoom: 18,
minzoom: 5
},
relief: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png'],
attribution: '国土地理院-段彩陰影図(海域部は海上保安庁海洋情報部の資料を使用して作成)',
maxzoom: 15,
minzoom: 5
}
},
layers:[
{
id: 'basemap',
type: 'raster',
source: 'std'
},
{
id: 'relief',
type: 'raster',
source: 'relief'
},
]
},
center: [139.7444, 35.7244], // 中心の経度緯度
zoom: 8, // 初期表示のズームレベル
maxPitch: 85, //最大の傾き
maxZoom: 4, // 最小ズーム
maxZoom: 18 // 最大ズーム
});
map.on('load', function(){
//凡例のプラグイン
const legend_target = {
'relief': '標高',
};
map.addControl(new MaplibreLegendControl(legend_target, {
showDefault: false,
onlyRendered: false,
reverseOrder: false
}), 'bottom-left');
})
//UI
map.addControl(new maplibregl.AttributionControl({customAttribution: "<br />MapLibre GL JSとwatergisのプラグインを使っています。" }));
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
map.addControl(new maplibregl.ScaleControl() );
//Export
map.addControl(new MaplibreExportControl({
PageSize: Size.A4,
PageOrientation: PageOrientation.Portrait,
Format: Format.PNG,
DPI: DPI[96],
//Crosshair: true,
PrintableArea: true,
Local: 'en'
}), 'top-right');
//debug
map.showTileBoundaries = false;
map.showCollisionBoxes = false;
</script>
</body>
</html>
試作2: 職場や家の位置を追加してみる(位置はダミーです)
こんどは、職場や自宅の位置を追加してみます。データを別途アップロードするのは大変なので、html中にGeoJSONで記述します。サンプルとして二つのポイントを作りました。MapLibreのaddSourceでソースデータを追加しますが、下図の'data'という要素の中で記載しています。そして、このデータを表示するためのスタイルレイヤもaddLayerとして定義しています。matchを使ってポイントの種類(オフィス、自宅)に応じて色を変えています。
実際のコード
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Test- 標高地図02-位置を追加 </title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css' rel='stylesheet' />
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.js'></script>
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.js'></script>
<style>
body { margin:0; padding:0; }
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body onload="popupFunction()">
<div id='map'></div>
<script>
function popupFunction() {
alert("Test")
}
var map = new maplibregl.Map({
container: 'map',
attributionControl: false, //あとでaddControlの中で指定する
hash: true,
//style: 'https://gsi-cyberjapan.github.io/optimal_bvmap/style/std.json', // スタイルファイルを外部ファイルで与える場合の例
style: {
version: 8,
sources: {
std: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png'],
attribution: '国土地理院-地理院タイル(標準地図)',
maxzoom: 18,
minzoom: 5
},
relief: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png'],
attribution: '国土地理院-段彩陰影図(海域部は海上保安庁海洋情報部の資料を使用して作成)',
maxzoom: 15,
minzoom: 5
}
},
layers:[
{
id: 'basemap',
type: 'raster',
source: 'std'
},
{
id: 'relief',
type: 'raster',
source: 'relief'
},
]
},
center: [139.7444, 35.7244], // 中心の経度緯度
zoom: 8, // 初期表示のズームレベル
maxPitch: 85, //最大の傾き
maxZoom: 4, // 最小ズーム
maxZoom: 18 // 最大ズーム
});
map.on('load', function(){
map.addSource('points',{
type: 'geojson',
data: {
'type': 'FeatureCollection',
'crs': {'type':'name','properties':{'name':'urn:ogc:def:crs:OGC:1.3:CRS84'}},
'features': [
{
'type':'Feature',
'properties': {'p-type':'office','name':'職場','memo':'ABC町XYZ番地'},
'geometry':{'type':'Point','coordinates':[139.747386,35.671165]}
},
{
'type':'Feature',
'properties': {'p-type':'home','name':'自宅A(ダミー)','memo':'DEF町GHI番地'},
'geometry':{'type':'Point','coordinates':[139.756809,35.673478]}}
]
}
})
map.addLayer({
'id':'locations',
'type':'circle',
'source':'points',
'layout':{},
'paint':{
'circle-color': ['match',['get','p-type'],'office','#0000FF','home','#FF0000','#000000'],
'circle-radius': 10
}
})
//凡例のプラグイン
const legend_target = {
'locations':'職場(青)・自宅(赤)の位置',
'relief': '標高',
};
map.addControl(new MaplibreLegendControl(legend_target, {
showDefault: false,
onlyRendered: false,
reverseOrder: false
}), 'bottom-left');
})
//UI
map.addControl(new maplibregl.AttributionControl({customAttribution: "<br />MapLibre GL JSとwatergisのプラグインを使っています。" }));
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
map.addControl(new maplibregl.ScaleControl() );
//Export
map.addControl(new MaplibreExportControl({
PageSize: Size.A4,
PageOrientation: PageOrientation.Portrait,
Format: Format.PNG,
DPI: DPI[96],
//Crosshair: true,
PrintableArea: true,
Local: 'en'
}), 'top-right');
//debug
map.showTileBoundaries = false;
map.showCollisionBoxes = false;
</script>
</body>
</html>
試作3: ポップアップを追加してみる
家やオフィスの丸の上にマウスを持っていくとポップアップが出てくるようにしてみました。昔の経験を思い出しながら、以下のようにポップアップの記述を書いてみます。maplibregl.Popupというものを使います。なお、p.cod1というスタイルもhtmlヘッダーで定義しておきます。
実際のコード
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Test- 標高地図03-popup </title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://unpkg.com/maplibre-gl@3.0.1/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@3.0.1/dist/maplibre-gl.css' rel='stylesheet' />
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.js'></script>
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.js'></script>
<style>
body { margin:0; padding:0; }
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
p.cod1 { font-style:normal; font-size: 9pt; }
</style>
</head>
<body onload="popupFunction()">
<style>
.maplibregl-popup{
max-width: 300px;
}
</style>
<div id='map'></div>
<script>
function popupFunction() {
alert("Test")
}
var map = new maplibregl.Map({
container: 'map',
attributionControl: false, //あとでaddControlの中で指定する
hash: true,
//style: 'https://gsi-cyberjapan.github.io/optimal_bvmap/style/std.json', // スタイルファイルを外部ファイルで与える場合の例
style: {
version: 8,
sources: {
std: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png'],
attribution: '国土地理院-地理院タイル(標準地図)',
maxzoom: 18,
minzoom: 5
},
relief: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png'],
attribution: '国土地理院-段彩陰影図(海域部は海上保安庁海洋情報部の資料を使用して作成)',
maxzoom: 15,
minzoom: 5
}
},
layers:[
{
id: 'basemap',
type: 'raster',
source: 'std'
},
{
id: 'relief',
type: 'raster',
source: 'relief'
},
]
},
center: [139.7444, 35.7244], // 中心の経度緯度
zoom: 8, // 初期表示のズームレベル
maxPitch: 85, //最大の傾き
maxZoom: 4, // 最小ズーム
maxZoom: 18 // 最大ズーム
});
map.on('load', function(){
map.addSource('points',{
type: 'geojson',
data: {
'type': 'FeatureCollection',
'crs': {'type':'name','properties':{'name':'urn:ogc:def:crs:OGC:1.3:CRS84'}},
'features': [
{
'type':'Feature',
'properties': {'p-type':'office','name':'職場','memo':'ABC町XYZ番地'},
'geometry':{'type':'Point','coordinates':[139.747386,35.671165]}
},
{
'type':'Feature',
'properties': {'p-type':'home','name':'自宅A(ダミー)','memo':'DEF町GHI番地'},
'geometry':{'type':'Point','coordinates':[139.756809,35.673478]}}
]
}
})
map.addLayer({
'id':'locations',
'type':'circle',
'source':'points',
'layout':{},
'paint':{
'circle-color': ['match',['get','p-type'],'office','#0000FF','home','#FF0000','#000000'],
'circle-radius': 10
}
})
//凡例のプラグイン
const legend_target = {
'locations':'職場(青)・自宅(赤)の位置',
'relief': '標高',
};
map.addControl(new MaplibreLegendControl(legend_target, {
showDefault: false,
onlyRendered: false,
reverseOrder: false
}), 'bottom-left');
})
//pop-up
var popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false
})
map.on('mousemove','locations', function(e){
map.getCanvas().style.cursor = 'pointer';
if(e.features[0].properties.name){
var html = "<p class='cod1'>" + e.features[0].properties.name + "<br />" + e.features[0].properties.memo + "</p>"
popup
.setLngLat(e.lngLat)
.setHTML(html)
.addTo(map)
} else {
popup.remove();
}
})
map.on('mouseleave','locations', function(){
map.getCanvas().style.cursor='';
popup.remove();
})
//UI
map.addControl(new maplibregl.AttributionControl({customAttribution: "<br />MapLibre GL JSとwatergisのプラグインを使っています。" }));
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
map.addControl(new maplibregl.ScaleControl() );
//Export
map.addControl(new MaplibreExportControl({
PageSize: Size.A4,
PageOrientation: PageOrientation.Portrait,
Format: Format.PNG,
DPI: DPI[96],
//Crosshair: true,
PrintableArea: true,
Local: 'en'
}), 'top-right');
//debug
map.showTileBoundaries = false;
map.showCollisionBoxes = false;
</script>
</body>
</html>
元データの属性情報の一部をポップアップとして表示することができました。
試行4:標高レイヤの透明度バーをつける
レンジスライダーを調整して標高レイヤの透過度を調整できるようにします。
bodyのスタイルに関係するスタイルを追加した後、bodyに新しいdivクラス(map-overlay用)をつくって、その中にsliderを入れます。scriptの中で、sliderとsliderValueを定義したら、map.on('load')の中に以下のようなイベントリスナーを追加します。
実際のコード
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Test- 標高地図04-popup </title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://unpkg.com/maplibre-gl@3.0.1/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@3.0.1/dist/maplibre-gl.css' rel='stylesheet' />
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-export@1.3.8/maplibre-gl-export.js'></script>
<link href='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.css' rel='stylesheet' />
<script src='https://ubukawa.github.io/cmv-test/watergis/maplibre-gl-legend@1.2.8/maplibre-gl-legend.js'></script>
<style>
body { margin:0; padding:0; }
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
p.cod1 { font-style:normal; font-size: 9pt; }
</style>
</head>
<body onload="popupFunction()">
<style>
.maplibregl-popup{
max-width: 300px;
}
.map-overlay{
font-style:bold;
position: absolute;
width: 25%;
top: 0;
left: 0;
padding: 10px;
}
.map-overlay .map-overlay-inner {
background-color: cornsilk;
box-shadow: 0 1px 2px rgba(0,0,0, 0.2) ;
border-radius: 4px;
padding: 12px;
margin-bottom: 10px;
}
.map-overlay label{
display: block;
margin: 0 0 10px;
}
.map-overlay input {
background-color: transparent;
display: inline-block;
width: 100%;
position: relative;
margin: 0;
cursor: ew-resize;
}
</style>
<div id='map'></div>
<div class="map-overlay top">
<div class="map-overlay-inner">
<label>標高レイヤ透明度: <span id="slider-value">100%</span></label>
<input id="slider" type="range" min="0" max="100" step="0" value="100">
</div>
</div>
<script>
function popupFunction() {
alert("Test")
}
var map = new maplibregl.Map({
container: 'map',
attributionControl: false, //あとでaddControlの中で指定する
hash: true,
//style: 'https://gsi-cyberjapan.github.io/optimal_bvmap/style/std.json', // スタイルファイルを外部ファイルで与える場合の例
style: {
version: 8,
sources: {
std: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png'],
attribution: '国土地理院-地理院タイル(標準地図)',
maxzoom: 18,
minzoom: 5
}
},
layers:[
{
id: 'basemap',
type: 'raster',
source: 'std'
}
]
},
center: [139.7444, 35.7244], // 中心の経度緯度
zoom: 8, // 初期表示のズームレベル
maxPitch: 85, //最大の傾き
maxZoom: 4, // 最小ズーム
maxZoom: 18 // 最大ズーム
});
const slider = document.getElementById('slider')
const sliderValue = document.getElementById('slider-value')
map.on('load', function(){
map.addSource('points',{
type: 'geojson',
data: {
'type': 'FeatureCollection',
'crs': {'type':'name','properties':{'name':'urn:ogc:def:crs:OGC:1.3:CRS84'}},
'features': [
{
'type':'Feature',
'properties': {'p-type':'office','name':'職場','memo':'ABC町XYZ番地'},
'geometry':{'type':'Point','coordinates':[139.747386,35.671165]}
},
{
'type':'Feature',
'properties': {'p-type':'home','name':'自宅A(ダミー)','memo':'DEF町GHI番地'},
'geometry':{'type':'Point','coordinates':[139.756809,35.673478]}}
]
}
})
map.addSource('relief',{
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png'],
attribution: '国土地理院-段彩陰影図(海域部は海上保安庁海洋情報部の資料を使用して作成)',
maxzoom: 15,
minzoom: 5
})
map.addLayer({
'id':'relief',
'source':'relief',
'type':'raster'
})
map.addLayer({
'id':'locations',
'type':'circle',
'source':'points',
'layout':{},
'paint':{
'circle-color': ['match',['get','p-type'],'office','#0000FF','home','#FF0000','#000000'],
'circle-radius': 10
}
})
slider.addEventListener('input',(e) => {
map.setPaintProperty(
'relief',
'raster-opacity',
parseInt(e.target.value, 10) / 100
)
sliderValue.textContent = e.target.value + '%';
})
//凡例のプラグイン
const legend_target = {
'locations':'職場(青)・自宅(赤)の位置'
};
map.addControl(new MaplibreLegendControl(legend_target, {
showDefault: false,
onlyRendered: false,
reverseOrder: false
}), 'bottom-left');
})
//pop-up
var popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false
})
map.on('mousemove','locations', function(e){
map.getCanvas().style.cursor = 'pointer';
if(e.features[0].properties.name){
var html = "<p class='cod1'>" + e.features[0].properties.name + "<br />" + e.features[0].properties.memo + "</p>"
popup
.setLngLat(e.lngLat)
.setHTML(html)
.addTo(map)
} else {
popup.remove();
}
})
map.on('mouseleave','locations', function(){
map.getCanvas().style.cursor='';
popup.remove();
})
//UI
map.addControl(new maplibregl.AttributionControl({customAttribution: "<br />MapLibre GL JSとwatergisのプラグインを使っています。" }));
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
map.addControl(new maplibregl.ScaleControl() );
//Export
map.addControl(new MaplibreExportControl({
PageSize: Size.A4,
PageOrientation: PageOrientation.Portrait,
Format: Format.PNG,
DPI: DPI[96],
//Crosshair: true,
PrintableArea: true,
Local: 'en'
}), 'top-right');
//debug
map.showTileBoundaries = false;
map.showCollisionBoxes = false;
</script>
</body>
</html>
一応こんな感じになりました。htmlファイルをいじっただけで、標高レイヤーの透過度を変えられて、自宅や職場の場所をマークできるhtmlファイルを作りました。
申請関係
なお、地理院地図タイルの標準地図は基本測量成果なので、地図の複製申請を出しておきました。GitHubで公開しているので、念のためです。公開方法や用途によってはこのような手続きが必要になると思います。
(後日追記)
国土地理院に申請したら、地理院タイルは測量成果であってもリアルタイムに読み込んで利用するならば出典明示だけでOKなのだそうでした。
『地理院タイルをウェブサイトやソフトウェア、アプリケーション上でリアルタイムに読み込んで利用する場合、地理院タイルは出典の明示のみで申請不要でご利用いただけます。』と説明があり、今回は出典の明示のみで利用出来そうです。
まとめ
簡単に地図が見られました。
こんな感じです
https://ubukawa.github.io/el-map/map04.html#14.79/35.67197/139.75092
参考にしたページ
- htmlファイル中にデータとスタイルを書くのに参考にしたページ
- ポップアップの出し方で参考にしたページ
- 透過のスライダーの作り方で参考にしたページ
- 国土地理院地理院タイル一覧
- 作業レポジトリ