JS F2E 精神時光屋 -口罩地圖

此為F2E 精神時光屋 - 口罩地圖的筆記
作品原始碼
作品連結


AJAX

為了抓取有販售的藥局,我們使用XMLHttpResquest API請求伺服器資料

 var xhr = new XMLHttpRequest();
 //準備和某某伺服要藥局剩餘口罩資料
 xhr.open("get", "https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json");
 //執行要資料的動作
 xhr.send();

 xhr.onload = function() {
 var data = JSON.parse(xhr.responseText).features;
 }
這樣我們就可以從data抓取資料

地圖

由於Google Map需要收錢,這邊我就使用openstreetmap圖資以及leaflet框架

地理位置定位

由於我們的位置需要判斷是否使用地理位置定位 (Geolocation),所以在使用地圖前先透過Promise方式取得位置在進行下一步


 let newpromise = new Promise(function(resolve, reject) {

    navigator.geolocation.getCurrentPosition(success, error)

    function success(position) {
        let userPosition = [position.coords.latitude, position.coords.longitude]
        resolve(userPosition)
    }

    function error() {
        let userPosition = [25.047702, 121.5151848];
        resolve(userPosition)
    }
 })
 newpromise.then(function(userPosition) {
   //內容
 }
上面抓取定位後,接著我們設定地圖

 //地圖的中心點與縮放距離
 var map = L.map('map', {
        zoom: 16, //縮放距離
        zoomControl: false
    });
    //地圖中心點
    map.setView(new L.LatLng(userPosition[0], userPosition[1]))

    //tileLayer:使用誰的圖資
    //attribution Leaflet原本的設定
    //addTo(map)新增到map變數的裡面
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors'
    }).addTo(map);
設定完地圖以及圖資後,接著是加入L.marker圖層
使用for迴圈
判斷口罩數量讓icon顏色呈現不同
套上圖層,並點擊後彈出HTML
最後套上圖層後,加入map裡

 var redIcon = new L.Icon({
    iconUrl: 'imgs/icon_nav_me.svg',
    shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
    iconSize: [28, 41],
    iconAnchor: [12, 41],
    popupAnchor: [1, -34],
    shadowSize: [41, 41]
 });

 //新增一個圖層,這圖層專門放icon群組,加入map裡
 var markers = new L.MarkerClusterGroup().addTo(map);
 var marker;

 //依序把maker到入圖層,跑陣列資料
 for (let i = 0; data.length > i; i++) {
    //map
    var mask;
    if (data[i].properties.mask_adult == 0 && data[i].properties.mask_child == 0) {
        mask = redIcon;
    } else {
        // mask = greenIcon;
        mask = L.divIcon({

            iconSize: [47, 40],
            iconAnchor: [20, 41], //經緯度位置
            popupAnchor: [1, -34],
            html: `
     ${data[i].properties.mask_adult + data[i].properties.mask_child}`,
            className: 'custom-div-icon',
        })
    }

    let distance = getDistance(userPosition[0], userPosition[1], data[i].geometry.coordinates[1], data[i].geometry.coordinates[0])
    
    markers.addLayer(L.marker([data[i].geometry.coordinates[1], data[i].geometry.coordinates[0]], { icon: mask }).bindPopup(
        `

${data[i].properties.name}

${distance >= 1 ? distance.toFixed(1) + 'km' : (distance * 1000 >>0) + 'm'} ${data[i].properties.updated}
成人 ${data[i].properties.mask_adult}
兒童 ${data[i].properties.mask_child}
` )); } //新增圖層到map裡 map.addLayer(markers); //用來控制可控制距離位置的按鈕要放在哪裡 L.control.zoom({ position: 'bottomright' }).addTo(map);

資料判斷與呈現

篩選資料

藥局資訊處理,在搜尋打上搜尋資料後,判斷後呈現

  • String.trim():用來去除字串前後的空白,此方法並不會改變原來的字串,而是傳回一個新的字串。
  • indexOf() :方法會回傳給定元素於陣列中第一個被找到之索引,若不存在於陣列中則回傳 -1。
  • for迴圈判斷地址和藥局名稱有相關字時,儲存資料到陣列


    let pharmacyStore = [];
    //搜尋字
    let searchValue = searchInput.value.trim();
    //空值
    if (searchValue == '') {
        return;
    }
    //顯示搜尋資訊
    for (let i = 0; data.length > i; i++) {
        if (data[i].properties.address.indexOf(searchValue) != -1 || data[i].properties.name.indexOf(searchValue) != -1) {
            //儲存data
            pharmacyStore.push(data[i]);
        }
    }

    StoreInfo(pharmacyStore);

於是pharmacyStore就有搜尋到的資料,我們就可以使用這份篩選過的資料給予其他使用ex:口罩數量、距離..等

呈現資料


  • 使用for迴圈呈現資料
  • 若篩選的資料有符合收藏的資料,回傳true


 function StoreInfo(pharmacyStore) {

    let searchList = [];
    let str = "";
    container.innerHTML = ''; //const container = document.querySelector('.side_nav_item')
    for (let i = 0; pharmacyStore.length > i; i++) {
        //星號標記是否為true
        let stared = starData.some(function(e) {
            return e === pharmacyStore[i].properties.phone
        })
        //距離計算
        let distance = getDistance(userPosition[0], userPosition[1], pharmacyStore[i].geometry.coordinates[1], pharmacyStore[i].geometry.coordinates[0])
        let div = document.createElement('div');
        div.className = 'card maskInfo'
        div.dataset.lat = pharmacyStore[i].geometry.coordinates[1]
        div.dataset.lng = pharmacyStore[i].geometry.coordinates[0]
        div.innerHTML = `
                

${pharmacyStore[i].properties.name}

${distance >= 1 ? distance.toFixed(1) + 'km' : (distance * 1000 >>0) + 'm'}
  • ${pharmacyStore[i].properties.address}
  • ${pharmacyStore[i].properties.phone}
  • 營業時間:${pharmacyStore[i].properties.note ? pharmacyStore[i].properties.note : '暫無資料'}
成人 ${pharmacyStore[i].properties.mask_adult}
兒童 ${pharmacyStore[i].properties.mask_child}
${data[i].properties.updated} 更新 ` container.appendChild(div); } }
距離計算

 function getDistance(lat1, lng1, lat2, lng2) {
    return 2 * 6378.137 * Math.asin(Math.sqrt(Math.pow(Math.sin(Math.PI * (lat1 - lat2) / 360), 2) + Math.cos(Math.PI * lat1 / 180) * Math.cos(Math.PI * lat2 / 180) * Math.pow(Math.sin(Math.PI * (lng1 - lng2) / 360), 2)))
 }

收藏

我們透過localStorage把想收藏的資訊儲存,並下次打開時依然存在

  • 首先判斷starData是否有資料,或者空值
  • 接著把資料傳到function判斷
  • 最後把資料放到陣列給StoreInfo(starlocal);


 const starData = JSON.parse(localStorage.getItem('starLocal')) || [];

 getStarlocal(item)


接著若有資料的話

 function getStarlocal(item) {
    let str = '';
    let starlocal = [];
    item.forEach(function(e) {
        for (let i = 0; data.length > i; i++) {
            if (data[i].properties.phone === e) {
                starlocal.push(data[i]);
            }
        }
    })

    StoreInfo(starlocal);
}

星號標記

我們可以收藏,就需要一個按鈕來代表已收藏
取得元素後使用forEach遍歷,使點選後可以加入/刪除localStorage

 //星號按鈕 加入收藏
 function starBtnActive() {
    const star = document.querySelectorAll('.maskInfo .icon-star');
    const stared = document.querySelectorAll('.maskInfo .icon-stared');

    star.forEach(function(e) {
        const starTel = e.parentNode.parentNode.children[0].children[2].children[1].textContent;
        e.addEventListener('click', function(el) {

            starData.push(starTel);
            e.classList.add('hide');
            e.parentNode.children[1].classList.add('show');
            localStorage.setItem('starLocal', JSON.stringify(starData));
            el.cancelBubble = true;
        })
    })

    stared.forEach(function(e) {
        const starTel = e.parentNode.parentNode.children[0].children[2].children[1].textContent;
        e.addEventListener('click', function(el) {
            removeByValue(starData, starTel);
            e.classList.remove('show');
            e.parentNode.children[0].classList.remove('hide');
            localStorage.setItem('starLocal', JSON.stringify(starData));
            el.cancelBubble = true;
        })

    })

 }

forEach:方法返回一個由原陣列中的每個元素呼叫一個指定方法後的返回值組成的新陣列
removeByValue(starData, starTel);用來刪除指定值

// 刪除陣列中的特定數值或字串。
function removeByValue(array, value) {
    return array.forEach((item, index) => {
        if (item === value) {
            array.splice(index, 1);
        }
    })
}
以上就是本次作品最主要區塊,其實搜尋口罩延伸出來的功能也蠻多的!
這次真的受益良多

留言