微信小程式添加外部地圖服務數據

先上效果:

緣起

使用微信小程式做地圖相關功能的時候,有個需求是需要接入自己發布的地圖服務。查看微信小程式地圖組件文檔,發現它對地圖相關的支援很少,只有一些基礎功能,比如添加點、線、面、氣泡和一些常規的地圖事件監聽,並沒有添加地圖服務相關的支援。

不過有了需求,也要想辦法解決呀。

圖層查詢

既然小程式不能直接添加地圖服務,那就把圖層數據查出來,然後通過添加點線面方式添加到地圖,具體要怎麼實現呢?

首先想到的是通過圖層查詢介面把所有數據查出來。

但是既然數據是按圖層發布的,一般數據量都比較大,把所有數據查詢出來,一次性添加過多的數據到地圖,地圖組件會受不了從而變的卡頓,另外微信小程式單次setData()的數據不能超過1024kB,因此這種方案就不可取了。

矢量瓦片

既然一次性請求數據量太大,是不是可以分批次請求呢?於是就想到了矢量瓦片。
矢量瓦片對於做GIS的人來說,大家都很熟悉了,這也是目前各種GIS產品對大數據量地圖展示所採用的主要方式。
但是,我們如何讓不支援添加外部圖層的小程式地圖組件支援矢量瓦片呢?

查看地圖組件相關文檔,會看到其中有個regionchange事件,該事件是在地圖視野改變,也就是拖動、縮放地圖時觸發,它會返回當前中心點、縮放級別、地圖範圍等資訊。

獲取瓦片

接下來就是如何根據這些參數獲取到矢量瓦片了。

假設,地圖切圖的原點是(originX,originY),地圖的瓦片大小是tileSize,地圖螢幕上1像素代表的實際距離是resolution。計算坐標點(x,y)所在的瓦片的行列號的公式是:

col = floor((x0 - x)/( tileSize*resolution))
row = floor((y0 - y)/( tileSize*resolution))

這個公式應該不難理解,簡單點說就是,先算出一個瓦片所包含的實際長度LtileSize,然後再算出此時螢幕上的地理坐標點離瓦片切圖的起始點間的實際距離LrealSize,然後用實際距離除以一個瓦片的實際長度,即可得此時的瓦片行列號:LrealSize/LtileSize

具體程式碼如下:

getTileXY: function (lon, lat, level) {
  let originX = -180; //坐標系原點的x的值,
  let originY = 90; //坐標系原點的y的值
  //根據你自己對應的切片方案改,這個就是其解析度resolution
  let resolution = [1.40625, 0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625,
    0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 0.0006866455078125, 0.0003433227539062,
    0.0001716613769531, 0.0000858306884766, 0.0000429153442383, 0.0000214576721191, 0.0000107288360596,
    0.0000053644180298, 0.0000026822090149, 0.0000013411045074, 0.0000006705522537, 0.0000003352761269
  ]

  let tileSize = 256 //這個值表示的是每張切片的大小,一般都是256
  let coef = resolution[level] * tileSize;
  let x = Math.floor((lon - originX) / coef); // 向下取整,丟棄小數部分
  let y = Math.floor((originY - lat) / coef); // 向下取整,丟棄小數部分
  let tmsY = Math.pow(2, (level - 1)) - y - 1;
  return {
    x: x,
    y: y,
    z: level - 1,
    tmsY: tmsY
  }
},

這裡可以看到我返回的數據中有一個y值,還有一個tmsY,這是因為WMTSTMS兩種方式調用切片時,傳入的y值是不同的,不過兩者之間是有可以轉換的,也就是tmsY = Math.pow(2, (level - 1)) - y - 1WMTS用的是這裡返回的y,TMS用的是這裡返回 的tmsY

參考鏈接:

WebGIS前端地圖顯示之根據地理範圍換算出瓦片行列號的原理(核心)

Slippy_map_tilenames

TMS和WMTS大概對比

接下來我們只需根據當前地圖可視範圍的最大、最小坐標以及地圖層級,即可獲取包含當前地圖可視範圍的瓦片的編號。

由於微信小程式地圖組件使用的是國測局加密坐標,而我發布的地圖服務數據為wgs84坐標,因此這裡在獲取切片編號時需要用坐標轉換方法將國測局坐標轉成wgs84坐標,坐標糾偏方法可參考leaflet中如何優雅的解決百度、高德地圖的偏移問題

getXYZList: function (region, level) {
  // 坐標轉換
  var newsouthwest = appcoord.gcj02_To_gps84(region.southwest.longitude, region.southwest.latitude); 
  var northeastwest = appcoord.gcj02_To_gps84(region.northeast.longitude, region.northeast.latitude);
  // 獲取瓦片編號
  var xyzInfo1 = this.getTileXY(newsouthwest.lng, northeastwest.lat, level)
  var xyzInfo2 = this.getTileXY(northeastwest.lng, newsouthwest.lat, level)
  var z = level - 1
  for (var x = xyzInfo1.x; x <= xyzInfo2.x; x++) {
    for (var y = xyzInfo1.y; y <= xyzInfo2.y; y++) {
      this.getGeoJson(x, y, z)
    }
  }
},

然後通過wx.request傳入請求地址以及x、y、z參數,即可獲取到對應矢量切片的geojson格式數據

getGeoJson: function (x, y, z) {
  const v = this
  wx.request({
    url: "//127.0.0.1:7000/geoserver/gwc/service/wmts/rest/test:test/EPSG:4326/EPSG:4326:" +
      z + "/" + y + "/" + x + "?format=application/json;type=geojson",
    method: 'get',
    success(res) {
      var tileId = 'tile-' + x + '-' + y + '-' + z
      tileData[tileId] = {
        tileId: tileId,
        features: []
      }
      if(res.statusCode === 200){
        tileData[tileId].features = res.data.features
      }
      v.addFeatures(tileId)
    }
  })
},

注意,這裡我是用geoserver發布的矢量瓦片,在調用過程中發現個問題,其中一個點圖層瓦片返回的數據中,各個瓦片總有很多重複數據,經檢查測試發現,這是由於發布該圖層(點圖層)時使用的樣式為一張大小為40x88的圖片點樣,這就導致切圖時整體向外緩衝了不少的像素值,所以,如果geoserver發布的圖層是用於矢量切片調用,最好將點圖層樣式設置為一個像素大小的像素點,這樣可以有效減少瓦片數據冗餘

添加數據

最後再通過微信小程式地圖組件中添加點線面的方法把獲取切片數據添加到地圖即可

addFeatures: function (tileId) {
  var polylines = this.data.polylines
  var markers = this.data.markers
  tileData[tileId].features.forEach(feature => {
    if (feature.geometry.type === 'LineString') {
      polylines.push(this.getPolyline(feature.geometry.coordinates, tileId))
    } else if (feature.geometry.type === 'Point') {
      markers.push(this.getMarker(feature.geometry.coordinates, tileId))
    }
  });
  this.setData({
    polylines: polylines,
    markers: markers
  })
},

存在問題

至此,微信小程式添加矢量瓦片數據已經完成,基本能滿足瀏覽外部矢量圖層的需求,但是,這裡還是有一些不足的地方

  1. 需要發布geojson格式矢量瓦片圖層
  2. 地圖拖動時圖層會閃一下,這是小程式重新往地圖上繪製點線面圖層引起的
  3. 在小比例尺瓦片返回數據量較大時可能會有卡頓現象(可以通過限定最小比例尺優化)
  4. 圖層配圖效果受小程式地圖點線面樣式限制

雖然該解決方案存在一些問題,但是鑒於微信小程式地圖組件的限制,並且確時又有添加圖層的需求,此方案還是可取的。

總結

  1. 微信小程式地圖組件不支援添加外部圖層服務
  2. 通過發布geojson格式矢量瓦片服務,然後按當前可視範圍獲取geojson格式瓦片數據
  3. 通過小程式地圖組件的regionchange事件監聽地圖拖動、縮放,可以獲取到當前中心點、縮放級別、地圖範圍
  4. 根據縮放級別、地圖範圍可以獲取到當前可視範圍的瓦片編號
  5. 請求瓦片數據,通過微信小程式地圖組件中添加點線面的方法把切片數據添加到地圖

程式碼地址

程式碼地址://gisarmory.xyz/blog/index.html?source=WechatVectorTile


原文地址://gisarmory.xyz/blog/index.html?blog=WechatVectorTile

歡迎關注《GIS兵器庫

本文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名《GIS兵器庫》(包含鏈接:  //gisarmory.xyz/blog/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。