開源地相簿OpenLayers的簡單使用

beckyye發表於2023-12-14

引言

最近在學習視覺化的東西,這讓我想起了一些以前用過的圖表庫,其實我在日常做的大多是普通的需求,視覺化方面應用的並不多,只是偶爾會因為個別特殊的需求,去借助一些圖表庫來實現圖表的展示,這些普通的圖表庫,在使用上都大差不差,並沒有什麼太大的區別,但是某些特殊的圖表庫,比如地相簿,在使用上和一些普通的圖表,還是存在一些不同,現在想一想還是需要做一些記錄,因為我沒有在當時使用的時候及時記錄,導致我現在對OpenLayers的使用也有一點模糊了,只能藉助程式碼來回憶,所以我現在把它的簡單使用做一次整理。

其實一開始我選擇使用的是比較普遍的高德地圖,但是需求做到後面,我才意識到當時做的專案是要部署在內網的,當時就有點傻眼了,因為我對WebGIS也並不熟悉,所以就上網匆匆的搜尋有什麼容易上手使用的開源地相簿,然後鎖定了OpenLayers這個庫。

OpenLayers使用起來不像高德地圖那麼方便,因為部署的是內網環境,需要自己準備瓦片服務,還記得當時下載地圖瓦片也下載了很久,因為要準備不同比例的地圖瓦片,不過幸好當時只需要下載杭州一個城市的瓦片。

準備工作

雖然之前我是在React的專案中使用OpenLayers的,但OpenLayers的使用與專案的具體框架並沒有太大的關係,所以我們只需要使用script標籤引入OpenLayers,就可以在專案中使用這個開源地相簿了。

因此我們的準備工作,只需要一個引入OpenLayers的頁面,然後在頁面上準備一個div,來作為地圖的容器就可以了,另外在這個例子中我使用了systemjs來進行瀏覽器端的包管理,不用也是可以的;當然,地圖瓦片也是需要提前準備的。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>OpenLayers demo</title>
    <script type="systemjs-importmap">
        {
            "imports": {
                "ol": "https://cdn.jsdelivr.net/npm/ol@v8.2.0/dist/ol.js"
            }
        }
    </script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v8.2.0/ol.css">
    <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/use-default.js"></script>
</head>
<body>
<div id="map" style="width: 500px; height: 500px;"></div>
</body>
</html>

地圖瓦片

地圖瓦片是什麼呢?簡單來說,它是地圖檢視的組成單元,也就是說,地圖的檢視是由一張張的瓦片拼湊而成。但是地圖的使用一般而言不是靜態的,通常在使用中我們需要對地圖進行縮放互動,來進行更細緻的觀察。所以我們一般需要準備多個比例下的地圖瓦片,具體的可以根據需求來確定。

雖然當時我查到可以由後端部署服務來提供瓦片,但因為當時比較匆忙,我沒來得及仔細研究,又覺得說本來這塊工作量之前也並不瞭解,前端這邊如果能直接處理就處理了,所以就用地圖瓦片下載器自己去下載了瓦片,然後部署到一個靜態服務下面;現在對於demo的編寫倒也是一個好處,可以方便前端的獨立展示。

實現效果

因為是簡單的使用,所以這裡我們主要實現地圖的展示、放大縮小等簡單的功能,以及一些簡單的互動處理。

具體實現

總體來說,Openlayers的使用並不複雜,普通的使用透過查閱API文件完全可以應對。

  • 首先我們提前把下載好的地圖瓦片放到服務目錄下。

  • 然後是最基本的,使用System.import方法引入ol依賴

    const ol = await System.import('ol');
    
  • 然後我們關注三個主要的類,分別是Map、View和TileLayer,這是我們用於構築地圖的主要部分

    const OlMap = ol.Map,
        	View = ol.View,
        	TileLayer = ol.layer.Tile;
    
  • 現在我們就可以透過new Map來建立新的地圖物件

    const map = new OlMap({
      target: 'map',
      view: new View({
        center: [120.212, 30.208],
        projection: 'EPSG:4326',
        zoom: 9,
        maxZoom: 17,
            minZoom: 7
      }),
    });
    

    其中target用於指定盛裝地圖的容器;

    在建立地圖物件的時候,會使用到View這個類,代表地圖的二維檢視。我們可以在檢視中透過經緯度陣列指定檢視的中心,View預設使用EPSG3857座標系設定,我們也可以透過projection選項來修改座標系,之前我使用的時候比較匆忙,沒有注意到這一塊,還很曲折的透過fromEPSG4326方法來把4326的經緯度轉換為3857座標系;zoom選項用於定義檢視初始解析度的縮放級別,這裡我當時是用了9這個級別,我感覺比較合適,當然具體的設定要看專案需求;然後我們可以透過maxZoom和minZoom這兩個選項限制最大和最小的縮放級別。

    現在我們就可以看到,頁面上其實已經生成地圖容器了,已經能看到放大縮小的操作按鈕了,只是說還沒有貼上瓦片,所以這時候的地圖還比較抽象。

  • 接下來我們就需要用到之前準備的瓦片了,用於給map物件設定layers圖層
    layers選項接收的是一個陣列,也就是說可以給地圖配置多個圖層;這裡我們使用剛剛引入的TileLayer這個類來建立一個圖層;另外我們還需要使用一個XYZ的類,來指定瓦片服務的地址。

    // ...
    const XYZ = ol.source.XYZ;
    const map = new OlMap({
      target: 'map',
      view: new View({
        center: [120.212, 30.208],
        projection: 'EPSG:4326',
        zoom: 9,
        maxZoom: 17,
            minZoom: 7
      }),
      layers: [
        new TileLayer({
          source: new XYZ({
            url: './maps/{z}/{x}/{y}.png'
          })
        })
      ]
    });
    

至此我們就可以在頁面上看到地圖的展示了,開啟控制檯我們也可以看到對地圖瓦片的請求,請求的是maps/9目錄下的瓦片,我們也能注意到,有一些404的請求,這是因為view圖層中的部分地圖瓦片我們沒有準備,透過在頁面上檢查元素,也可以看到map容器中確實存在一部分沒有貼上瓦片,這通常來說沒什麼關係,可以不用管;可以看到當使用滑鼠滾動縮放地圖的時候,也會去請求相應縮放比例的地圖瓦片。

  • 新增簡單的互動事件
    最後來新增一些簡單的互動。
    之前我做的需求中需要根據介面返回的資料批次標註地圖上的點,但是因為現在沒有資料,這裡就實現一些簡單的互動吧。

    const olExtent = ol.extent;
    map.on('moveend', e => {
      console.log('zoom', map.frameState_.viewState.zoom);
      const extent = map.frameState_.extent;
      console.log('extent', extent);
      console.log('TopLeft', olExtent.getTopLeft(extent));
      console.log('BottomRight', olExtent.getBottomRight(extent));
    });
    

    我們可以透過zoom獲取檢視的縮放級別,透過extent獲取檢視的經緯度範圍,還可以進一步透過extent的getTopLeft和getBottomRight分別獲取左上角的經緯度和右下角的經緯度;這樣我們就可以在縮放檢視和移動圖層時根據檢視的經緯度範圍來載入相應的資料。

    const Feature = ol.Feature;
    const Point = ol.geom.Point;
    const Style = ol.style.Style,
      CircleStyle = ol.style.Circle,
      Fill = ol.style.Fill,
      Stroke = ol.style.Stroke;
    const VectorSource = ol.source.Vector,
      VectorLayer = ol.layer.Vector;
    let count = 0;
    map.on('click', e => {
      const features = [];
        console.log(e.coordinate); // 獲取座標
    
        const iconFeature = new Feature({
         geometry: new Point(e.coordinate),
         name: count ++,
         location: e.coordinate
      	});
      	const style = new Style({
          image: new CircleStyle({
            radius: 10,
            fill: new Fill({
              color: '#f49d41'
            }),
            stroke: new Stroke({
              color: '#836365',
              width: 1
            })
          })
      });
      iconFeature.setStyle(style);
      features.push(iconFeature);
      const vectorSource = new VectorSource({
        features
      });
      const vectorLayer = new VectorLayer({
        source: vectorSource,
        opacity: 0.8
      });
      map.addLayer(vectorLayer);
    });
    

    我們還可以在處理地圖的滑鼠點選事件時,獲取滑鼠點對應的經緯度,透過Feature類給地圖新增標註,再透過Style類給標註設定樣式;也可以在新增新標註前移除舊的標註。

    const layers = map.getLayers();
    layers.forEach(item => {
      if(item instanceof VectorLayer) map.removeLayer(item);
    });
    

到這裡我們就完成了OpenLayers的簡單使用,如果有感興趣的小夥伴,可以去OpenLayers的GitHub和官方文件再去進一步的瞭解。

以下是執行效果:

image

整體程式碼參考這個CodePen