如何使用 Mapbox 将可随时更改的图像显示为地图?

How to display an image that can be changed at any time as a map using Mapbox?

在我的应用程序中,用户必须能够向地图添加平面图。用户使用简单的表单上传 PNG 图片,然后这张图片必须显示为地图背景。所以我们这里有:

Mapbox 有 sourceslayers,我需要利用它们将此图像添加为地图背景,而实际世界地图根本不能显示。

我见过很多这样的例子(这个用的是mapbox-gl-js):

...
"sources": {
    "overlay": {
        "type": "image",
        "url": "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif",
        "coordinates": [
            [-80.425, 46.437],
            [-71.516, 46.437],
            [-71.516, 37.936],
            [-80.425, 37.936]
        ]
    }
},
...

还有这个(这个使用 deck.gl 层):

import DeckGL from '@deck.gl/react';
import {BitmapLayer} from '@deck.gl/layers';

const App = ({data, viewport}) => {

  const layer = new BitmapLayer({
    id: 'bitmap-layer',
    bounds: [-122.5190, 37.7045, -122.355, 37.829],
    image: 'https://raw.githubusercontent.com/uber-common/deck.gl-data/master/website/sf-districts.png'
  });

  return (<DeckGL {...viewport} layers={[layer]} />);
}

但它们始终具有图像的预定义 坐标。因为用户可以随时更新我的​​图像,所以我需要以某种方式 计算这些坐标,同时考虑图像的纵横比。我数学不太好,你能帮帮我吗? deck.gl 能够指定层的坐标系甚至 4x4 投影矩阵,但我不太明白如何在我的案例中使用它。

好的,我解决了这个问题。解决方案的关键是停止尝试让计划填满整个地图,而是调整图像大小使其非常小,并将其放置在地图上的 [0, 0] 坐标处。这样我们就可以假设这里的世界是平的,根本不用担心它的曲率。

所以当地图加载时,我正在加载图像以获取其尺寸:

this.map.current.on('load', () => {
  const img = new Image()
  const self = this
  img.addEventListener('load', function () {
    // ...
  })
  img.src = planUrl
})

完成后,我将在图像的 load 处理程序中调整图像大小并为其创建 LngLatBounds。我只是在这里简单地除以宽度和高度以获得图像的 lnglat — 它们在 lnglat 上都小于 1,所以我不'认为地球的曲率会是这个层面的问题:

img.addEventListener('load', function () {
  const maxWidth = 1
  const maxHeight = 0.5

  const [width, height] = resizeImage(
    this.naturalWidth,
    this.naturalHeight,
    maxWidth,
    maxHeight
  )

  const sw = [-width / 2, -height / 2]
  const ne = [width / 2, height / 2]

  const bounds = new LngLatBounds(sw, ne)
  // ...
})

然后我在地图上添加一个带有平面图的源和一个显示平面图的图层:

self.map.current.addSource('plan', {
  type: 'image',
  url: planUrl,
  coordinates: [
    bounds.getNorthWest().toArray(),
    bounds.getNorthEast().toArray(),
    bounds.getSouthEast().toArray(),
    bounds.getSouthWest().toArray()
  ]
})

self.map.current.addLayer({
  id: 'image',
  source: 'plan',
  type: 'raster'
})

然后我将地图边界设置为图像边界大小的 2 倍,这样平面图周围就会有很好的填充。

const mapBounds = new LngLatBounds(sw, ne)
mapBounds.extend(new LngLatBounds([-width, -height], [width, height]))
self.map.current.setMaxBounds(mapBounds)

可能有比这个更好的解决方案,但看起来它对我来说效果很好。希望它能帮助其他人。