
17.アメダス気温の表示の仕方
今回は、アメダス気温表示プログラムの説明をします。
アメダス観測所のjsonデータのアドレスは、下記になります。
https://www.jma.go.jp/bosai/amedas/const/amedastable.json
と、下記URLからアメダス気象情報データの読み取りを行い、地図上に気温データを色分けし可視化表示します。
"https://www.jma.go.jp/bosai/amedas/data/map/" + queryDatetime + ".json"
では、早速コードを見ていきましょう。
<script>
<div id="mapid" style="position:absolute;height: 100%;width: 100vw;"></div>
<script>
(async function() {
// 気温カラースケール定義
const temparatureColorScale = [
{ temp: 35, color: '#B40068' },
{ temp: 30, color: '#FF2800' },
{ temp: 25, color: '#FF9900' },
{ temp: 20, color: '#FFF500' },
{ temp: 15, color: '#FFFF96' },
{ temp: 10, color: '#FFFFF0' },
{ temp: 5, color: '#B9EBFF' },
{ temp: 0, color: '#0096FF' },
{ temp: -5, color: '#0041FF' },
{ temp: -273.15, color: '#002080' }
]
// 気温カラーの取得
const getTemparatureColor = (temp) => {
const colorScale = temparatureColorScale.find(item => {
return temp > item.temp
})
if( !colorScale ) return {temp: 'N/A', color: '#000000'}
return colorScale
}
// アメダスデータの読み取り
const fetchAmedasThenGenerateVoronoi = async (option) => {
// アメダス観測所データの読み取り
const fetchAmedasObservatories = async () => {
const url = "https://www.jma.go.jp/bosai/amedas/const/amedastable.json"
const amedasObservatories = {}
const res = await fetch(url)
const json = await res.json()
Object.keys(json).map( key => {
const amedas = json[key]
json[key].lat = amedas.lat[0] + (amedas.lat[1] / 60)
json[key].lon = amedas.lon[0] + (amedas.lon[1] / 60)
})
return json
}
// アメダス気象情報データの読み取り
const fetchAmedasWeatherInformations = async (queryDatetime) => {
const url = "https://www.jma.go.jp/bosai/amedas/data/map/" + queryDatetime + ".json"
const res = await fetch(url)
const json = await res.json()
return json
}
// アメダス観測所からボロノイポリゴンを作成
const generateGeoJsonAmedasVoronoiPolygons = async (amedas, option) => {
const points = []
Object.keys(amedas).map( key => {
const item = amedas[key]
points.push(turf.point([item.lon, item.lat],{'key': key, 'kjName': item.kjName, 'knName': item.knName, 'alt': item.alt}))
})
const collection = turf.featureCollection(points)
const voronoiPolygons = turf.voronoi(collection, {bbox: turf.bbox(turf.buffer(collection, 50, {}))})
datetime = option.queryDatetime.match( /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ )
// アメダス観測所ポイントとボロノイポリゴンの内外判定を行い、観測所データをポリゴンへ移植
points.map( point => {
voronoiPolygons.features.some( polygon => {
if( turf.booleanPointInPolygon(point, polygon) === true ){
polygon.properties.key = point.properties.key
polygon.properties.kjName = point.properties.kjName
polygon.properties.knName = point.properties.knName
polygon.properties.alt = point.properties.alt
polygon.properties.lon = point.geometry.coordinates[0]
polygon.properties.lat = point.geometry.coordinates[1]
polygon.properties.queryDatetime = datetime[1] + '-' + datetime[2] + '-' + datetime[3] + ' ' + datetime[4] + ':' + datetime[5] + ':' + datetime[6]
return true
}
return false
})
})
// ボロノイ図を陸地でクリッピング
if( option.isClipping === true) {
const dissolvePolygons = turf.dissolve(turf.buffer(collection, 30, {steps: 2}))
voronoiPolygons.features.map( voronoiPolygon => {
dissolvePolygons.features.some( dissolvePolygon => {
const intersectPolygon = turf.intersect(voronoiPolygon, dissolvePolygon)
if (!intersectPolygon){
return false
}
voronoiPolygon.geometry = intersectPolygon.geometry
return true
})
})
}
return voronoiPolygons
}
// 現在日時からアメダス気象情報取得パラメータを生成
const generateDateimeString = () => {
const nowTime = new Date()
nowTime.setHours((new Date()).getHours() -1)
//nowTime.setMonth((new Date()).getMonth() -4)
return nowTime.getFullYear().toString() + (nowTime.getMonth() + 1).toString().padStart(2, '0') + nowTime.getDate().toString().padStart(2, '0') + nowTime.getHours().toString().padStart(2, '0') + "0000"
}
const queryDatetime = generateDateimeString()
const amedasObservatories = await fetchAmedasObservatories()
const amedasWeatherInfos = await fetchAmedasWeatherInformations(queryDatetime)
const voronoiPolygons = await generateGeoJsonAmedasVoronoiPolygons(amedasObservatories, {isClipping: option.isClipping, queryDatetime: queryDatetime})
return {observatories: amedasObservatories, weatherInfos: amedasWeatherInfos, voronoiPolygons: voronoiPolygons, queryDatetime: queryDatetime }
}
// Leaflet地図初期化
const initializeMainMap = () => {
const map = L.map("mapid", L.extend({
zoom: 18,
minZoom: 3,
worldCopyJump: "true"
}))
map.setView([35.6,139.8], 5)
map.locate({setView: true, maxZoom: 9});
function onLocationFound(e) {
var radius = e.accuracy;
L.circleMarker(e.latlng,{radius:10,color:'hsl(125, 93%, 38%)'}).addTo(map);
}
map.on('locationfound', onLocationFound);
// スケールの追加
L.control.scale({
imperial: false,
metric: true
}).addTo(map)
const controlLayers = L.control.layers({}, {},{collapsed: false}).addTo(map)
return { map: map, controlLayers: controlLayers }
}
// ベースマップ
const initializeBaseMaps = (mainMap) => {
const map = mainMap.map
const controlLayers = mainMap.controlLayers
// ベース地図の追加
const osmDarkLayer = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', {
attribution: 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.',
maxZoom: 22,
maxNativeZoom: 18
}).addTo(map)
const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
maxZoom: 22,
maxNativeZoom: 18
})
const gsiStdLayer = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>',
maxZoom: 22,
maxNativeZoom: 18
})
const gsiOrtLayer = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg', {
attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>, Images on 世界衛星モザイク画像 obtained from site https://lpdaac.usgs.gov/data_access maintained by the NASA Land Processes Distributed Active Archive Center (LP DAAC), USGS/Earth Resources Observation and Science (EROS) Center, Sioux Falls, South Dakota, (Year). Source of image data product.',
maxZoom: 22,
maxNativeZoom: 8
})
const gsiblankLayer = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png', {
attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>',
maxZoom: 22,
maxNativeZoom: 14
})
const baseMaps = {
'OpenStreetMap(Dark)': osmDarkLayer,
'OpenStreetMap': osmLayer,
'国土地理院(標準)': gsiStdLayer,
'国土地理院(写真)': gsiOrtLayer,
'国土地理院(白地図)': gsiblankLayer
}
// レイヤ切り替えコントール設定
Object.keys(baseMaps).map( key => {
controlLayers.addBaseLayer(baseMaps[key], key)
})
return baseMaps
}
// パネルウインドウ
const initializePanels = (mainMap) => {
const map = mainMap.map
// 情報パネル
const addInfoPanel = (lmap) => {
const info = L.control()
const div = L.DomUtil.create('div', 'info')
info.onAdd = (map) => {
return div
}
info.update = (props) => {
div.innerHTML = '<h4>Temperature</h4>' +
(props ?
'<b>' + props.queryDatetime + '<br />' + props.kjName + '<br />' + props.knName + '</b><br />' + props.temp + ' ℃'
: 'Hover over polygon'
)
}
info.addTo(lmap)
return info
}
// 凡例パネル
const addLegendPanel = (lmap) => {
const legend = L.control({position: 'bottomright'})
legend.onAdd = (map) => {
const div = L.DomUtil.create('div', 'info legend')
temparatureColorScale.map( item => {
div.innerHTML +=
'<i style="background:' + item.color + '"></i> ' +
'> ' + item.temp + '<br>'
})
div.innerHTML += '<i style="background:' + getTemparatureColor('N/A').color + ' opacity:0.0"></i> ' + 'N/A'
return div
}
legend.addTo(lmap)
return legend
}
const infoPanel = addInfoPanel(map)
const legendPanel = addLegendPanel(map)
return {info : infoPanel, legend: legendPanel}
}
// オーバーレイレイヤ
const initializeOverlayMaps = (mainMap, amedas, infoPanel) => {
const map = mainMap.map
const controlLayers = mainMap.controlLayers
// アメダス観測所レイヤ
const amedasObservatoryLayer = L.featureGroup([], {
attribution: '<a href="https://www.jma.go.jp/bosai/map.html#6/41.27/133.308/&elem=temp&contents=amedas&interval=60">気象庁「アメダス」を加工して作成</a>'
})
.on('add', (event) => {
Object.keys(amedas.observatories).map( key => {
const amedasItem = amedas.observatories[key]
L.circle([amedasItem.lat, amedasItem.lon], {radius: 200, weight: 2}).addTo(event.sourceTarget).bindPopup(amedasItem.kjName)
})
})
.addTo(map)
// アメダス気象情報(気温)レイヤ
const amedasTemperatureLayer = L.geoJSON( amedas.voronoiPolygons, {
attribution: '<a href="https://www.jma.go.jp/bosai/map.html#6/41.27/133.308/&elem=temp&contents=amedas&interval=60">気象庁「アメダス」を加工して作成</a>',
onEachFeature: (feature, layer) => {
layer.on({
add: (event) => {
const layer = event.target
const style = {'color': '#000000', 'weight': 0.5, 'opacity': 1, 'fillOpacity': 0.8 }
const weather = amedas.weatherInfos[layer.feature.properties.key]
if( weather && weather.temp ) {
style.fillColor = getTemparatureColor(weather.temp[0]).color
layer.feature.properties.temp = weather.temp[0]
} else {
style.fillOpacity = 0
style.fillColor = getTemparatureColor(30).color
layer.feature.properties.temp = 'N/A'
}
layer.originalStyle = style
layer.setStyle(style)
},
mouseover: (event) => {
const layer = event.target
layer.setStyle({
weight: 5,
color: '#666666'
})
infoPanel.update(layer.feature.properties)
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront()
}
},
mouseout: (event) => {
const layer = event.target
layer.setStyle(layer.originalStyle)
infoPanel.update()
}
})
}
})
.addTo(map)
const overlayMaps = {
"アメダス観測所": amedasObservatoryLayer,
"気温": amedasTemperatureLayer
}
// レイヤ切り替えコントール設定
Object.keys(overlayMaps).map( key => {
controlLayers.addOverlay(overlayMaps[key], key)
})
return overlayMaps
}
// メイン
const main = async () => {
const amedas = await fetchAmedasThenGenerateVoronoi({isClipping: true})
const mainMap = initializeMainMap()
const panels = initializePanels(mainMap)
const baseMaps = initializeBaseMaps(mainMap)
const overlayMaps = initializeOverlayMaps(mainMap, amedas, panels.info)
}
main()
}())
</script>
<style>
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #777777;
}
.legend {
line-height: 18px;
color: #555555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
</style>
このコードは、Leaflet.jsを用いて日本のアメダス(地域気象観測システム)の気象データ(特に気温)を地図上に可視化するものです。ボロノイ図を用いて観測所ごとのエリアを描画し、気温に基づいた色でエリアを塗り分けています。以下に、各部分の詳細な説明を示します。
HTML部分
<div id="mapid" style="position:absolute;height: 100%;width: 100vw;"></div>
- 地図を表示するための要素。
id="mapid"
がLeaflet.jsで参照されます。 - スタイルでは、地図が画面全体に広がるよう設定されています。
JavaScript部分
1. 気温カラースケール定義
const temparatureColorScale = [
{ temp: 35, color: '#B40068' },
{ temp: 30, color: '#FF2800' },
...
]
- 気温ごとに異なる色を定義します。高温は赤系、低温は青系で、摂氏-273.15度まで対応。
const getTemparatureColor = (temp) => {
const colorScale = temparatureColorScale.find(item => temp > item.temp);
if (!colorScale) return {temp: 'N/A', color: '#000000'};
return colorScale;
}
- 指定した気温に対応する色を取得する関数。気温がスケール外の場合、黒色を返します。
2. アメダスデータの読み取りとボロノイ図生成
(1) 観測所データの取得
const fetchAmedasObservatories = async () => {
const url = "https://www.jma.go.jp/bosai/amedas/const/amedastable.json";
...
Object.keys(json).map(key => {
json[key].lat = amedas.lat[0] + (amedas.lat[1] / 60);
json[key].lon = amedas.lon[0] + (amedas.lon[1] / 60);
});
return json;
}
- アメダス観測所の位置データ(緯度・経度)を取得し、度分表記を度単位に変換。
(2) 気象データの取得
const fetchAmedasWeatherInformations = async (queryDatetime) => {
const url = "https://www.jma.go.jp/bosai/amedas/data/map/" + queryDatetime + ".json";
const res = await fetch(url);
const json = await res.json();
return json;
}
- 指定した日時のアメダス気象データを取得。
(3) ボロノイ図の生成
const generateGeoJsonAmedasVoronoiPolygons = async (amedas, option) => {
...
const voronoiPolygons = turf.voronoi(collection, {bbox: turf.bbox(turf.buffer(collection, 50, {}))});
...
}
- Turf.jsを使用してボロノイ図を生成。
- 観測所のデータをエリア(ポリゴン)に関連付けし、必要に応じて陸地部分だけにクリップ(切り取り)します。
(4) データ取得日時の生成
const generateDateimeString = () => {
const nowTime = new Date();
nowTime.setHours((new Date()).getHours() - 1);
return nowTime.getFullYear().toString() + ... + "0000";
}
- 現在日時をフォーマットし、APIで利用可能な形式に変換。
3. Leaflet地図の初期化
const initializeMainMap = () => {
const map = L.map("mapid", { zoom: 18, minZoom: 3, worldCopyJump: "true" });
map.setView([35.6,139.8], 5);
...
}
- 地図を
id="mapid"
に表示。 - 初期位置は緯度35.6度、経度139.8度(東京都周辺)。
- ユーザーの現在位置に基づいてズームインする機能も追加。
4. ベースマップの設定
const initializeBaseMaps = (mainMap) => {
const osmDarkLayer = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', ...);
...
}
- OpenStreetMapや地理院地図などの異なる背景地図を設定。
- ユーザーが地図を切り替えられるようにコントロールを追加。
5. パネルウィンドウの追加
const initializePanels = (mainMap) => {
const addInfoPanel = (lmap) => {
...
}
const addLegendPanel = (lmap) => {
...
}
}
- 気温情報を表示する情報パネルと、気温カラースケールを示す凡例パネルを地図に追加。
6. オーバーレイレイヤ
const initializeOverlayMaps = (mainMap, amedas, infoPanel) => {
...
const amedasTemperatureLayer = L.geoJSON(amedas.voronoiPolygons, { ... });
}
- ボロノイ図のポリゴンとアメダス観測所の位置をオーバーレイレイヤとして地図に描画。
- 気温に応じてポリゴンの色を変化。
CSS部分
.info {
padding: 6px 8px;
background: rgba(255,255,255,0.8);
...
}
.legend {
line-height: 18px;
color: #555555;
}
- 情報パネルと凡例パネルのスタイルを設定。
全体の流れ
- アメダスデータを取得し、観測所と気象データを整理。
- ボロノイ図を生成し、観測所ごとのエリアを作成。
- 地図を初期化し、ボロノイ図や観測所をオーバーレイレイヤとして追加。
- ユーザーが地図を操作しやすいように、情報パネルや凡例を表示。
アメダスの概要
アメダス(AMeDAS)とは「Automated Meteorological Data Acquisition System」の略で、「地域気象観測システム」といいます。
雨、風、雪などの気象状況を時間的、地域的に細かく監視するために、降水量、風向・風速、気温、湿度の観測を自動的におこない、気象災害の防止・軽減に重要な役割を果たしています。
アメダス観測所の例

四要素(降水量、風向・風速、気温、湿度)と積雪深を観測
アメダスは1974年11月1日に運用を開始して、現在、降水量を観測する観測所は全国に約1,300か所(約17km間隔)あります。
このうち、約840か所(約21km間隔)では降水量に加えて、風向・風速、気温、湿度を観測しているほか、雪の多い地方の約330か所では積雪の深さも観測しています。
アメダスで観測したデータを初期値として、数値予報モデルで計算し予測することで、天気予報を発表しています。
天気サイトを作ることで、気象学に興味を持ったあなたは、是非気象予報士試験に挑戦してみて下さいね