iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0

本身蠻喜歡地圖的,也想過要仔細研究GIS。
所以在認識到Mapbox和Leaflet時,就像挖到了寶藏。

其中前者是基於OSM的大型地圖服務;而後者正如其名,是個如葉子般輕量級的函式庫。像目前中央氣象局網站上的地圖就是以Leaflet所打造。
Mapbox本身有使用到Leaflet,後者的創辦人Volodymyr Agafonkin自己也在13年加入Mapbox,算是點「本是同根生」的小八卦。

網路上有不少根據Leaflet寫口罩地圖的教學資源,但卻沒看過很多從React Leaflet著手的。兩者的一大差異便是,名字有React的可以爽用宣告式語句。
心一橫,想說既然都要投入精神,就來闖比較少人走過的路吧。

這次打算來寫個關於下雨的地圖。

第一步先安裝react-leaflet。
然後拿官網提供的set up demo試試。
記得要import { MapContainer, Marker, Popup, TileLayer} from "react-leaflet";

誰曾想,最基礎的畫面卻render成了六親不認的樣子。
查找後發現,除了react-leaflet,leaflet本身仍要載呢。
還必須在css裡調整leaflet-container的高度。
感謝網友barbalex在issue#1052的回答

看似一切順利。但等等,地標的icon怎麼不見了……
這算是React Leaflet的坑,沒有把Leaflet預設之圖例移植過來。
我先是在stackoverflow上找到看起來很合理的解法,但依舊無效。
發現邏輯上是更改圖片網址的前綴後,徑直把寫法簡化成了L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";

總算是有個樣子了。

這裡會需要import L from 'leaflet';
而這個L我們後續還會再碰見。

import "./styles.css";
import "leaflet/dist/leaflet.css";
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";

import L from "leaflet";
L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";

export default function App() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
}

接著把經緯度改成臺灣的位置;旋即更改布林值,解封滾輪限制。
漸入佳境。再來用好入門的useEffect抓天氣api。

const [data, setData] = useState();
useEffect(() => {
fetch(
  "https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0002-001?Authorization=rdec-key-123-45678-011121314"
)
  .then((res) => res.json())
  .then((resJson) => setData(resJson))
  .catch((err) => console.log(err));
}, []);

此時我希望能夠抓到「嘉義」測站即時的雨量資訊,但只要一重新整理就會出事。隨後就想到,對耶,filter也要寫在useEffect這邊才對。

import "./styles.css";
import "leaflet/dist/leaflet.css";
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
import { useState, useEffect } from "react";

import L from "leaflet";
L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";

export default function App() {
  const [data, setData] = useState();
  useEffect(() => {
    fetch(
      "https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0002-001?Authorization=rdec-key-123-45678-011121314"
    )
      .then((res) => res.json())
      .then(
        (resJson) =>
          resJson.records.Station.filter((s) => s.StationName === "嘉義")[0]
            .RainfallElement.Now.Precipitation
      )
      .then((resJson) => setData(resJson))
      .catch((err) => console.log(err));
  }, []);

  return (
    <MapContainer center={[23.48, 120.45]} zoom={13} scrollWheelZoom={true}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[23.48, 120.45]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
          {JSON.stringify(data)}
        </Popup>
      </Marker>
    </MapContainer>
  );
}

中途我也有試過,如果想要嘉義市所有的測站資訊,該怎麼篩選。

.then((resJson) =>
    resJson.records.Station.filter((s) =>
      s.GeoInfo.CountyName.match("嘉義市")
    )
)

重頭戲來了:我們要分開取得測站名、雨量和經緯度資訊。
跳出警告Invalid LatLng object: (undefined, 120.45),其實是useState裡忘記加上初始值0的基本錯誤。記住一個useEffect做一件事的原則,就不會有問題了。

結果程式碼寫著寫著,我發現icon又不見了?!
一查,原來這次是API的網址連不上。

是不是呼叫上限?或其他問題?
這時還沒確定,便決定先睡一覺再說吧。


上一篇
【Day27】R3F 3
下一篇
【Day29】React Leaflet 2
系列文
【現在學React還來得及嗎?】30天Takeaway分享30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言