Skip to Content

镜像地球开放平台

ECMWF 全球预报 WebGL 图层接入

使用 leaflet-wind WebglLayer 对 ECMWF IFS 全球预报温度数据进行 WebGL 渲染,支持全球覆盖、经度循环与前端点击取值。

ECMWF IFS(Integrated Forecasting System)是欧洲中期天气预报中心发布的全球预报模型,分辨率约 25km,覆盖全球,预报精度业界领先。

接入方式与 GFS 全球模型高度一致,仅模型标识和 URL 路径不同。

1. 关键参数说明

参数说明
modelecmwf
编码图 URLhttps://api.mirror-earth.com/api/vis/ecmwf/temperature_2m/{time}.jpeg?size=2048&apikey={apikey}
meta 接口https://api.mirror-earth.com/api/vis/ecmwf/meta?apikey={apikey}&timezone=Asia/Shanghai
coordinates全球四角坐标(同 GFS):[[-180, 85.05], [180, 85.05], [180, -85.05], [-180, -85.05]]
wrapXtrue
地图初始视角center: [20.0, 0.0]zoom: 2

与 GFS 的唯一区别:URL 中 archive_gfs 替换为 ecmwf,meta 接口路径同理。其余所有代码完全一致。

2. 安装依赖

npm install leaflet leaflet-wind

3. 示例代码

import { useEffect, useRef, useState } from 'react';
import 'leaflet/dist/leaflet.css';

const API_KEY = 'YOUR_API_KEY';

// 与 GFS 共用全球坐标
const GLOBAL_COORDS: [number, number][] = [
  [-180, 85.051129],  // NW
  [180, 85.051129],   // NE
  [180, -85.051129],  // SE
  [-180, -85.051129], // SW
];

const TEMP_COLORS: [number, number[]][] = [
  [-30, [98, 113, 183, 255]],
  [-20, [57, 97, 159, 255]],
  [-10, [74, 148, 169, 255]],
  [0, [77, 141, 123, 255]],
  [10, [83, 165, 83, 255]],
  [20, [167, 157, 81, 255]],
  [30, [159, 127, 58, 255]],
  [40, [175, 80, 136, 255]],
];

export default function EcmwfWebglDemo() {
  const mapRef = useRef<HTMLDivElement>(null);
  const mapInstance = useRef<any>(null);
  const layerRef = useRef<any>(null);
  const [clickedValue, setClickedValue] = useState<string | null>(null);
  const [status, setStatus] = useState('正在加载 ECMWF 全球图层...');

  useEffect(() => {
    if (!mapRef.current) return;
    let isMounted = true;

    const init = async () => {
      const L: any = await import('leaflet');
      const { WebglLayer, ImageSource, DecodeType, RenderFrom, RenderType } =
        await import('leaflet-wind');

      if (!isMounted || !mapRef.current) return;

      const map = new L.Map(mapRef.current, { zoom: 2, center: [20.0, 0.0] });
      L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
        subdomains: ['a', 'b', 'c', 'd'],
        attribution: '© CartoDB',
      }).addTo(map);
      mapInstance.current = map;

      try {
        // 1. 获取 ECMWF 最新时间(注意路径为 ecmwf)
        const { data: [time] } = await fetch(
          `https://api.mirror-earth.com/api/vis/ecmwf/meta?apikey=${API_KEY}&timezone=Asia/Shanghai`
        ).then(r => r.json());

        // 2. ECMWF 温度编码图 URL
        const url = `https://api.mirror-earth.com/api/vis/ecmwf/temperature_2m/${time}.jpeg?size=2048&apikey=${API_KEY}`;

        const source = new ImageSource('temperature', {
          url,
          coordinates: GLOBAL_COORDS,
          decodeType: DecodeType.imageWithExif,
          wrapX: true,
        });

        const interpolateColor = TEMP_COLORS.reduce(
          (acc, [val, rgba]) => acc.concat(val, `rgba(${rgba.join(',')})`),
          [] as any[]
        );

        const layer = new WebglLayer('temperature', source, {
          styleSpec: {
            'fill-color': ['interpolate', ['linear'], ['get', 'value'], ...interpolateColor],
            opacity: 0.8,
          },
          renderFrom: RenderFrom.r,
          displayRange: [-50, 50],
          renderType: RenderType.colorize,
          picking: true,
        });

        map.addLayer(layer);
        layerRef.current = layer;
        if (isMounted) setStatus('ECMWF 全球温度图层已加载,点击地图取值');

        map.on('click', async (e: any) => {
          if (!layerRef.current) return;
          const [v] = await layerRef.current.picker(e.latlng);
          setClickedValue(
            v != null
              ? `坐标: ${e.latlng.lat.toFixed(2)}, ${e.latlng.lng.toFixed(2)} | 温度: ${v.toFixed(2)}℃`
              : '无数据(超出图层范围)'
          );
        });
      } catch {
        if (isMounted) setStatus('图层加载失败,请检查 API Key');
      }
    };

    init();

    return () => {
      isMounted = false;
      mapInstance.current?.remove();
      mapInstance.current = null;
    };
  }, []);

  return (
    <div>
      <p style={{ padding: '8px 12px', background: '#fafafa', margin: 0 }}>{status}</p>
      <div ref={mapRef} style={{ width: '100%', height: '500px' }} />
      {clickedValue && (
        <div style={{ padding: '8px 12px', background: '#f6ffed', borderTop: '1px solid #b7eb8f' }}>
          {clickedValue}
        </div>
      )}
    </div>
  );
}

4. Demo 仓库

完整可运行示例(含 HRES/GFS/ECMWF 切换)已上传至 Gitee:

  • 仓库:https://gitee.com/gfyml/me-layer-demo.git
  • React 示例:react-demo/src/components/WebglLayerDemo.tsx(选择 ECMWF 模型)
  • Vue 示例:vue-demo/src/components/WebglLayerDemo.vue(选择 ECMWF 模型)

Previous

GFS 全球预报 WebGL 接入

Next

GFS 全球预报风场粒子接入