图片方式接入
通过图片方式将气象图层接入到 WebGIS 项目中,支持 React 和 Vue 示例,包含温度图层、雷达图及点击取值功能。
通过图片方式加载气象图层是最基础、也是接入门槛最低的方式。本指南将展示如何使用 Leaflet 在 React 和 Vue 项目中加载图层。
1. 业务流程
图片图层是通过单张带有地理范围的图片(Image Overlay)形式提供。在拼接图片 URL 之前,我们需要先获取最新的数据时间(time)。
- 获取时间:调用 meta 接口获取对应模型的可用时间列表,并取最新起报时间。
- 拼接 URL:将
time、model、element_level组合,生成单张图片 URL。格式如:https://api.mirror-earth.com/api/vis/part/{model}/{element}/{time}.webp?apikey={apikey}。- 关于投影:图片默认是以墨卡托投影(EPSG)返回(即接口自身默认
tms=3857)。如果您使用的底层地图为 WGS84 等距圆柱投影(EPSG),可以直接在 URL 的 query 传参中加入tms=4326以返回匹配的地图图片。
- 关于投影:图片默认是以墨卡托投影(EPSG)返回(即接口自身默认
- 加载到地图:指定对应模型和要素的地理范围(Bounds),使用 Leaflet 的
L.imageOverlay将该 URL 叠加到地图上。 - 点击取值:根据点击位置的经纬度,调用取值 API 获取该点的具体气象数据。
2. 示例应用 (Playground)
下面的代码示例展示了如何在项目中通过 Leaflet 加载图层(以 HRES 的温度图层、雷达图为例),并实现点击取值功能。
import React, { useEffect, useRef, useState } from 'react';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
const API_KEY = 'YOUR_API_KEY';
// 获取对应模型可用时间列表的最新时间
const fetchTime = async (model: string) => {
const metaUrl = `https://api.mirror-earth.com/api/vis/${model}/meta?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const response = await fetch(metaUrl);
const result = await response.json();
// 取列表中的第一个(最新时间)
return result.data[0];
};
const MapComponent = () => {
const mapRef = useRef<HTMLDivElement>(null);
const mapInstance = useRef<L.Map | null>(null);
const [clickedValue, setClickedValue] = useState<string | null>(null);
useEffect(() => {
if (!mapRef.current || mapInstance.current) return;
// 初始化地图
const map = L.map(mapRef.current).setView([35.0, 105.0], 4);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
mapInstance.current = map;
const loadLayers = async () => {
// --- HRES 温度图层接入 ---
const hresTime = await fetchTime('hres');
// HRES 覆盖中日韩区域的 bbox,格式为 [[南, 西], [北, 东]]
const hresBounds: L.LatLngBoundsExpression = [
[14.9587, 69.9588],
[55.0412, 140.0799] // 参考 1-quickstart 的 hres 覆盖范围
];
const tempUrl = `https://api.mirror-earth.com/api/vis/part/hres/temperature_2m/${hresTime}.webp?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const tempLayer = L.imageOverlay(tempUrl, hresBounds, { opacity: 0.7 });
tempLayer.addTo(map);
// --- 雷达图层接入 ---
const radarTime = await fetchTime('radar');
// 雷达覆盖范围的 bbox
const radarBounds: L.LatLngBoundsExpression = [
[15.5201, 71.965],
[54.0081, 148.7043] // 参考 1-quickstart 的 radar 覆盖范围
];
const radarUrl = `https://api.mirror-earth.com/api/vis/part/radar/composite_reflectivity/${radarTime}.webp?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const radarLayer = L.imageOverlay(radarUrl, radarBounds, { opacity: 0.7 });
// 可选:将雷达图也可以添加到地图上
// radarLayer.addTo(map);
};
loadLayers();
// 点击取值
map.on('click', async (e) => {
const { lat, lng } = e.latlng;
// 调用单点取值接口获取气象数据
const valueApiUrl = `https://api.mirror-earth.com/v1/point/hres/temperature_2m?lat=${lat}&lon=${lng}&apikey=${API_KEY}`;
try {
const response = await fetch(valueApiUrl);
const data = await response.json();
setClickedValue(`坐标: ${lat.toFixed(2)}, ${lng.toFixed(2)} | 温度: ${data.value}℃`);
} catch (err) {
setClickedValue('获取数据失败');
}
});
return () => {
map.remove();
mapInstance.current = null;
};
}, []);
return (
<div>
<div ref={mapRef} style={{ width: '100%', height: '500px' }} />
{clickedValue && (
<div style={{ marginTop: '10px', padding: '10px', background: '#f0f0f0', borderRadius: '4px' }}>
点击结果: {clickedValue}
</div>
)}
</div>
);
};
export default MapComponent;<template>
<div>
<div ref="mapContainer" style="width: 100%; height: 500px"></div>
<div v-if="clickedValue" class="result-box">
点击结果: {{ clickedValue }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
const mapContainer = ref(null);
let map = null;
const clickedValue = ref(null);
const API_KEY = 'YOUR_API_KEY';
// 获取对应模型可用时间列表的最新时间
const fetchTime = async (model) => {
const metaUrl = `https://api.mirror-earth.com/api/vis/${model}/meta?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const response = await fetch(metaUrl);
const result = await response.json();
return result.data[0];
};
onMounted(async () => {
if (!mapContainer.value) return;
// 初始化地图
map = L.map(mapContainer.value).setView([35.0, 105.0], 4);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// --- HRES 温度图层接入 ---
const hresTime = await fetchTime('hres');
const hresBounds = [
[14.9587, 69.9588],
[55.0412, 140.0799]
];
const tempUrl = `https://api.mirror-earth.com/api/vis/part/hres/temperature_2m/${hresTime}.webp?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const tempLayer = L.imageOverlay(tempUrl, hresBounds, { opacity: 0.7 });
tempLayer.addTo(map);
// --- 雷达图层接入 ---
const radarTime = await fetchTime('radar');
const radarBounds = [
[15.5201, 71.965],
[54.0081, 148.7043]
];
const radarUrl = `https://api.mirror-earth.com/api/vis/part/radar/composite_reflectivity/${radarTime}.webp?apikey=${API_KEY}&timezone=Asia/Shanghai`;
const radarLayer = L.imageOverlay(radarUrl, radarBounds, { opacity: 0.7 });
// radarLayer.addTo(map); // 如果需要直接展示雷达图
// 点击取值
map.on('click', async (e) => {
const { lat, lng } = e.latlng;
const valueApiUrl = `https://api.mirror-earth.com/v1/point/hres/temperature_2m?lat=${lat}&lon=${lng}&apikey=${API_KEY}`;
try {
const response = await fetch(valueApiUrl);
const data = await response.json();
clickedValue.value = `坐标: ${lat.toFixed(2)}, ${lng.toFixed(2)} | 温度: ${data.value}℃`;
} catch (err) {
clickedValue.value = '获取数据失败';
}
});
});
onUnmounted(() => {
if (map) {
map.remove();
}
});
</script>
<style scoped>
.result-box {
margin-top: 10px;
padding: 10px;
background: #f0f0f0;
border-radius: 4px;
}
</style>