これまで気象庁発表の情報を描画する際に、気象庁ホームページのタイル画像を使用してきましたが、画像形式ですので独自のデザイン変更等は難しい仕様です。もともとが内部ファイルをたまたま使えてしまっているだけなので仕方ないところですが、今回は後加工のしやすいgeojson形式の境界データを作成してみます
作成の方針
警報・注意報や地震情報の発表地域のGISデータについては、気象庁ホームページの気象データ高度利用のページに掲載されているため、掲載されているシェープファイルのデータをQGIS等のGISソフトでgeojson等に変換することで利用可能です。ただ、このデータはかなり詳細なうえ、単純にQGISで簡素化すると、隣接する領域間で省かれる点が異なるため、隙間ができてクレバス状態となってしまいます
また、市町村・まとめた地域・1次細分区域・府県それぞれのシェープファイルを別々に簡素化すると、それぞれで省かれる点が異なり、重ね合わせた時にはみ出る部分が出てしまいます
このため今回は、
市町村のデータをtopojson(隣接の領域と境界を共有する)に変換してから簡素化(=クレバスができない)
→まとめた地域、1次細分区域、府県の順に結合(それぞれを重ね合わせてもきれいに重なる)
の順に作成していきたいと思います
作成手順
気象庁ホームページの気象データ高度利用のページからシェープファイルをダウンロードします。警報・注意報を描画するなら、市町村等(気象警報等)をダウンロードします
続いて、mapshaperでtopojsonに変換します。npmでmapshaperをインストール後、展開したシェープファイル一式のうち、拡張子が.shpのものを指定し、topojsonに変換します
npm install -g mapshaper
mapshaper class20s.shp -o class20s.topojson
mapshaperでtopojsonの簡素化を行った後、geojsonに変換します。「1%」の部分は適宜変更してください(小さくするほど簡素化)
mapshaper class20s.topojson -simplify 1% -o class20s_1p.topojson
mapshaper class20s_1p.topojson -o class20s_1p.geojson
ここまででひとまず描画に利用できる市町村単位のデータができました。このデータをもとに、より広い地域(まとめた地域、1次細分区域、府県)のデータを作成していきます
まずはJavaScriptでデータを整えます(JavaScriptでなくても、好きな方法で構いません)。行っている処理は以下の通りです。
- 緯度経度を小数点以下4桁に丸める(若干の軽量化のため)
- 小笠原村に沖ノ鳥島格子を追加(簡素化で省かれてしまうため。鳥島なども適当に追加しても良い)
- 気象庁ホームページのarea.jsonから包含関係を取得1し、まとめた地域、1次細分区域、府県の情報を付加(この情報をもとに関係地域を結合して、府県ごとのgeojson等を作成するため)
- 南千島(択捉島・国後島・色丹島)には根室地方の地域情報を付加
作成に使用したスクリプトはページ末尾に掲載しています
最後に、QGISで領域を結合していきます。QGISでgeojsonファイルを開いた後、(必要に応じて)ジオメトリの修復→ディゾルブ(融合)の順に行うことで広域の地域ファイルを作成できます
作成してみて
気象庁サイトにgeojson形式で掲載がない都道府県単位や季節予報の地域区分の境界線など、いろいろな形のデータが作れましたが、一部簡素化の際にクレバスが残ってしまったのか、陸地の中に隙間ができてしまう場所がありました。適宜QGIS式による抽出で面積の小さい領域を削除する等、少しチューニングを行う必要がありそうです
広域の地域情報付加のために使用したスクリプト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>geoJson簡素化</title>
<style>
input[type=number]{ width:4rem;}
</style>
</head>
<body>
<div id="menu">
<button id="readFile">Go</button>
<input type="file" id="in"><br>
<input type="number" id="thresholdPolygonCount" value="0">点以下のパスを削除<br>
小数点以下<input type="number" id="digit" value="4">桁に丸める(0で丸めなし)<br>
コード<input id="ogasawaraCode" value="1342100">の地域に沖ノ鳥島格子を追加<br>
<label><input type="checkbox" id="addParent">親地域情報を付加</label>
</div>
<div id="out"></div>
<a id="download" target="_blank">出力ファイル</a>
<script>
"use strict";
let areaInfos = {};
fetch("https://www.jma.go.jp/bosai/common/const/area.json")
.then((response) => response.json())
.then((response) => {
areaInfos = response;
});
document.getElementById("readFile").addEventListener("click",function(e){
readFile();
});
document.getElementById("in").addEventListener("change",function(e){
readFile();
});
function readFile(){
try{
let inFile = document.getElementById("in").files[0];
let reader = new FileReader();
reader.readAsText(inFile);
reader.onload = function(e){
analyse( reader.result);
}
}catch(e){
console.log(e);
}
}
function analyse( inText){
let thresholdPolygonCount = document.getElementById("thresholdPolygonCount").value;
let inLength = inText.length;
let inJson = JSON.parse(inText);
console.log( inJson);
let outJson = JSON.parse(inText);
for( let i=0; i<outJson['features'].length; i++){
if( outJson['features'][i]['geometry']==null){
console.log( outJson['features'][i]);
outJson['features'].splice(i,1);
i--;
}
}
for( let feature of outJson['features']){
// if( feature['geometry']==null){
// continue;
// }
let code = feature['properties']['regioncode'];
if( document.getElementById("addParent").checked){
try{
if( feature['properties']['name']=="色丹島" || feature['properties']['name']=="国後島" || feature['properties']['name']=="択捉島"){
feature['properties']['code'] = "hoppo";
feature['properties']['name'] = "根室地方";
feature['properties']['class15Code'] = "hoppo";
feature['properties']['class10Code'] = "014010";
feature['properties']['officeCode'] = "014100";
feature['properties']['centerCode'] = "010100";
feature['properties']['japanCode'] = "010000";
}else{
let class20Code = code;
let class15Code = areaInfos['class20s'][class20Code]['parent'];
feature['properties']['class15Code'] = class15Code;
let class10Code = areaInfos['class15s'][class15Code]['parent'];
feature['properties']['class10Code'] = class10Code;
let officeCode = areaInfos['class10s'][class10Code]['parent'];
feature['properties']['officeCode'] = officeCode;
let centerCode = areaInfos['offices'][officeCode]['parent'];
feature['properties']['centerCode'] = centerCode;
feature['properties']['japanCode'] = "010000";
}
}catch(e){
console.log( feature['properties']['name'], e);
}
}
feature['properties']['code'] = feature['properties']['regioncode'];
delete feature['properties']['regioncode'];
delete feature['properties']['regionname'];
delete feature['properties']['namekana'];
if( feature['geometry']['type']=="Polygon"){
feature['geometry']['type'] = "MultiPolygon";
feature['geometry']['coordinates'] = [feature['geometry']['coordinates']];
}
for( let i=0; i<feature['geometry']['coordinates'].length; i++){
for( let j=0; j<feature['geometry']['coordinates'][i].length; j++){
for( let k=0; k<feature['geometry']['coordinates'][i][j].length; k++){
const digit = document.getElementById("digit").value;
if( digit==0){
feature['geometry']['coordinates'][i][j][k][0] = feature['geometry']['coordinates'][i][j][k][0];
feature['geometry']['coordinates'][i][j][k][1] = feature['geometry']['coordinates'][i][j][k][1];
}else{
feature['geometry']['coordinates'][i][j][k][0] = (feature['geometry']['coordinates'][i][j][k][0].toFixed(digit))*1;
feature['geometry']['coordinates'][i][j][k][1] = (feature['geometry']['coordinates'][i][j][k][1].toFixed(digit))*1;
}
}
if( feature['geometry']['coordinates'][i][j].length <= thresholdPolygonCount){
feature['geometry']['coordinates'][i].splice(j,1);
j--;
}
}
if( feature['geometry']['coordinates'][i].length == 0){
feature['geometry']['coordinates'].splice(i,1);
i--;
}
}
const ogasawaraCode = document.getElementById("ogasawaraCode").value;
if( code==ogasawaraCode){
feature['geometry']['coordinates'].push( [[[136.0809,20.4258],[136.0814,20.4258],[136.0814,20.4253],[136.0809,20.4253],[136.0809,20.4258]]]);
feature['geometry']['coordinates'].push( [[[136.0695,20.4255],[136.07,20.4255],[136.07,20.4250],[136.0695,20.4250],[136.0695,20.4255]]]);
}
}
console.log( outJson);
let outText = JSON.stringify(outJson);
let outLength = outText.length;
let out = "";
out += "<table>";
out += "<tr><th>in</th><td>" + inLength + "</td></tr>";
out += "<tr><th>out</th><td>" + outLength + "</td></tr>";
out += "</table>";
document.getElementById("out").innerHTML = out;
const blob = new Blob([outText], { type: 'application/json' });
// a 要素の href 属性に Object URL を セット
document.getElementById('download').href = window.URL.createObjectURL(blob);
}
</script>
</body>
</html>
-
ここでは気象庁サイトの内部ファイルから地域包含関係を取得していますが、公式にデータを取得したい場合は気象庁防災情報XMLの技術情報ページから、個別コード表(記事作成日時点では 20XXXXXX_AreaInformationCity-AreaForecastLocalM.xls のファイル名)を利用することで取得できます ↩