前回は地震情報を表形式で表示してきましたが、今回は地図表示にトライしてみます
apiのURL
- 最近発生した地震のリストは、 https://www.jma.go.jp/bosai/quake/data/list.json
- 個別の地震の情報は、
https://www.jma.go.jp/bosai/quake/data/{jsonName}.json
に格納されています
これらの詳細は 前々回の記事をご覧ください
また、
- 地震情報の震度速報で用いる地域の境界データが https://www.jma.go.jp/bosai/common/const/geojson/earthquake.json
に格納されています
今回のコード例では用いませんが、市町村ごと・都道府県ごとに震度を描画したい場合は、
- 市町村・都道府県の中心の位置データ(気象に関する情報で使用) https://www.jma.go.jp/bosai/common/const/xy.json
- 地震情報の地域コードと気象の地域コードの対応表 https://www.jma.go.jp/bosai/common/const/earthquake_area.json
を活用することが可能です
apiの構造
個別の地震情報(quake/data/{jsonName}.json)
個別の地震情報のapiは以下のような構造となっています(震度に関わる部分を中心に抜粋)
{
"Body": {
"Earthquake": {
"Hypocenter": {
"Area": {
"Name": "十勝地方南部",
"Coordinate": "+42.6+143.1-80000/"
}
}
},
"Intensity": {
"Observation": {
"MaxInt": "5+",
"Pref": [
{
"Name": "北海道",
"Code": "01",
"MaxInt": "5+",
"Area": [
{
"Name": "十勝地方中部",
"Code": "156",
"MaxInt": "5+",
"City": [
{
"Name": "浦幌町",
"Code": "0164900",
"MaxInt": "5+",
"IntensityStation": [
{
"Name": "浦幌町桜町*",
"Code": "0164920",
"Int": "5+",
"latlon": {
"lat": 42.81,
"lon": 143.66
}
},......
]
},......
]
},......
]
},......
]
}
}
}
}
| 要素名 | 説明 | 例 |
|---|---|---|
| Body.Earthquake.Hypocenter.Area.Name | 震源地名 | 十勝地方南部 |
| Body.Earthquake.Hypocenter.Area.Coordinate | 震源座標 | +42.6+143.1-80000/ |
| Body.Intensity.Observation | 震度 |
震度に関しては、Pref(都道府県別震度)>Area(地域別震度)>City(市町村別震度)>IntensityStation(地点別震度)の入れ子構造となっています。それぞれについて、
| 要素名 | 説明 | 例 |
|---|---|---|
| Name | 地域名/観測点名 | 浦幌町桜町 |
| Code | 地域コード/観測点コード | 0164920 |
| MaxInt(地点別震度では Int) | 震度 | 5+ |
| latlon(地点別震度のみ) | 観測点座標 | {"lat":42.81, "lon":143.66} |
のようにデータが格納されています( 詳細は 前々回の記事をご覧ください )
観測点座標は、IntensityStation(地点別震度)にのみ格納されています
地域別の震度については、{geojson/earthquake.json}に格納されたgeojsonをそのまま面的に描画することが可能です。ただ、色分けだけでは見分けづらい場合もあるため、今回の実装では{geojson/earthquake.json}に格納された境界線の緯度経度を平均し、各地域の中心の位置を計算して描画する実装としています。
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[141.4039, 43.7378],
[141.4173, 43.7112],
[141.4906, 43.6844],
[141.5833, 43.6099],
[141.5949, 43.5838],
[141.6001, 43.5846],......
]
]
]
},
"properties": {
"code": "100" // 地震の地域コード
}
},......
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[139.1433, 34.2437],
[139.1678, 34.234],
[139.1762, 34.2078],
[139.129, 34.1844],
[139.1204, 34.1954],
[139.1433, 34.2437]
]
]
]
},
"properties": {
"code": "354",
"islandBold": true // 島しょ部は "islandBold":true となっており、塗りつぶしのほかに縁取りを付けるなどして強調表示が可能
}
},......
]
}
市町村/都道府県の座標を取得するには、
{earthquake_area.json}.to_class20.{Code}から気象の2次細分区域コード(class20Code)を取得
↓
{xy.json}.class20s.{class20Code}に格納されている市町村ごとの中心位置の座標を取得
という方法が考えられます。その際、気象分野で市町村を分割して発表している地域があるため、格納された座標を平均する等の処理が必要な場合があります
{
"from_class20": { // 気象の2次細分区域コード→地震の市町村コードの対応表
"0110000": [ // 気象の2次細分区域 0110000(札幌市) は地震では 0110100(札幌中央区)、0110200(札幌北区)...... に分割されている
"0110100",
"0110200",
"0110300",
"0110400",
"0110500",
"0110600",
"0110700",
"0110800",
"0110900",
"0111000"
],
"0120200": [
"0120200"
],......
},
"to_class20": { // 地震の市町村コード→気象の2次細分区域コードの対応表
"0110100": [
"0110000"
],......
"0120600": [ // 地震の市町村コード 0120600(釧路市) は気象では 0120601(釧路市釧路)、0120602(釧路市阿寒)...... に分割されている
"0120601",
"0120602",
"0120603"
],......
},
"from_office": {
"011000": [
"0121400",
"0151100",
"0151200",
"0151300",
"0151400",
"0151600",
"0151700",
"0151800",
"0151900",
"0152000"
],......
},
"to_office": {
"0110100": [
"016000"
],
"0110200": [
"016000"
],......
}
}
{
"offices": { // 都道府県の中心座標
"011000": [45.0281, 142.1684],
"012000": [43.9838, 142.3374],......
},
"class20s": { // 気象の2次細分区域(市町村)の中心座標
"0110000": [42.9907, 141.2505],
"0120200": [41.8401, 140.9224],......
}
}
表示用のコード
ここまでの内容を地図表示していきます
アメダスのデータを地図表示した記事の時と同様に、Leaflet.jsを用いて地図表示していきます
headタグ内でLeafletのスタイルシートとスクリプトを読み込みます(Leafletのチュートリアルからそのまま拝借)
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
ページのレイアウトを定義します。html、bodyを画面幅いっぱいに表示する設定とし、地図部分(div#map)の背景色には濃い青(#3b4580)を指定します。また、Leafletでは地図にアイコンを載せる際、デフォルトではleaflet-div-iconというクラスがつき、背景色と境界線が付いてしまうため、それらを付けないためのiconというクラスを定義しています
(参考 気象庁ホームページの配色指針)
<style>
*{ font-family:sans-serif;}
html, body{ width:100%; height:100%; margin:0; overflow:hidden;}
body{ display:flex; flex-direction:column;}
div#map{ flex:1; background:#3b4580;}
.icon{ background:#0000;}
</style>
震度とそれに対応する日本語の表現、震度別の画像のファイル名、表示優先順位を定義しておきます
const intInfos = {
"7":{"ja":"震度7","filename":"i7","priority":10},
"6+":{"ja":"震度6強","filename":"i6p","priority":9},
"6-":{"ja":"震度6弱","filename":"i6m","priority":8},
"5+":{"ja":"震度5強","filename":"i5p","priority":7},
"震度5弱以上未入電":{"ja":"震度5弱以上と推定されるが未受信/精査中","filename":"i5u","priority":6},
"5-":{"ja":"震度5弱","filename":"i5m","priority":5},
"4":{"ja":"震度4","filename":"i4","priority":4},
"3":{"ja":"震度3","filename":"i3","priority":3},
"2":{"ja":"震度2","filename":"i2","priority":2},
"1":{"ja":"震度1","filename":"i1","priority":1}
}
震源アイコンのバツ印と、震度のアイコンは、適当に表計算ソフトで自作しました。震度のアイコンの数字には、源暎ユニバーサンズを使用させていただきました
震度アイコンの例
初期化時に、地図のレイヤーを定義します。今回は、気象庁ホームページ内の灰色の地図と、国土地理院の淡色地図の2択としていますが、お好きな地図タイルを追加することも可能です
L.control.layers({
"気象庁地図":L.tileLayer('https://www.jma.go.jp/tile/jma/gray-cities/{z}/{x}/{y}.png', { minZoom:4, maxZoom:12, maxNativeZoom:10, attribution: '© <a href="https://www.jma.go.jp/jma/kishou/info/coment.html">気象庁</a>'}).addTo(map),
"地理院地図":L.tileLayer('https://maps.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', { minZoom:2, maxZoom:18, attribution: '© <a href="https://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>'})
}).addTo(map);
震度アイコンの配置に使う地域情報ファイルを取得します。
取得完了後に、各地域ごとの中心の座標(境界線の座標の緯度経度を平均)を求めます。(中央値をとることも考えられますが、離島など極端に離れた点に引っ張られて海上等にアイコンが打たれることの防止のため、平均としています)
fetch("https://www.jma.go.jp/bosai/common/const/geojson/earthquake.json")
.then((response) => response.json())
.then((response) => {
quakePolygons = response;
let areaLatlons = {};
for( let feature of quakePolygons['features']){
let areaCode = feature['properties']['code'];
if( areaLatlons[areaCode] == undefined){
areaLatlons[areaCode] = [];
}
for( let i in feature['geometry']['coordinates']){
for( let j in feature['geometry']['coordinates'][i]){
for( let k in feature['geometry']['coordinates'][i][j]){
let latlon = feature['geometry']['coordinates'][i][j][k];
areaLatlons[areaCode].push( latlon);
}
}
}
}
areaCenters = {};
for( let areaCode in areaLatlons){
let sum=[0,0], count=0;
for( let latlon of areaLatlons[areaCode]){
sum[0] += latlon[0];
sum[1] += latlon[1];
count++;
}
areaCenters[areaCode] = [sum[1]/count, sum[0]/count];
}
get();
});
URLパラメータから取得したファイル名をもとに、個別の地震情報のファイルを取得します
const params = new URLSearchParams(window.location.search);
fetch("https://www.jma.go.jp/bosai/quake/data/" + params.get("json"))
.then((response) => response.json())
.then((response) => {
display( response);
});
表示用のレイヤーを定義します。今回は、
- 震源アイコン用のレイヤー(layers['hypo']。常に表示)
- 地域震度用のレイヤー(layers['area']。広域の地図で表示)
- 観測点震度用のレイヤー(layers['obs']。詳細の地図で表示)
を定義します
layers['hypo'] = L.layerGroup();
layers['area'] = L.layerGroup();
layers['obs'] = L.layerGroup();
地域別の震度・地域名・座標を読み取ります。座標については、geojson/earthquake.jsonを読み取って計算した中心の位置を用います。
震度の情報については、「震源に関する情報」などでは丸々省略される場合があるため、その場合にはスキップして震源の描画に進むようにします(今回はざっくりtryで囲ってしまっています)
try{
let ints = {"area":[], "obs":[]};
for( let pref of report['Body']['Intensity']['Observation']['Pref']){
for( let area of pref['Area']){
let areaName = area['Name'], areaInt = area['MaxInt'], areaCode = area['Code'], areaLatlon = [35,135];
if( areaCenters[areaCode]==undefined){
console.log("couldn't get latlon of " + areaName);
}else{
areaLatlon = areaCenters[areaCode];
ints['area'].push( {"name":areaName, "int":areaInt, "latlon":areaLatlon});
}
......
}
}
......
}catch(e){console.log(e)}
同様に、観測点別の震度・地域名・座標を読み取ります。観測点別のデータには緯度経度が格納されているためそのまま使用します。
市町村/観測点別の震度は、震度速報などでは格納されていないため、未定義の場合は読み飛ばす処理を最初に加えます
if( area['City']==undefined){
continue;
}
for( let city of area['City']){
for( let obs of city['IntensityStation']){
let obsName = obs['Name'], obsInt = obs['Int'], obsLatlon = [obs['latlon']['lat'],obs['latlon']['lon']];
ints['obs'].push( {"name":obsName, "int":obsInt, "latlon":obsLatlon});
}
}
各地域区分ごとに、震度アイコンを作成してレイヤーに追加していきます。その際、震度の優先度別にzIndexOffsetを追加して、高い震度が上に表示されるようにします(より厳密には、レイヤーを分けて対処するのが好ましいと思います)
for( const areaType of ['area','obs']){
for( let int of ints[areaType]){
let intIcon = L.divIcon({
html:"<img style='width:24px;' src='./images/" + intInfos[int['int']]['filename'] + ".png' title='" + int['name'] + " " + intInfos[int['int']]['ja'] + "'>",
iconSize: [24,24], iconAnchor: [12,12], className:'icon'
});
let intMarker = L.marker( int['latlon'], {icon: intIcon, zIndexOffset:intInfos[int['int']]['priority']*1000});
layers[areaType].addLayer( intMarker);
}
}
震源アイコンをレイヤーに追加します。震度よりも高いzIndexOffsetを追加して、最前面に表示されるようにしています。レイヤーに追加後、震源のレイヤーを表示します
try{
let hypocenter = report['Body']['Earthquake']['Hypocenter'], hypoName = hypocenter['Area']['Name'];
let hypoLatLon = hypocenter['Area']['Coordinate'].replaceAll("/","").split(/(?=[-+])/);
let hypo = L.divIcon({
html:"<img style='width:30px;' src='./images/hypocenter.png' title='震源 " + hypoName + "'>",
iconSize: [30,30], iconAnchor: [15,15], className:'icon'
});
let hypoMarker = L.marker(hypoLatLon, {icon: hypo, zIndexOffset:11000});
layers['hypo'].addLayer( hypoMarker);
}catch(e){console.log(e)}
map.addLayer( layers['hypo']);
controlLayers();
縮尺に応じて表示するレイヤーを選択します。今回は、ズームレベルが8以下の場合または観測点別震度がない場合に地域震度のレイヤー(layers['area'])を、ズームレベルが9以上の場合に観測点別震度のレイヤー(layers['obs'])を表示するようにしています
// ズームレベル変更時
map.on('zoomend', function(){
controlLayers();
});
function controlLayers(){
const zoom = map.getZoom();
if( zoom<=8 || Object.keys(layers['obs'].getLayers()).length==0){
try{ map.addLayer(layers['area']);}catch(e){}
try{ map.removeLayer(layers['obs']);}catch(e){}
}else{
try{ map.removeLayer(layers['area']);}catch(e){}
try{ map.addLayer(layers['obs']);}catch(e){}
}
}
表示ページ全体のソースコード
サンプルページ
サンプルページ(Date APIを利用した版)
震度アイコンの例
地震リスト
地震一覧
前回のコードを流用します
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>地震情報(一覧)</title>
<style>
*{ font-family:sans-serif;}
table, tr, td, th{ border-collapse:collapse; white-space:nowrap; text-align:center;}
th,td{ padding:2px 8px; border-style:solid; border-width:1px 0; border-color:#d8d8db;}
th{ background-color:#f1f1f4;}
td.i7 { background:#b40068; color:white;}
td.i6p{ background:#a50021; color:white;}
td.i6m{ background:#ff2800; color:white;}
td.i5p{ background:#ff9900; color:black;}
td.i5m{ background:#ffe600; color:black;}
td.i4 { background:#fae696; color:black;}
td.i3 { background:#0041ff; color:white;}
td.i2 { background:#00aaff; color:black;}
td.i1 { background:#f2f2ff; color:black;}
</style>
</head>
<body>
<h1>地震情報(一覧)</h1>
<div id="out"></div>
<script>
"use strict";
get();
function get(){
fetch("https://www.jma.go.jp/bosai/quake/data/list.json")
.then((response) => response.json())
.then((reports) => display(reports));
}
function display( reports){
let out = "";
out += "<table><tr><th>発生時刻</th><th>震源</th><th>震度</th><th colspan='2'>詳細</th></tr>";
for( let report of reports){
out += "<tr>";
if( report['at']==undefined){
out += "<td></td>";
}else{
let arrivalTime = Temporal.ZonedDateTime.from( report['at']+"[Asia/Tokyo]");
out += "<td>" + dateFormat(arrivalTime,'m/d h:n') + "</td>";
}
if( report['anm']==undefined || report['anm']==""){
if( report['ttl']==undefined){
out += "<td></td>";
}else{
out += "<td>(" + report['ttl'] + ")</td>"; // infoType
}
}else{
out += "<td>" + report['anm'] + "</td>"; // areaName
}
if( report['ttl']=="顕著な地震の震源要素更新のお知らせ"){
out += "<td><strong>更</strong></td>";
}else if( report['maxi']==undefined){
out += "<td></td>";
}else{
out += "<td class='i" + report['maxi'].replace("-","m").replace("+","p") + "'>" + report['maxi'] + "</td>"; // maxInt = maximumIntensity
}
if( report['json']==undefined){
out += "<td></td>";
}else{
out += "<td><a href='./quake2.html?json=" + report['json'] + "'>表</a></td><td><a href='./quakeMap.html?json=" + report['json'] + "'>地図</a></td>";
}
out += "</tr>";
}
out += "</table>";
document.getElementById("out").innerHTML = out;
}
function dateFormat( t, f='y-m-dTh:n:s', is24=false){ // for Temporal
const j=["","月","火","水","木","金","土","日"];const e=["","Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
if(is24&&t.hour==0){t.substract({days:1});f=f.replace(/h/g,"24").replace(/H/g,24);}
const y=t.year,m=t.month,d=t.day,h=t.hour,n=t.minute,s=t.second,w=t.dayOfWeek;
f=f.replace(/y/g,("000"+y).slice(-4)).replace(/Y/g,y).replace(/m/g,("0"+m).slice(-2)).replace(/M/g,m).replace(/d/g,("0"+d).slice(-2)).replace(/D/g,d);
f=f.replace(/w/g,j[w]).replace(/W/g,e[w]);
f=f.replace(/h/g,("0"+h).slice(-2)).replace(/H/g,h).replace(/n/g,("0"+n).slice(-2)).replace(/N/g,n).replace(/s/g,("0"+s).slice(-2)).replace(/S/g,s);
return f;
}
</script>
</body>
</html>
個別の地震情報
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>地震(地図)</title>
<!-- https://leafletjs.com/ -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<style>
*{ font-family:sans-serif;}
html, body{ width:100%; height:100%; margin:0; overflow:hidden;}
body{ display:flex; flex-direction:column;}
div#map{ flex:1; background:#3b4580;}
.icon{ background:#0000;}
</style>
</head>
<body>
<div id="map"></div>
<script>
"use strict";
const params = new URLSearchParams(window.location.search);
let quakePolygons = {}, areaCenters = {}, layers = {};
const intInfos = {
"7":{"ja":"震度7","filename":"i7","priority":10},
"6+":{"ja":"震度6強","filename":"i6p","priority":9},
"6-":{"ja":"震度6弱","filename":"i6m","priority":8},
"5+":{"ja":"震度5強","filename":"i5p","priority":7},
"震度5弱以上未入電":{"ja":"震度5弱以上と推定されるが未受信/精査中","filename":"i5u","priority":6},
"5-":{"ja":"震度5弱","filename":"i5m","priority":5},
"4":{"ja":"震度4","filename":"i4","priority":4},
"3":{"ja":"震度3","filename":"i3","priority":3},
"2":{"ja":"震度2","filename":"i2","priority":2},
"1":{"ja":"震度1","filename":"i1","priority":1}
}
let map = L.map('map').setView([35, 135], 5);
initialize();
// 要素選択欄の作成、地図の表示、アメダス定数ファイルの取得
function initialize(){
L.control.layers({
"気象庁地図":L.tileLayer('https://www.jma.go.jp/tile/jma/gray-cities/{z}/{x}/{y}.png', { minZoom:4, maxZoom:12, maxNativeZoom:10, attribution: '© <a href="https://www.jma.go.jp/jma/kishou/info/coment.html">気象庁</a>'}).addTo(map),
"地理院地図":L.tileLayer('https://maps.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', { minZoom:2, maxZoom:18, attribution: '© <a href="https://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>'})
}).addTo(map);
getPolygons();
}
function getPolygons(){
fetch("https://www.jma.go.jp/bosai/common/const/geojson/earthquake.json")
.then((response) => response.json())
.then((response) => {
quakePolygons = response;
let areaLatlons = {};
for( let feature of quakePolygons['features']){
let areaCode = feature['properties']['code'];
if( areaLatlons[areaCode] == undefined){
areaLatlons[areaCode] = [];
}
for( let i in feature['geometry']['coordinates']){
for( let j in feature['geometry']['coordinates'][i]){
for( let k in feature['geometry']['coordinates'][i][j]){
let latlon = feature['geometry']['coordinates'][i][j][k];
areaLatlons[areaCode].push( latlon);
}
}
}
}
areaCenters = {};
for( let areaCode in areaLatlons){
let sum=[0,0], count=0;
for( let latlon of areaLatlons[areaCode]){
sum[0] += latlon[0];
sum[1] += latlon[1];
count++;
}
areaCenters[areaCode] = [sum[1]/count, sum[0]/count];
}
get();
});
}
function get(){
fetch("https://www.jma.go.jp/bosai/quake/data/" + params.get("json"))
.then((response) => response.json())
.then((response) => {
display( response);
});
}
function display( report){
layers['hypo'] = L.layerGroup();
layers['area'] = L.layerGroup();
layers['obs'] = L.layerGroup();
try{
let ints = {"area":[], "obs":[]};
for( let pref of report['Body']['Intensity']['Observation']['Pref']){
for( let area of pref['Area']){
let areaName = area['Name'], areaInt = area['MaxInt'], areaCode = area['Code'], areaLatlon = [35,135];
if( areaCenters[areaCode]==undefined){
console.log("couldn't get latlon of " + areaName);
}else{
areaLatlon = areaCenters[areaCode];
ints['area'].push( {"name":areaName, "int":areaInt, "latlon":areaLatlon});
}
if( area['City']==undefined){
continue;
}
for( let city of area['City']){
for( let obs of city['IntensityStation']){
let obsName = obs['Name'], obsInt = obs['Int'], obsLatlon = [obs['latlon']['lat'],obs['latlon']['lon']];
ints['obs'].push( {"name":obsName, "int":obsInt, "latlon":obsLatlon});
}
}
}
}
for( const areaType of ['area','obs']){
for( let int of ints[areaType]){
let intIcon = L.divIcon({
html:"<img style='width:24px;' src='./images/" + intInfos[int['int']]['filename'] + ".png' title='" + int['name'] + " " + intInfos[int['int']]['ja'] + "'>",
iconSize: [24,24], iconAnchor: [12,12], className:'icon'
});
let intMarker = L.marker( int['latlon'], {icon: intIcon, zIndexOffset:intInfos[int['int']]['priority']*1000});
layers[areaType].addLayer( intMarker);
}
}
}catch(e){console.log(e)}
try{
let hypocenter = report['Body']['Earthquake']['Hypocenter'], hypoName = hypocenter['Area']['Name'];
let hypoLatLon = hypocenter['Area']['Coordinate'].replaceAll("/","").split(/(?=[-+])/);
let hypo = L.divIcon({
html:"<img style='width:30px;' src='./images/hypocenter.png' title='震源 " + hypoName + "'>",
iconSize: [30,30], iconAnchor: [15,15], className:'icon'
});
let hypoMarker = L.marker(hypoLatLon, {icon: hypo, zIndexOffset:11000});
layers['hypo'].addLayer( hypoMarker);
}catch(e){console.log(e)}
map.addLayer( layers['hypo']);
controlLayers();
}
// ズームレベル変更時
map.on('zoomend', function(){
controlLayers();
});
function controlLayers(){
const zoom = map.getZoom();
if( zoom<=8 || Object.keys(layers['obs'].getLayers()).length==0){
try{ map.addLayer(layers['area']);}catch(e){}
try{ map.removeLayer(layers['obs']);}catch(e){}
}else{
try{ map.removeLayer(layers['area']);}catch(e){}
try{ map.addLayer(layers['obs']);}catch(e){}
}
}
</script>
</body>
</html>