mapboxgl載入tiff

2022-09-29 12:01:06

緣起

近期在專案中遇到這麼一個需求,需要在地圖上展示一組格網資料,格網大小為2m*2m,地圖api用的mapboxgl。起初拿到這個需要感覺很easy,在地圖上新增一個fill圖層就好啦。把格網面資料新增到地圖上之後,在大比例尺下顯示正常,但是當地圖層級小於15級時,渲染出的結果會消失。

簡單理一下原因,應該是在地圖縮小後,每個網格所佔的畫素太小,所以就消失了。

mapboxgl在處理symbol圖層的時候,會遇到點位自動避讓問題,導致部分點位不顯示。解決方法是把layout中的icon-allow-overlap設定為true,這樣就相當於關閉了自動避讓功能,所有點圖示保持可見狀態。但是針對fill圖層卻沒有這麼一個屬性。

但是這種情況又需要檢視資料,要如何實現呢?

首先分析下資料,我的原始資料是通過模型匯出的tiff格式的柵格資料,然後在後臺根據tiff格式資料中每個畫素所在行列號以及其灰度值生成帶屬性的格網資料,其中畫素的灰度值就是在渲染時需要分類展示的值。既然原始tiff資料的灰度值就是所用的屬性值,那是不是直接新增到地圖就好了。接下來的解決方案就是看是否能直接用mapboxgl直接載入tiff資料,並渲染出自己想要的效果。

mapboxgl載入tiff

檢視mapboxgl檔案,可以看到mapboxgl支援image圖層,只需傳入url和coordinates

// 新增至地圖
map.addSource('some id', {
  type: 'image',
  url: 'https://www.mapbox.com/images/foo.png',
  coordinates: [
    [-76.54, 39.18],
    [-76.52, 39.18],
    [-76.52, 39.17],
    [-76.54, 39.17]
]});

可是,當我把地址換成tiff資料時卻報錯了。下面為報錯內容:

Could not load image because of The source image could not be decoded.. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported

可以簡單理解為不支援tiff格式。

tiff檔案解析

既然mapboxglimage圖層不支援tiff格式,那是不是可以把tiff資料匯出成png呢,於是使用arcmap開啟了tiff資料,匯出資料格式也支援png,但是在儲存時又報錯了。

經過分析,發現是tiff資料波段數量的原因,我的這份資料波段數為1,從網上下載了一份測試資料,波段數為3,可以成功匯出。

在查詢相關解決方案的時候,看到這麼個工具,geotiff.js,可以通過js解析tiff資料並渲染,leaflet有個擴充套件就是用的這個工具,https://github.com/stuartmatthews/leaflet-geotiff。檢視geotiff.js相關檔案,發現其實用起來還是挺方便的,通過簡單的程式碼實現的我的需求。

先使用geotiff.js解析tiff資料,再配合使用canvas繪製圖片匯出base64格式資料,然後就可以使用新增到mapboxgl圖層了。

核心程式碼如下:

async function getData() {
  GeoTIFF.fromUrl(url).then(tiff => {
    console.log(tiff)
    getImage(tiff)
  });
}
async function getImage(tiff) {
  const image = await tiff.getImage();
  let bbox = await image.getBoundingBox();
  let data = await image.readRasters({
    samples: rgbBands // 波段數量,一個波段:[0],三個波段:[2,1,0]
  });
  let base64Image = getBase64Image(data)
  addToMapboxgl(base64Image)
}
function getBase64Image(data) {
  let thumbnailPixelHeight = data.height
  let thumbnailPixelWidth = data.width
  let canvas = document.createElement('canvas')
  canvas.width = thumbnailPixelWidth
  canvas.height = thumbnailPixelHeight
  let ctx = canvas.getContext("2d")
  let totalPixelCount = 0
  for (let y = 0; y < thumbnailPixelHeight; y++) {
    for (let x = 0; x < thumbnailPixelWidth; x++) {
      let colour = 'rgb(0, 0, 0, 0)' // let the default be no data (transparent)
      // 根據灰度值所在範圍渲染顏色
      if (data[0][totalPixelCount] > 0) {
        if (data[0][totalPixelCount] > 50 && data[0][totalPixelCount] <= 55) {
          colour = `rgb(15, 255, 0, 1)`
        } else if (data[0][totalPixelCount] > 55 && data[0][totalPixelCount] <= 60) {
          colour = `rgb(155, 255, 0, 1)`
        } else if (data[0][totalPixelCount] > 60 && data[0][totalPixelCount] <= 65) {
          colour = `rgb(255, 255, 0, 1)`
        } else {
          colour = `rgb(255, 255, 0, 1)`
        }
      }
      ctx.fillStyle = colour
      ctx.fillRect(x, y, 1, 1)
      totalPixelCount++
    }
  }
  let canvasImage = canvas.toDataURL("image/png")
  return canvasImage
}
// 將圖片新增到地圖
function addToMapboxgl(image) {
  map.addSource('tiff-source', {
    "type": "image",
    "url": image,
    "coordinates": [
      [114.425597191307, 38.1091563484708],
      [114.538187627939, 38.1091563484708],
      [114.538187627939, 37.9627378349512],
      [114.425597191307, 37.9627378349512]
    ]
  });
  map.addLayer({
    id: 'tiff-layer',
    'type': 'raster',
    'source': 'tiff-source',
    'paint': {
      'raster-fade-duration': 0
    }
  });
}

本以為到這裡問題已經解決,但是在檢視地圖時,發現圖片圖層資料疊加到底圖有不小的偏移。

經過一番對比分析,發現原來是tiff資料的座標系與地圖座標系不一致的導致的。我拿到的tiff資料座標系為西安80的投影座標系,在展示時設定的為wgs84地理座標系,所以會有偏差。既然是座標系問題,那就通過工具對tiff檔案做下投影轉換。這裡用的是arcmap,開啟ArcToolbox–>Data Management Tools–>Projections and Transformations–>Raster–>Project Raster

轉換之後會發現,資料的行列值也會發生變化,也就是tiff圖片的大小和形狀都有所變化。

轉換前:

轉換後:

使用轉換後的資料再次解析,然後疊加到地圖,位置完全匹配。

最終展示方案

通過嘗試發現,單獨的圖片展示時,由於圖片解析度固定,當地圖等級放大到一定程度圖片會被放大很多導致圖片模糊不清,展示效果不理想;單獨的格網面展示時,當地圖等級縮小到一定程度,面圖層則會消失,也就是文章開頭提到的問題。

綜上,根據自己的格網資料大小,判斷在哪個等級格網面資料會消失,小於這個等級使用圖片展示,大於這個等級用格網面展示,就可以完美的展示出想要的效果。

處理前效果:

處理後效果:

以上為有tiff柵格資料情況的解決方案,針對於只有格網面資料,而沒有tiff柵格資料的情況要怎麼解決呢?

如果在這組格網資料中,每個網格的屬性中有他所在原始tiff資料的畫素位置,以及原始tiff資料畫素大小,就可以寫一個類似上文中的getBase64Image方法,遍歷每個網格,在網格對應的畫素位置上繪製顏色,然後再通過canvas匯出圖片新增到地圖。

總結

  1. mapboxglimage圖層無法直接新增tiff柵格資料
  2. mapboxgl新增fill圖層時,地圖層級縮小到一定程度,面資料所佔畫素值過小無法顯示
  3. tiff資料可以使用geotiff.js+canvas解析,得到base64的圖片,新增到mapboxglimage圖層
  4. 在解析tiff資料時,需注意它的座標系、波段個數等資訊
  5. 在做展示時可以image圖層和fill圖層結合展示,效果較好

參考資料:

  1. https://geotiffjs.github.io/geotiff.js/
  2. https://github.com/stuartmatthews/leaflet-geotiff
  3. https://www.cnblogs.com/arxive/p/6746570.html

原文地址:http://gisarmory.xyz/blog/index.html?blog=mapboxgl-geotiff

歡迎關注《GIS兵器庫

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