自製口罩地圖
說明
- 武漢肺炎/新冠狀病毒(COVID-19)全球肆虐,人人都需要口罩。
- 身為攻城獅/碼農,跟風自己寫一個口罩地圖方便買口罩是一定要的啦!
- CSS 框架採用 Bootstrap,JS 採用了 jQuery。
- 地圖採用 leaflet,這是整合 OpenStreetMap 的地圖套件。
- 因為需要 Cluster 的功能,需要額外引入 MarkerCluster 才能使用。
Code
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>口罩地圖 by Mike</title>
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
}
</style>
<!-- jquery -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<!-- bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<!-- bootstrap-select -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.9/dist/css/bootstrap-select.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.9/dist/js/bootstrap-select.min.js"></script>
<!-- leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.css">
</link>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css">
</link>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script>
<style>
.marker-cluster-small {
background-color: rgba(181, 226, 140, 0.6);
}
.marker-cluster-small div {
background-color: rgba(110, 204, 57, 0.6);
}
.marker-cluster-medium {
background-color: rgba(241, 211, 87, 0.6);
}
.marker-cluster-medium div {
background-color: rgba(240, 194, 12, 0.6);
}
.marker-cluster-large {
background-color: rgba(253, 156, 115, 0.6);
}
.marker-cluster-large div {
background-color: rgba(241, 128, 23, 0.6);
}
.marker-cluster {
background-clip: padding-box;
border-radius: 20px;
}
.marker-cluster div {
width: 30px;
height: 30px;
margin-left: 5px;
margin-top: 5px;
text-align: center;
border-radius: 15px;
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.marker-cluster span {
line-height: 30px;
}
/*Legend specific*/
.legend {
padding: 6px 8px;
font: 14px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255, 255, 255, 0.8);
/*box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);*/
/*border-radius: 5px;*/
line-height: 24px;
color: #555;
}
.legend h4 {
text-align: center;
font-size: 16px;
margin: 2px 12px 8px;
color: #777;
}
.legend span {
position: relative;
bottom: 3px;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin: 0 8px 0 0;
opacity: 0.7;
}
.legend i.icon {
background-size: 12px;
background-color: rgba(255, 255, 255, 1);
}
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#">口罩地圖</a>
<select class="selectpicker" id="rangeSel">
<option value="1000">搜尋1公里內的藥局</option>
<option value="5000">搜尋5公里內的藥局</option>
<option value="10000">搜尋10公里內的藥局</option>
<option value="15000">搜尋15公里內的藥局</option>
<option value="20000">搜尋20公里內的藥局</option>
</select>
</nav>
<div id="mapid" style="width: 100%; height: 90%;"></div>
<script>
var mymap = L.map('mapid');
var layerGroup = L.layerGroup().addTo(mymap);
var markers = L.markerClusterGroup();
var mask_api = "https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json";
var masklist = [];
var mylon = 121.564101;
var mylat = 25.033493;
var range = 1000; //default search 1km
var 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]
});
var redIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.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]
});
/*Legend specific*/
var legend = L.control({ position: "bottomleft" });
legend.onAdd = function (map) {
var div = L.DomUtil.create("div", "legend");
div.innerHTML += "<h4>圖例</h4>";
// div.innerHTML += '<i style="background: #477AC2"></i><span>Water</span><br>';
// div.innerHTML += '<i style="background: #448D40"></i><span>Forest</span><br>';
div.innerHTML += '<i class="icon" style="background-image: url(https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png);background-repeat: no-repeat;"></i><span>你的位置</span><br>';
div.innerHTML += '<i class="icon" style="background-image: url(https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png);background-repeat: no-repeat;"></i><span>有口罩的藥局</span><br>';
div.innerHTML += '<i class="icon" style="background-image: url(https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png);background-repeat: no-repeat;"></i><span>沒口罩的藥局</span><br>';
return div;
};
legend.addTo(mymap);
$("#rangeSel").change(function () {
range = parseInt($('#rangeSel').val());
// console.log(range);
layerGroup.clearLayers();
markers.clearLayers();
setCircle();
setMyPosition();
setMaskPosition();
})
if ("geolocation" in navigator) {
/* geolocation is available */
navigator.geolocation.getCurrentPosition(function (position) {
// do_something(position.coords.latitude, position.coords.longitude);
mylon = position.coords.longitude;
mylat = position.coords.latitude;
// console.log(mylat, mylon);
mymap.setView([mylat, mylon], 14)
// L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
// attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors | ' +
// 'App developed by <a href="https://mike2014mike.github.io/">Mike Chen</a>'
// }).addTo(mymap);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: '© <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, ' +
'App developed by <a href="https://mike2014mike.github.io/">Mike Chen</a>',
id: 'mapbox.streets'
}).addTo(mymap);
setCircle();
setMyPosition();
});
} else {
/* geolocation IS NOT available */
}
var xhr = new XMLHttpRequest();
xhr.open("get", mask_api);
xhr.send();
xhr.onload = function () {
var data = JSON.parse(xhr.responseText).features
data.map(function (x) {
masklist.push({
name: x.properties.name,
address: x.properties.address,
adult: x.properties.mask_adult,
child: x.properties.mask_child,
lon: x.geometry.coordinates[0],
lat: x.geometry.coordinates[1]
});
})
console.log(masklist);
setMaskPosition();
}
function setMyPosition() {
L.marker([mylat, mylon]).addTo(mymap).bindPopup("<strong>你的位置</strong>").openPopup();
}
function setCircle() {
var circle = L.circle(
[mylat, mylon], // 圓心座標
range, // 半徑(公尺)
{
color: 'blue', // 線條顏色
fillColor: 'blue', // 填充顏色
fillOpacity: 0.2 // 透明度
}
).addTo(layerGroup);
console.log(mylat, mylon, range);
}
var range_list = [];
function setMaskPosition() {
range_list.length = 0;
masklist.map(function (item, index) {
var dis = distanceByLnglat(item.lat, item.lon, mylat, mylon);
if (dis < range) {
var link = "https://www.google.com/maps/dir/?api=1&destination=" + item.lat + "," + item.lon;
var str = "<strong>" + item.name + "</strong><br>";
str += '<a target="_blank" href="' + link + '">' + item.address + '</a><br>';
str += "<span style='color: green;'>成人口罩:" + item.adult + "</span><br>";
str += "<span style='color: blue;'>兒童口罩:" + item.child + "</span><br>";
// str += '<button type="button" class="btn btn-secondary btn-sm btn-block">前往</button>';
// str += '<a target="_blank" style="color: white;" href="' + link + '" class="btn btn-secondary btn-sm btn-block" role="button">' + item.address + '</a>';
var myIcon = greenIcon;
if (item.adult == 0 || item.child == 0) myIcon = redIcon;
// L.marker([item.lat, item.lon], { icon: myIcon }).addTo(layerGroup).bindPopup(String(str));
markers.addLayer(L.marker([item.lat, item.lon], { icon: myIcon }).bindPopup(String(str))).addTo(layerGroup);
item.dis = dis;
range_list.push(item);
}
});
range_list = range_list.sort(function (a, b) {
return a.dis > b.dis ? 1 : -1;
});
console.log(range_list);
}
function distanceByLnglat(lat1, lng1, lat2, lng2) {
var radLat1 = Rad(lat1);
var radLat2 = Rad(lat2);
var a = radLat1 - radLat2;
var b = Rad(lng1) - Rad(lng2);
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * 6378137.0;// 取WGS84標準參考橢球中的地球長半徑(單位:m)
s = Math.round(s * 10000) / 10000;
return s;
// //下面為兩點間空間距離(非球面體)
// var value= Math.pow(Math.pow(lng1-lng2,2)+Math.pow(lat1-lat2,2),1/2);
// alert(value);
}
function Rad(d) {
return d * Math.PI / 180.0;
}
</script>
</body>
</html>