import * as L from '@mapgis/leaflet'
// eslint-disable-next-line import/no-extraneous-dependencies
import * as T from '@turf/turf'
import { defaultValue, FetchMethod } from '@mapgis/webclient-common'
L.TileLayer.Clip = L.TileLayer.extend({
options: {
clippingArea: null
},
initialize(url, options) {
L.TileLayer.prototype.initialize.call(this, url, options)
// 缓存clippingArea像素边界
this._clippingAreaPx = null
// 缓存瓦片裁剪区
this._TileClippingGeometryCache = {}
// 设置请求方式
this.httpMethod = defaultValue(options.httpMethod, FetchMethod.get)
},
createTile(coords, done) {
const src = this.getTileUrl(coords)
return this._drawClippingTile(src, coords, done)
},
/**
* @description: 设置裁剪区域
* @param {*} data
* @return {*}
*/
setClippingArea(data) {
this.options.clippingArea = data
this._clearClippingCache()
this.redraw()
return this
},
/**
* @description: 获取裁剪区域
* @return {*}
*/
getClippingArea() {
return this.options.clippingArea
},
/**
* @description: 根据瓦片行列号获取空间裁剪区
* @param {*} x
* @param {*} y
* @param {*} z
* @return {*}
*/
_getTileGeometryAndState(x, y, z) {
// 不设置裁剪区默认不裁剪
if (!this.options.clippingArea) {
return {
in: true,
geometry: null
}
}
// 缓存瓦片id
const cacheId = `${x},${y},${z}`
// 返回缓存的几何
if (this._TileClippingGeometryCache[cacheId]) {
return {
intersect: true,
geometry: this._TileClippingGeometryCache[cacheId]
}
}
const clippingAreaPx = this._getClippingAreaPx()
const tileSize = this.options.tileSize
const zLevel = Math.pow(2, z - this.options.zoomOffset)
// 计算0级范围
const x1 = (x * tileSize) / zLevel
const y1 = (y * tileSize) / zLevel
const x2 = ((x + 1) * tileSize) / zLevel
const y2 = ((y + 1) * tileSize) / zLevel
const tileBbox = T.polygon([
[
[x1, y1],
[x2, y1],
[x2, y2],
[x1, y2],
[x1, y1]
]
])
// 判断裁剪区>瓦片范围
if (T.booleanContains(clippingAreaPx, tileBbox)) {
return {
in: true,
geometry: null
}
}
// 判断裁剪区<瓦片范围
if (T.booleanContains(tileBbox, clippingAreaPx)) {
this._TileClippingGeometryCache[cacheId] = clippingAreaPx
return {
intersect: true,
geometry: clippingAreaPx
}
}
// 判断是否相交
if (!T.booleanOverlap(clippingAreaPx, tileBbox)) {
return {
out: true,
geometry: null
}
}
const intersectResult = T.intersect(clippingAreaPx, tileBbox)
if (!intersectResult) {
return {
out: true,
geometry: null
}
}
this._TileClippingGeometryCache[cacheId] = intersectResult
return {
intersect: true,
geometry: intersectResult
}
},
/**
* 获取合并后的点集
* @param {Number[][][]} polygon 面
* @return {Number[][][]} 计算像素坐标系点
*/
_toMercGeometry(polygons) {
// 缓存0级地图下几何的相对位置
const rings = []
for (let j = 0; j < polygons.length; j++) {
const ring = []
for (let k = 0; k < polygons[j].length; k++) {
const p = this._map.project(
L.latLng(polygons[j][k][1], polygons[j][k][0]),
0
)
ring.push([p.x, p.y])
}
rings.push(ring)
}
return rings
},
/**
* 计算裁剪区像素坐标几何以及更新其四至
* @return {Object|null} 合并后的点集
*/
_getClippingAreaPx() {
if (this._clippingAreaPx) {
return this._clippingAreaPx
}
if (this.options.clippingArea) {
this._clippingAreaPx = T.polygon(
this._toMercGeometry(this.options.clippingArea)
)
this._clippingAreaPxBounds = T.bbox(this._clippingAreaPx)
} else {
this._clippingAreaPx = null
this._clippingAreaPxBounds = null
}
return this._clippingAreaPx
},
/**
* @description: 清空缓存
* @return {*}
*/
_clearClippingCache() {
this._clippingAreaPx = null
this._clippingAreaPxBounds = null
this._TileClippingGeometryCache = {}
},
/**
* @description: 重绘图片
* @param {*} url
* @param {*} coords
* @param {*} done
* @return {*}
*/
_drawClippingTile(url, coords, done) {
const { x, y, z } = coords
const state = this._getTileGeometryAndState(x, y, z)
// 创建tile
const tile = document.createElement('img')
// 处理状态
if (state.in) {
return this._getTileImage(tile, url, done)
}
if (state.out) {
const canvas = document.createElement('canvas')
canvas.width = this.options.width
canvas.height = this.options.height
return this._getTileImage(tile, null, done)
}
if (state.intersect && state.geometry) {
this._getTileImage(tile, url, done)
// 创建绘制的canvas
const canvas = document.createElement('canvas')
canvas.width = this.options.tileSize
canvas.height = this.options.tileSize
const ctx = canvas.getContext('2d')
const tileSize = this.options.tileSize
const tileX = tileSize * x
const tileY = tileSize * y
const zLevel = Math.pow(2, z - this.options.zoomOffset)
const geometryCoords = T.coordAll(state.geometry)
// 图像加载完毕绘制canvas
tile.onload = function () {
canvas.complete = true
// 定时器
setTimeout(() => {
const pattern = ctx.createPattern(tile, 'repeat')
let drawX
let drawY
// 开始绘制
ctx.beginPath()
// 移动到开始点
drawX = geometryCoords[0][0] * zLevel - tileX
drawY = geometryCoords[0][1] * zLevel - tileY
ctx.moveTo(drawX, drawY)
for (let j = 1; j < geometryCoords.length; j++) {
drawX = geometryCoords[j][0] * zLevel - tileX
drawY = geometryCoords[j][1] * zLevel - tileY
// 开始绘制
ctx.lineTo(drawX, drawY)
}
// 设置裁剪
ctx.clip()
ctx.beginPath()
ctx.rect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = pattern
ctx.fill()
done()
}, 0)
}
if (this.options.crossOrigin) {
tile.crossOrigin = ''
}
return canvas
}
},
_getTileImage(tile, url, done) {
if (!url) return tile
L.DomEvent.on(tile, 'load', L.Util.bind(this._tileOnLoad, this, done, tile))
L.DomEvent.on(
tile,
'error',
L.Util.bind(this._tileOnError, this, done, tile)
)
if (this.options.crossOrigin || this.options.crossOrigin === '') {
tile.crossOrigin =
this.options.crossOrigin === true ? '' : this.options.crossOrigin
}
// for this new option we follow the documented behavior
// more closely by only setting the property when string
if (typeof this.options.referrerPolicy === 'string') {
tile.referrerPolicy = this.options.referrerPolicy
}
// The alt attribute is set to the empty string,
// allowing screen readers to ignore the decorative image tiles.
// https://www.w3.org/WAI/tutorials/images/decorative/
// https://www.w3.org/TR/html-aria/#el-img-empty-alt
tile.alt = ''
// 验证是否为base64字符串
let imageUrl = url
if (!/data:image\/jpg;base64/.test(url)) {
imageUrl = this._getInterceptorsUrl(url)
const config = this._map.options.config
tile.onload = function () {
L.InterceptorsUtils.serviceConfigResponseInterceptors(
config,
imageUrl,
tile
)
}
}
if (this.httpMethod === FetchMethod.post) {
this._postImage(tile, url)
} else {
tile.src = imageUrl
}
return tile
},
// 由子类实现
_postImage(tile, options) {},
_getInterceptorsUrl(url) {
// 处理拦截器代码
const config = this._map.options.config
const tokenStr = `&${L.InterceptorsUtils.serviceConfigToken(config)}`
let imageUrl = L.InterceptorsUtils.serviceConfigRequestInterceptors(
config,
url
)
if (tokenStr !== '&') {
imageUrl += tokenStr
}
return imageUrl
}
})
L.TileLayer.clip = function (url, options) {
return new L.TileLayer.Clip(url, options)
}