# 使用 react & leaflet.js 製作口罩地圖 ###### tags: `w3HexSchool` . `leaflet.js` . `react` ###### github : [andrew781026/findMask](https://github.com/andrew781026/findMask) > 最近`武漢肺炎(2019-nCoV)` 疫情爆發 , 大家瘋搶口罩 , > 為了方便查詢藥局的現有口罩數量 , 六角學院舉辦 `口罩地圖` 的製作[活動](https://challenge.thef2e.com/news/21) ! > > 在下參加了這個活動 , 以下是小弟的製作過程 ## 使用之第三方套件 ### 1. [leaflet](https://www.npmjs.com/package/leaflet) : 顯示 openstreetmap 的輔助工具 ### 2. [geolib](https://www.npmjs.com/package/geolib) : 計算圖上 2 點距離 ### 3. [material-ui](https://www.npmjs.com/package/geolib) : 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` ```javascript= import "leaflet/dist/leaflet.css"; import L from "leaflet"; ``` ### 4. 設定圖資來源 ```javascript= const OSMUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"; L.tileLayer(OSMUrl).addTo(mymap); ``` > 完整的 `App.js` ```javascript= // 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; ``` > 結果 : 地圖呈現 <iframe src="https://codesandbox.io/embed/react-using-leaflet-gs64g?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="react-using-leaflet" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" ></iframe> ## 取得 `口罩數量` > 口罩數量 : https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json ## 設定提示框的樣式 ( Style popUp ) 我們將預設的 下三角 拿掉 , 並控制不要有 padding 的產生 ### 1.追加修改的 css 到 global 中 ```css= .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` ```javascript= marker.bindPopup(content, {className: 'popupCustom'}); ``` ### 3.製作一個 popUp 的 react-element - 在 index.js 中匯入 [`tailwind.css`](https://tailwindcss.com/) ```javascript= // src/index.js + import './styles/tailwind.css'; ``` - add Tooltip.js ```javascript= // 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 ```css= /* 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) */ } ``` 目標示意圖 ![](https://i.imgur.com/mgNHOnL.png) ## render Element to String > 使用 marker.bindPopup( string ) 來顯示上面格式化後的 Popup > 並且利用 [ReactDOMServer.renderToString](https://zh-hant.reactjs.org/docs/react-dom-server.html) 將 ReactElement 轉換成 string ## 參考資料 卡斯柏的口罩地圖教學共筆 : https://paper.dropbox.com/doc/2020-0213-2XPGbqH5JqE9vTD7a6npd