React-Leaflet+Material-UIでスライダを使った地図画像の重ね合わせ


Reactテンプレートの作成

$ pwd
/home/yono2844/Workspace
$ npx create-react-app sample-app
$ cd sample-app
$ npm install leaflet react-leaflet
$ npm install @material-ui
$ npm start

NOAA/GFSデータダウンロード&地図画像の作成

$ i=000
$ wget https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod/gfs.20210515/00/atmos/gfs.t00z.pgrb2.1p00.f${i}
$ gfs=gfs.t00z.pgrb2.1p00.f${i}
$ d=580
$ wgrib2 ${gfs} -d ${d} -csv - | awk 'BEGIN{FS=","}{print $5,$6,$7}' > tmp.xyz
$ gmt gmtset PROJ_ELLIPSOID=Sphere
$ gmt xyz2grd tmp.xyz -I1 -R-179/180/-90/90 -Gtmp.grd
$ gmt grd2cpt tmp.grd > tmp.cpt
$ gmt grdimage -Jm0.04 -R-180/180/-85/85 tmp.grd -Ctmp.cpt -K > tmp.eps
$ gmt pscoast -J -R -Df -W -O >> tmp.eps
$ bname=`basename ${gfs}`
$ gmt psconvert -Tg -A -P tmp.eps -D/home/yono2844/Workspace/sample-app/public -F${bname}.${d}

$ for i in `seq -f %03g 3 3 45`;do
>  echo ${i}
>  wget https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod/gfs.20210515/00/atmos/gfs.t00z.pgrb2.1p00.f${i}
>  gfs=gfs.t00z.pgrb2.1p00.f${i}
>  d=581
>  wgrib2 ${gfs} -d ${d} -csv - | awk 'BEGIN{FS=","}{print $5,$6,$7}' > tmp.xyz
>  gmt gmtset PROJ_ELLIPSOID=Sphere
>  gmt xyz2grd tmp.xyz -I1 -R-179/180/-90/90 -Gtmp.grd
>  gmt grd2cpt tmp.grd > tmp.cpt
>  gmt grdimage -Jm0.04 -R-180/180/-85/85 tmp.grd -Ctmp.cpt -K > tmp.eps
>  gmt pscoast -J -R -Df -W -O >> tmp.eps
>  bname=`basename ${gfs}`
>  gmt psconvert -Tg -A -P tmp.eps -D/home/yono2844/Workspace/sample-app/public -F${bname}.${d}
$ done

http://localhost:3000/地図画像名で確認。

実装

./sample-app/src/components/styles.js
.leaflet-container {
  height: 100vh;
}
./sample-app/src/components/MyMap.js
import React, { useState } from "react";
import { Map, TileLayer, ImageOverlay } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "./styles.css";
import { Slider, Typography } from "@material-ui/core";

const MyMap = () => {
  const [d, setD] = useState(0);
  const [url, setUrl] = useState(
    "http://localhost:3000/gfs.t00z.pgrb2.1p00.f000.580.png"
  );
  const handleChange = (event, newD) => {
    setD(newD);
    if (newD == 0) {
      setUrl("http://localhost:3000/gfs.t00z.pgrb2.1p00.f000.580.png");
    } else {
      setUrl(
        "http://localhost:3000/gfs.t00z.pgrb2.1p00" +
          ".f" +
          ("000" + newD).slice(-3) +
          ".581.png"
      );
    }
  };

  return (
    <div>
      <Slider value={d} min={0} max={45} step={3} onChange={handleChange} />
      <Typography>{url}</Typography>
      <Map center={[0, 0]} zoom={2} worldCopyJump={true}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <ImageOverlay
          url={url}
          bounds={[
            [-85, -180],
            [85, 180],
          ]}
          opacity={0.5}
        />
      </Map>
    </div>
  );
};

export default MyMap;
./sample-app/src/App.js
import React from "react";
import MyMap from "./components/MyMap";

const App = () => {
  return (
    <div>
      <MyMap />
    </div>
  );
};

export default App