入門Leaflet之小Demo

liuvigongzuoshi發表於2018-03-14

入門Leaflet之小Demo


寫在前面 —- WebGIS開發基礎之Leaflet

  1. GIS基本概念:GIS、Map、Layer、Feature、Geometry、Symbol、Data(Point、Polyline、Polygon)、Renderer、Scale、Project、Coordinates;
  2. GIS開發概述:架構模式、常用平臺和SDK、二維三維
  3. 使用Leaflet開發常用功能

    • 地圖載入(底圖型別、切換):
    • 地圖操作(縮放、平移、定位/書籤、動畫):
    • 圖層管理(載入、移除、調整順序):
    • 要素標繪(點/聚簇、線、面,符號化/靜態動態):
    • 屬性標註(欄位可選、樣式定製):
    • 專題地圖(點、線、面,渲染):
    • 查詢定位(屬性查詢、空間查詢/周邊搜尋/緩衝區/面查點線面/點線查面、圖屬互查、綜合查詢):
    • 資訊視窗(入口、Popup、定製):
    • 座標轉換():
    • 空間運算(長度面積測量、點取座標、緩衝區、相交包含關係):
    • 動態監控(固定點狀態切換、車輛監控):

  1. Leaflet API

Demo用到的庫


PART 1: 地圖載入(底圖型別、切換) Demo 1

  • 庫引用
<link rel="stylesheet" type="text/css" href="./lib/Flat-UI-master/dist/css/vendor/bootstrap/css/bootstrap.min.css"
    /> 
<link rel="stylesheet" href="./lib/Flat-UI-master/dist/css/flat-ui.min.css">
<link rel="stylesheet" href="./lib/leaflet/leaflet.css">
<script src="./lib/Flat-UI-master/dist/js/vendor/jquery.min.js"></script>
<script src="./lib/Flat-UI-master/dist/js/flat-ui.js"></script>
<script src="./lib/leaflet/leaflet.js"></script>
<script src="./js/urlTemplate.js"></script>
  • 地圖載入與切換
const map = L.map("mapDiv", {
        crs: L.CRS.EPSG3857, //要使用的座標參考系統,預設的座標參考系,網際網路地圖主流座標系
        // crs: L.CRS.EPSG4326, //WGS 84座標系,GPS預設座標系
        zoomControl: true,
        // minZoom: 1,
        attributionControl: true,
    }).setView([30.6268660000, 104.1528940000], 18);//定位在成都北緯N30°37′45.58″ 東經E104°09′1.44″
    let Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
       maxZoom: 17, //最大檢視
        minZoom: 2, //最小檢視
        attribution: `liuvigongzuoshi@foxmail.com  &copy; <a href="https://github.com/liuvigongzuoshi/WebGIS-for-learnning/tree/master/Leaflet_Demo">WebGIS-for-learnning</a>`
    }).addTo(map);

const setLayer = (ele) => {
    map.removeLayer(Baselayer)
    if (ele == "mapbox_Image") {
        Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
            maxZoom: 17,
            minZoom: 2
        }).addTo(map);
    } else if (ele == "mapbox_Vector") {
        Baselayer = L.tileLayer(urlTemplate.mapbox_Vector, {
            maxZoom: 17,
            // minZoom: 2
        }).addTo(map);
        console.log(Baselayer)
    }
}

基於Demo 1 利用H5 Geolocation API 定位到當前位置 Demo 1.1

  • 庫引用 如上 Demo 1
//marker高亮顯示庫引用
<link rel="stylesheet" href="./lib/leaflet.marker.highlight/leaflet.marker.highlight.css">
<script src="./lib/leaflet.marker.highlight/leaflet.marker.highlight.js"></script>
  • 判斷瀏覽器是否支援
    let map;
    let Baselayer;
    // 使用H5 API定位 定位在當前位置
    if (navigator.geolocation) {
        console.log(`/* 地理位置服務可用 */`)
        navigator.geolocation.getCurrentPosition(h5ApiSuccess, h5ApiError);
    } else {
        console.log(`/* 地理位置服務不可用 */`)
        mapInit([30.374558, 104.09144]);//指定一個資料 定位在成都北緯N30°37′45.58″ 東經E104°09′1.44″
    }
  • 定位成功或失敗
    const h5ApiSuccess = (position) => {
        let latitude = position.coords.latitude; //緯度
        let longitude = position.coords.longitude; //經度
        console.log(`你的經度緯度分別為` + longitude + `,` + latitude + `。`)
        return mapInit([latitude, longitude]);
    };

    const h5ApiError = () => {
        console.log(`/* 地理位置請求失敗 */`)
        mapInit([30.374558, 104.09144]);//指定一個資料 定位在成都北緯N30°37′45.58″ 東經E104°09′1.44″
    };
  • 成功後初始化底圖
    const mapInit = (LatLng) => {
        map = L.map("mapDiv", {
            crs: L.CRS.EPSG3857, //要使用的座標參考系統,預設的座標參考系
            // crs: L.CRS.EPSG4326, //國內的座標參考系
            zoomControl: true,
            // minZoom: 1,
            attributionControl: true,
        }).setView(LatLng, 18);//定位在當前位置
        Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
            maxZoom: 17, //最大檢視
            minZoom: 2, //最小檢視
            attribution: `liuvigongzuoshi@foxmail.com  &copy; <a href="https://github.com/liuvigongzuoshi/WebGIS-for-learnning/tree/master/Leaflet_Demo">WebGIS-for-learnning</a>`
        }).addTo(map);
        
        L.marker(LatLng, {
            highlight: "permanent" //永久高亮顯示
        }).addTo(map);
    }
  • 更多瞭解geolocation物件,可參考MDN Web 文件
  • 更多瞭解使用marker高亮顯示,可參考leaflet.marker.highlight外掛
  • 基於Demo 1 利用eaflet封裝好的H5定位API,定位到當前位置 Demo

PART 2: 地圖操作(縮放、平移、定位/書籤、動畫) Demo 2

  • 庫引用 如上 Demo 1
  • 設定地圖縮放到指定圖層
map.setZoom(10, {
  // animate: false
})  //設定地圖縮放到
  • 圖層往裡進一個圖層,放大
map.zoomIn() //圖層往裡進一個圖層,放大
//map.zoomOut()  //圖層往裡出一個圖層,縮小
  • 地圖平移至中心點
map.panTo([37.91082, 128.73583], {
    animate: true
}) //地圖平移,預設就是true,將地圖平移到給定的中心。如果新的中心點在螢幕內與現有的中心點不同則產生平移動作。
  • 地圖飛到中心點
map.flyTo([36.52, 120.31]); // 點到點的拋物線動畫,平移加縮放動畫

注意:儘量避免setZoom()等地圖縮放方法與flyTo、flyToBounds一起合用,因為這兩類地圖操作方法都有各自的縮放值,造成動畫不流暢、不能定位到目的點。

  • 地圖飛到邊界的合適的位置
map.flyToBounds(polygon.getBounds());   //getBounds(獲取邊界):返回地圖檢視的經緯度邊界。
    //飛到這個多變形區域上面,自動判斷區域塊的大小,合適縮放圖層,將地圖檢視儘可能大地設定在給定的地理邊界內。
    
let polygon = L.polygon(
          [[37, -109.05], 
          [41, -109.03], 
          [41, -102.05], 
          [37, -102.04]],
     [40.774, -74.125], {
       color: `green`,
       fillColor: `#f03`,
       fillOpacity: 0.5
    }).addTo(map);  //地圖上繪製一個多形
  • 地圖定位到邊界的合適的位置
map.fitBounds(polygon.getBounds());  //getBounds(獲取邊界):返回地圖檢視的經緯度邊界。
  //平移到一個區域上面,自動判斷區域塊的大小,合適縮放圖層
    
let polygon = L.polygon(
          [[37, -109.05], 
          [41, -109.03], 
          [41, -102.05], 
          [37, -102.04]],
     [40.774, -74.125], {
       color: `green`,
       fillColor: `#f03`,
       fillOpacity: 0.5
    }).addTo(map);  //地圖上繪製一個多邊形

PART 3: 圖層管理(載入、移除、調整順序): Demo 3

  • 庫引用
<link rel="stylesheet" type="text/css"  href="./lib/Flat-UI-master/dist/css/vendor/bootstrap/css/bootstrap.min.css"
    />
<link rel="stylesheet" href="./lib/Flat-UI-master/dist/css/flat-ui.min.css">
<link rel="stylesheet" href="./lib/leaflet/leaflet.css">
<script src="./lib/Flat-UI-master/dist/js/vendor/jquery.min.js"></script>
<script src="./lib/Flat-UI-master/dist/js/flat-ui.js"></script>
<script src="./lib/leaflet/leaflet.js"></script>
<script src="./lib/esri-leaflet-v2.1.2/dist/esri-leaflet.js"></script> <!-- esri-leafleat外掛 -->
<script src="./js/urlTemplate.js"></script>
  • 使用esri-leaflet外掛載入ArcGIS底圖服務
let oMap = null;
    let oLayer = [];

    oMap = L.map(`mapDiv`, {
        crs: L.CRS.EPSG4326,
        zoomControl: false,
        minZoom: 7,
        attributionControl: false
    }).setView([29.59, 106.59], 12); //定位在重慶
    
    oLayer.push(L.esri.tiledMapLayer({
        url: urlTemplate.SYS_CQMap_IMG_MAPSERVER_PATH,
        maxZoom: 17,
        minZoom: 0,
        useCors: false, //是否瀏覽器在跨域的情況下使用GET請求。
    }).addTo(oMap)); //載入第一個底圖

    oLayer.push(L.esri.tiledMapLayer({
        url: urlTemplate.SYS_CQMap_IMG_LABEL_MAPSERVER_PATH,
        maxZoom: 17,
        minZoom: 0,
        useCors: false,
    }).addTo(oMap));  //載入第二個底圖
  • 切換底圖(移除及載入)
const setLayer = (layerUrls, maxZoom) => {
        for (let i = 0; i < oLayer.length; i++) {
            oMap.removeLayer(oLayer[i]) //將圖層在地圖上移除
        }
        oLayer = [] //制空陣列
        layerUrls.map((item) => {
            oLayer.push(L.esri.tiledMapLayer({
                url: item,
                useCors: false, 
                maxZoom: maxZoom, // 設定最大放大圖層值
            }).addTo(oMap));
        })
    }

不同的底圖可能圖層數不一樣,就可能造成瀏覽器去請求不存在的圖層,以及給使用者展示出空白區域的不好體驗,所以切換圖層時候應注意設定最大及最小縮放值。

PART 4: 要素標繪(點、線、面,符號化/靜態動態) Demo 4

  • 庫引用 如上 Demo 1
  • 畫一個圓
// 畫一個circle
const circle = L.circle([36.52, 120.31], {
  color: `green`, //描邊色
  fillColor: `#f03`,  //填充色
  fillOpacity: 0.5, //透明度
  radius: 10000 //半徑,單位米
}).addTo(map);
// 繫結一個提示標籤
circle.bindTooltip(`我是個圓`);
  • Maker及自定義Maker
// 做一個maker
const marker = L.marker([36.52, 120.31]).addTo(map);
// 繫結一個提示標籤
marker.bindTooltip(`這是個Marker`, { direction: `left` }).openTooltip();


//自定義一個maker
const greenIcon = L.icon({
  iconUrl: `./icon/logo.png`,
  iconSize: [300, 79], // size of the icon
  popupAnchor: [0, -10] // point from which the popup should open relative to the iconAnchor
});

const oMarker = L.marker([36.52, 124.31], { icon: greenIcon }).addTo(map);
// 繫結一個提示標籤
oMarker.bindTooltip(`這是個自定義Marker`, { direction: `left`, offset: [-150, 0] });
  • 畫一根線
//畫一根線
const polyline = L.polyline([[45.51, -122.68], [37.77, -122.43], [34.04, -118.2]], { color: `red` }).addTo(map);
// 飛到這個線的位置
// map.fitBounds(polyline.getBounds());
  • 畫一個多邊形
// 畫一個polygon
const polygon = L.polygon([
  [[37, -109.05], [41, -109.03], [41, -102.05], [37, -102.04]], // outer ring
  [[37.29, -108.58], [40.71, -108.58], [40.71, -102.50], [37.29, -102.50]] // hole
], {
    color: `green`,
    fillColor: `#f03`,
    fillOpacity: 0.5
  }).addTo(map);
// 繫結一個提示標籤
polygon.bindTooltip(`this is 個多邊形`);
// 飛到這個多邊形的位置
// map.fitBounds(polygon.getBounds());

### PART 5: 資訊視窗(入口、Popup、定製) Demo 5

  • 庫引用 如上 Demo 1

    • 畫一個circle並繫結一個Popup
    // 畫一個circle
    const circle = L.circle([36.92, 121.31], {
     color: `green`, //描邊色
     fillColor: `#f03`,  //填充色
     fillOpacity: 0.5, //透明度
     radius: 10000 //半徑,單位米
    }).addTo(map);
    
    // 繫結一個彈窗
    circle.bindPopup(`我是個圓`);
    • 定位一個marker,繫結一個自定義Popup
// 定位一個maker
const marker = L.marker([36.52, 120.31]).addTo(map);

//maker上自定義一個popup
const html = `<p>Hello world!<br />This is a nice popup.</p>`;

const popup = marker.bindPopup(html, { maxHeight: 250, maxWidth: 490, className: `content`, offset: [0, 0] }).on(`popupopen`, function (params) {
  console.log(params)
});
  • 實現動態改變Popup的內容
const mypop = L.popup();

map.on(`click`, function (e) {
  mypop.setLatLng(e.latlng)
    .setContent(`你臨幸了這個點:<br>` + e.latlng.toString())
    .openOn(map);
});

### PART 6: geojson 資料繪製邊界(座標轉換、渲染) Demo 6

  • 庫引用 如上 Demo 1
  • 獲得geojson並處理資料
// 請求geojson並處理資料
const population = () => {
    $.get("./js/geojson.json", function (response) {
        const poplData = response.data
        const PolygonsCenter = response.geopoint
        drawPolygons(poplData, PolygonsCenter)
    });
}

模擬後臺返回的資料geojson

  • 繪製邊界並新增圖例
let oPolygon_VilPop = [];

const legend = L.control({
    position: `bottomright`
 });
 
const drawPolygons = (poplData, PolygonsCenter) => {
    for (const i in poplData) {
        poplData[i].geoJson = JSON.parse(poplData[i].geoJson)
        oPolygon_VilPop[i] = L.geoJSON(poplData[i].geoJson, {
            style: function () {
                return {
                    color: `white`,
                    fillColor: getBgColor(poplData[i].population), //獲取邊界的填充色
                    fillOpacity: 0.6,
                    weight: 3,
                    dashArray: `10`
                };
            }
        }).bindTooltip(poplData[i].villageName + `<br><br>人口` + poplData[i].population + `人`, {
            direction: `top`
        }).on({
            mouseover: highlight, //滑鼠移動上去高亮
            mouseout: resetHighlight, //滑鼠移出恢復原樣式
            click: zoomTo //點選最大化
        }).addTo(oMap);
    }

    // 新增圖例
    legend.onAdd = legendHtml;
    legend.addTo(oMap);

    // 定位到該界限的中心位置
    oMap.flyToBounds(PolygonsCenter);
}
    
// 新增圖例
legend.onAdd = legendHtml;
legend.addTo(oMap);

// 定位到該界限的中心位置
oMap.flyToBounds(PolygonsCenter);
}
  • 返回邊界的填充色及圖列的樣式
const getBgColor = (d) => {
    return d > 400 ? `#800026` : d > 300 ? `#BD0026` : d > 200 ? `#FC4E2A` : d > 100 ? `#FD8D3C` : d > 50 ? `#FED976` : `#FFEDA0`;
}

const legendHtml = (map) => {
    let div = L.DomUtil.create(`div`, `legend locateVP_legend`),
        grades = [0, 50, 100, 200, 400],
        labels = [],
        from, to;
    for (let i = 0; i < grades.length; i++) {
        from = grades[i];
        to = grades[i + 1];
        labels.push(
            `<i style="background:` + getBgColor(from + 1) + `"></i> ` +
            from + (to ? ` &sim; ` + to + `人` : `以上`));
    }
    div.innerHTML = labels.join(`<br>`);
    return div;
    };
  • 滑鼠移動上去的事件、滑鼠移出的事件、發生點選的事件
const highlight = (e) => {
    let layer = e.target;
    layer.setStyle({
        weight: 6,
        color: `#fff`,
        fillOpacity: 0.9,
        dashArray: `0`
    })
}

const resetHighlight = (e) => {
    let layer = e.target;
    layer.setStyle({
        color: `white`,
        weight: 3,
        fillOpacity: 0.6,
        dashArray: `10`
    })
}

const zoomTo = (e) => {
    oMap.fitBounds(e.target.getBounds());
}

寫在後面

國內常用地圖服務資源載入外掛

Leaflet.ChineseTmsProviders Provider for Chinese Tms Service

  • Leaflet呼叫國內各種地圖的功能十分複雜,幸好有leaflet.ChineseTmsProviders這個外掛,這四種地圖直接就可以載入進來,十分方便。
  • 使用方法很簡單可點選上面連結去GitHub看使用說明,或拉這個demo下來來瞧一瞧程式碼。

優化marker相關的外掛

模組化開發的載入包注意的問題

  • 引 leaflet 包的時候不要忘記引用包裡的css import `leaflet/dist/leaflet.css`;

關於Leaflet 1.2.0 和esri-leaflet 2.1.1 一起使用L.esri.TiledMapLayer載入ArcGIS 服務切片底圖時,控制檯列印報錯 Uncaught ReferenceError: proj4 is not defined

  • 檢視了下原始碼 if (!proj4) { warn(`L.esri.TiledMapLayer is using a non-mercator spatial reference. Support may be available through Proj4Leaflet http://esri.github.io/esri-leaflet/examples/non-mercator-projection.html`);} 問題就出在這裡,esri-leaflet裡的一個外掛proj4leaflet依賴proj4,所以需要手動引入proj4這個包。
  • 這個GitHub上面的提問及回答 Github esri-leaflet Issues
  • 如果你是模組化開發,需要再npm i proj4 然後再引入進來好了 `import * as proj4 from `proj4`;

window[`proj4`] = proj4;`

  • 如果你是常規開發,直接新增一個script標籤引用CDN資源上託管的Proj4js就是了 <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4-src.js"></script>

參考

持續更新中 原文地址: https://juejin.im/post/5a6586…

相關文章