LinYeFangHuo/packages/mars3d/PbfLayer.js

286 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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
function createBaseCanvas () {
let 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
return baseCanv
}
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 = createBaseCanvas()
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
// setTimeout(() => {
// console.log( this.mapboxRenderer._style)
// if (options.extendImages) {
// let msii = this.mapboxRenderer._style.imageManager.images
// if (Object.keys(msii).length > 0) {
// let copydata = msii[Object.keys(msii)[0]]
// let extendImages = this.handleExtendImages(options.extendImages, copydata)
// Object.assign(this.mapboxRenderer._style.imageManager.images, extendImages)
// }
// }
// },350)
})
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
}
handleExtendImages (extImg, copydata) {
let extendImages = extImg
let parr = Object.keys(extImg)
if (parr.length > 0 ) {
if (!(extImg[parr[0]].data.data instanceof Uint8Array)) {
parr.forEach(item => {
let { width, height, data } = extendImages[item].data
let tempdata = {}
tempdata = Object.assign(
Object.create(Object.getPrototypeOf(copydata.data)),
copydata.data
);
tempdata.width = width
tempdata.height = height
tempdata.data = Uint8Array.from(Object.keys(data).map(ktm => data[ktm]))
extendImages[item].data = tempdata
})
}
}
return extendImages
}
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.PbfLayer = PbfLayer
})(window)
// export { PbfLayer }