# 使用 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