245 lines
8.3 KiB
JavaScript
245 lines
8.3 KiB
JavaScript
|
|
// import * as mars3d from "mars3d"
|
|||
|
|
// const Cesium = mars3d.Cesium
|
|||
|
|
|
|||
|
|
// 按mars3d规范,封装的pbf图层
|
|||
|
|
;(function (window) {
|
|||
|
|
const BasicRenderer = window.mapboxRenderer.BasicRenderer
|
|||
|
|
|
|||
|
|
// 创建一个全局变量作为pbfBasicRenderer渲染模板,避免出现16个canvas上下文的浏览器限制,以便Cesium ImageLayer.destory()正常工作。
|
|||
|
|
// https://github.com/mapbox/mapbox-gl-js/issues/7332
|
|||
|
|
const OFFSCREEN_CANV_SIZE = 1024
|
|||
|
|
const baseCanv = document.createElement("canvas")
|
|||
|
|
baseCanv.style.imageRendering = "pixelated"
|
|||
|
|
baseCanv.addEventListener("webglcontextlost", () => console.log("webglcontextlost"), false)
|
|||
|
|
baseCanv.width = OFFSCREEN_CANV_SIZE
|
|||
|
|
baseCanv.height = OFFSCREEN_CANV_SIZE
|
|||
|
|
|
|||
|
|
class MVTImageryProvider {
|
|||
|
|
/**
|
|||
|
|
*
|
|||
|
|
* @param {Object} options
|
|||
|
|
* @param {Object} options.style - mapbox style object
|
|||
|
|
* @param {Function} [options.sourceFilter] - sourceFilter is used to filter which source participate in pickFeature process.
|
|||
|
|
* @param {Number} [options.maximumLevel] - if cesium zoom level exceeds maximumLevel, layer will be invisible.
|
|||
|
|
* @param {Number} [options.minimumLevel] - if cesium zoom level belows minimumLevel, layer will be invisible.
|
|||
|
|
* @param {Number} [options.tileSize=512] - can be 256 or 512.
|
|||
|
|
* @param {Boolean} [options.hasAlphaChannel] -
|
|||
|
|
* @param {String} [options.credit] -
|
|||
|
|
*
|
|||
|
|
*/
|
|||
|
|
constructor(options) {
|
|||
|
|
this.options = options
|
|||
|
|
|
|||
|
|
let pbfStyle = options.style
|
|||
|
|
this.mapboxRenderer = new BasicRenderer({
|
|||
|
|
style: pbfStyle
|
|||
|
|
})
|
|||
|
|
this.mapboxRenderer._transformRequest = (url, resourceType) => {
|
|||
|
|
return this.transformRequest(url, resourceType)
|
|||
|
|
}
|
|||
|
|
this.mapboxRenderer._canvas = baseCanv
|
|||
|
|
this.mapboxRenderer._canvas.addEventListener("webglcontextrestored", () => this.mapboxRenderer._createGlContext(), false)
|
|||
|
|
this.mapboxRenderer._createGlContext()
|
|||
|
|
|
|||
|
|
if (options.showCanvas) {
|
|||
|
|
this.mapboxRenderer.showCanvasForDebug()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.ready = false
|
|||
|
|
this.readyPromise = this.mapboxRenderer._style.loadedPromise.then(() => {
|
|||
|
|
this.ready = true
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.tilingScheme = options.tilingScheme ?? new Cesium.WebMercatorTilingScheme()
|
|||
|
|
this.rectangle = this.tilingScheme.rectangle
|
|||
|
|
this.tileSize = this.tileWidth = this.tileHeight = options.tileSize || 512
|
|||
|
|
this.maximumLevel = options.maximumLevel || Number.MAX_SAFE_INTEGER
|
|||
|
|
this.minimumLevel = options.minimumLevel || 0
|
|||
|
|
this.tileDiscardPolicy = undefined
|
|||
|
|
this.errorEvent = new Cesium.Event()
|
|||
|
|
this.credit = new Cesium.Credit(options.credit || "", false)
|
|||
|
|
this.proxy = new Cesium.DefaultProxy("")
|
|||
|
|
this.hasAlphaChannel = options.hasAlphaChannel ?? true
|
|||
|
|
this.sourceFilter = options.sourceFilter
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
transformRequest = (url) => {
|
|||
|
|
if (this.options.transformUrl) {
|
|||
|
|
url = this.options.transformUrl(url)
|
|||
|
|
}
|
|||
|
|
return { url: url, headers: this.options.headers || {}, credentials: "" }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getTileCredits(x, y, level) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
createTile() {
|
|||
|
|
const canv = document.createElement("canvas")
|
|||
|
|
canv.width = this.tileSize
|
|||
|
|
canv.height = this.tileSize
|
|||
|
|
canv.style.imageRendering = "pixelated"
|
|||
|
|
const ctx = canv.getContext("2d")
|
|||
|
|
if (ctx) {
|
|||
|
|
ctx.globalCompositeOperation = "copy"
|
|||
|
|
}
|
|||
|
|
return canv
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_getTilesSpec(coord, source) {
|
|||
|
|
const { x, y, zoom } = coord
|
|||
|
|
const TILE_SIZE = this.tileSize
|
|||
|
|
// 3x3 grid of source tiles, where the region of interest is that corresponding to the central source tile
|
|||
|
|
const ret = []
|
|||
|
|
const maxTile = (1 << zoom) - 1
|
|||
|
|
for (let xx = -1; xx <= 1; xx++) {
|
|||
|
|
let newx = x + xx
|
|||
|
|
if (newx < 0) {
|
|||
|
|
newx = maxTile
|
|||
|
|
}
|
|||
|
|
if (newx > maxTile) {
|
|||
|
|
newx = 0
|
|||
|
|
}
|
|||
|
|
for (let yy = -1; yy <= 1; yy++) {
|
|||
|
|
const newy = y + yy
|
|||
|
|
if (newy < 0) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if (newy > maxTile) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
ret.push({
|
|||
|
|
source: source,
|
|||
|
|
z: zoom,
|
|||
|
|
x: newx,
|
|||
|
|
y: newy,
|
|||
|
|
left: 0 + xx * TILE_SIZE,
|
|||
|
|
top: 0 + yy * TILE_SIZE,
|
|||
|
|
size: TILE_SIZE
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ret
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requestImage(x, y, zoom, releaseTile = true) {
|
|||
|
|
if (zoom > this.maximumLevel || zoom < this.minimumLevel) {
|
|||
|
|
return Promise.reject(undefined)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.mapboxRenderer.filterForZoom(zoom)
|
|||
|
|
const tilesSpec = this.mapboxRenderer.getVisibleSources().reduce((a, s) => a.concat(this._getTilesSpec({ x, y, zoom }, s)), [])
|
|||
|
|
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const canv = this.createTile()
|
|||
|
|
const ctx = canv.getContext("2d")
|
|||
|
|
const renderRef = this.mapboxRenderer.renderTiles(
|
|||
|
|
ctx,
|
|||
|
|
{
|
|||
|
|
srcLeft: 0,
|
|||
|
|
srcTop: 0,
|
|||
|
|
width: this.tileSize,
|
|||
|
|
height: this.tileSize,
|
|||
|
|
destLeft: 0,
|
|||
|
|
destTop: 0
|
|||
|
|
},
|
|||
|
|
tilesSpec,
|
|||
|
|
(err) => {
|
|||
|
|
/**
|
|||
|
|
* In case of err ends with 'tiles not available', the canvas will still be painted.
|
|||
|
|
* relate url: https://github.com/landtechnologies/Mapbox-vector-tiles-basic-js-renderer/blob/master/src/basic/renderer.js#L341-L405
|
|||
|
|
*/
|
|||
|
|
if (typeof err === "string" && !err.endsWith("tiles not available")) {
|
|||
|
|
reject(undefined)
|
|||
|
|
} else if (releaseTile) {
|
|||
|
|
renderRef.consumer.ctx = undefined
|
|||
|
|
resolve(canv)
|
|||
|
|
// releaseTile默认为true,对应Cesium请求图像的情形
|
|||
|
|
this.mapboxRenderer.releaseRender(renderRef)
|
|||
|
|
this.mapboxRenderer._style.sourceCaches?.origin?._tileCache.reset()
|
|||
|
|
} else {
|
|||
|
|
// releaseTile为false时在由pickFeature手动调用,在渲染完成之后在pickFeature里边手动释放tile
|
|||
|
|
resolve(renderRef)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pickFeatures(x, y, zoom, longitude, latitude) {
|
|||
|
|
return this.requestImage(x, y, zoom, false).then((renderRef) => {
|
|||
|
|
let targetSources = this.mapboxRenderer.getVisibleSources(zoom)
|
|||
|
|
targetSources = this.sourceFilter ? this.sourceFilter(targetSources) : targetSources
|
|||
|
|
|
|||
|
|
const queryResult = []
|
|||
|
|
|
|||
|
|
longitude = Cesium.Math.toDegrees(longitude)
|
|||
|
|
latitude = Cesium.Math.toDegrees(latitude)
|
|||
|
|
|
|||
|
|
targetSources.forEach((s) => {
|
|||
|
|
const data = this.mapboxRenderer.queryRenderedFeatures({
|
|||
|
|
source: s,
|
|||
|
|
renderedZoom: zoom,
|
|||
|
|
lng: longitude,
|
|||
|
|
lat: latitude,
|
|||
|
|
tileZ: zoom
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
for (const key in data) {
|
|||
|
|
const item = data[key]
|
|||
|
|
for (let index = 0; index < item.length; index++) {
|
|||
|
|
const element = item[index]
|
|||
|
|
element.layer = key
|
|||
|
|
queryResult.push({ properties: element })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// release tile
|
|||
|
|
renderRef.consumer.ctx = undefined
|
|||
|
|
this.mapboxRenderer.releaseRender(renderRef)
|
|||
|
|
this.mapboxRenderer._style.sourceCaches?.origin?._tileCache.reset()
|
|||
|
|
return queryResult
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
destroy() {
|
|||
|
|
this.mapboxRenderer._cancelAllPendingRenders()
|
|||
|
|
Object.values(this.mapboxRenderer._style.sourceCaches).forEach((cache) => cache._tileCache.reset())
|
|||
|
|
this.mapboxRenderer._gl.getExtension("WEBGL_lose_context").loseContext()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class PbfLayer extends mars3d.layer.BaseTileLayer {
|
|||
|
|
// 构建ImageryProvider
|
|||
|
|
async _createImageryProvider(options) {
|
|||
|
|
return createImageryProvider(options)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function createImageryProvider(options) {
|
|||
|
|
if (options.url) {
|
|||
|
|
let data = await Cesium.Resource.fetchJson(options)
|
|||
|
|
const provider = new MVTImageryProvider({ ...options, style: data })
|
|||
|
|
await provider.readyPromise
|
|||
|
|
return provider
|
|||
|
|
} else {
|
|||
|
|
const provider = new MVTImageryProvider(options)
|
|||
|
|
await provider.readyPromise
|
|||
|
|
return provider
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PbfLayer.createImageryProvider = createImageryProvider
|
|||
|
|
|
|||
|
|
// 注册下
|
|||
|
|
const layerType = "pbf" // 图层类型
|
|||
|
|
mars3d.LayerUtil.register(layerType, PbfLayer)
|
|||
|
|
mars3d.LayerUtil.registerImageryProvider(layerType, createImageryProvider)
|
|||
|
|
|
|||
|
|
// 对外接口
|
|||
|
|
window.mars3d.layer.PbfLayer = PbfLayer
|
|||
|
|
})(window)
|
|||
|
|
|
|||
|
|
// export { PbfLayer }
|