微信小程式新增外部地圖服務資料

GIS兵器庫發表於2021-10-27

先上效果:

緣起

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

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

圖層查詢

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

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

但是既然資料是按圖層釋出的,一般資料量都比較大,把所有資料查詢出來,一次性新增過多的資料到地圖,地圖元件會受不了從而變的卡頓,另外微信小程式單次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: "http://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. 請求瓦片資料,通過微信小程式地圖元件中新增點線面的方法把切片資料新增到地圖

程式碼地址

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


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

歡迎關注《GIS兵器庫

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

相關文章