Try   HackMD

使用 react & leaflet.js 製作口罩地圖

tags: w3HexSchool . leaflet.js . react
github : andrew781026/findMask

最近武漢肺炎(2019-nCoV) 疫情爆發 , 大家瘋搶口罩 ,
為了方便查詢藥局的現有口罩數量 , 六角學院舉辦 口罩地圖 的製作活動 !

在下參加了這個活動 , 以下是小弟的製作過程

使用之第三方套件

1. leaflet : 顯示 openstreetmap 的輔助工具

2. geolib : 計算圖上 2 點距離

3. material-ui : react 的 UI 元件庫

建立環境

使用 create-react-app 建立

顯示地圖

demo : https://codesandbox.io/s/react-using-leaflet-gs64g

1. use create-react-app make a default project

2. 安裝 leaflet - yarn add leaflet

3. import leaflet & leaflet.css

import "leaflet/dist/leaflet.css"; import L from "leaflet";

4. 設定圖資來源

const OSMUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; L.tileLayer(OSMUrl).addTo(mymap);

完整的 App.js

// App.js import React from "react"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; // 參考資料 : https://leafletjs.com/examples/quick-start/ & https://juejin.im/post/5cc192976fb9a032092e8e0a class LeafletMap extends React.Component { componentDidMount() { const mymap = L.map("mapid").setView([25.03418, 121.564517], 17); const OSMUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; L.tileLayer(OSMUrl).addTo(mymap); // 使用 leaflet-color-markers ( https://github.com/pointhi/leaflet-color-markers ) 當作 marker const greenIcon = new L.Icon({ iconUrl: "https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png", shadowUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); const marker = L.marker([25.03418, 121.564517], { icon: greenIcon }).addTo( mymap ); marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup(); L.circle([25.03418, 121.564517], { color: "red", fillColor: "#f03", fillOpacity: 0.5, radius: 10 }).addTo(mymap); } render() { // 設定 height 顯示地圖 ( 預設值 height : 0 ) return <div id="mapid" style={{ height: "100vh", width: "100vw" }} />; } } export default LeafletMap;

結果 : 地圖呈現

取得 口罩數量

口罩數量 : https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json

設定提示框的樣式 ( Style popUp )

我們將預設的 下三角 拿掉 , 並控制不要有 padding 的產生

1.追加修改的 css 到 global 中

.popupCustom .leaflet-popup-tip { display: none; } .popupCustom .leaflet-popup-close-button { display: none; } .popupCustom .leaflet-popup-content-wrapper { background: transparent; color: transparent; } .popupCustom .leaflet-popup-content-wrapper .leaflet-popup-content { margin: 0; }

2.使用 popUp 時設定 className

marker.bindPopup(content, {className: 'popupCustom'});

3.製作一個 popUp 的 react-element

// src/index.js + import './styles/tailwind.css';
  • add Tooltip.js
// Tooltip.js import React from "react"; import Styles from "./Tooltip.module.css"; const Tooltip = ({medicalStore}) => ( <div className={Styles.card_root}> <div className={Styles.text_root}> <div className='pb-4'> <span className={Styles.text_name}>{medicalStore.name}</span> </div> <div className='pb-4'> <span className={Styles.text_address}> {medicalStore.address} </span> </div> <div className='flex'> <div className='flex-1 flex items-center'> <img className='px-4' height='12px' src={clockSvg} alt="時鐘 : "/> <span className={Styles.time_letter}>{medicalStore.time}</span> </div> <div className='flex-1 flex items-center'> <img className='px-4' src={phoneSvg} height='12px' alt="電話 : "/> <span className={Styles.phone_letter}>{medicalStore.phone}</span> </div> </div> </div> <div className='flex'> <div className={Styles.adult_mask}> <div>成人口罩數量</div> <div className='w-full flex items-end pt-4'> <div className='flex flex-1 justify-center items-center'> <img src={adultFaceSvg} height='37px' alt="成人頭像"/> </div> <div> <span className='text-16 pr-4'>{medicalStore.leftMask.adult}</span></div> </div> </div> <div className={Styles.children_mask}> <div>兒童口罩數量</div> <div className='w-full flex items-end pt-4'> <div className='flex flex-1 justify-center items-center'> <img src={childFaceSvg} height='40px' alt="兒童頭像"/> </div> <div> <span className='text-16 pr-4'>{medicalStore.leftMask.children}</span></div> </div> </div> </div> </div> ); export default Tooltip;
  • Styling Toolip.js
/* Tooltip.module.css */ .adult_mask { border-bottom-left-radius: 5px; display: flex; flex: 1; flex-direction: column; color: #fdfdfe; padding: 8px; background-color: #06b66c; } .children_mask { border-bottom-right-radius: 5px; display: flex; flex: 1; flex-direction: column; color: #fdfdfe; padding: 8px; background-color: #faa23f; } .text_name { color: #848484; font-size: 20px; font-weight: 900; } .text_address { color: #ababab; font-size: 11px; font-weight: 700; } .time_letter, .phone_letter { color: #ababab; font-size: 10px; letter-spacing: 2px; } .card_root { width: 300px; background-color: #fff; border-radius: 6px; border: 1px solid rgba(0, 0, 0, 0.3); /* Position the tooltip */ position: absolute; z-index: 1000; bottom: calc(100% + 10px); left: 50%; margin-left: -150px; display: flex; flex-direction: column; } .text_root { padding: 8px 14px 8px 14px; /* (top, right, bottom, and left) */ }

目標示意圖

render Element to String

使用 marker.bindPopup( string ) 來顯示上面格式化後的 Popup
並且利用 ReactDOMServer.renderToString 將 ReactElement 轉換成 string

參考資料

卡斯柏的口罩地圖教學共筆 : https://paper.dropbox.com/doc/2020-0213-2XPGbqH5JqE9vTD7a6npd