Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TileLayer support only load tiles in mask #2333

Merged
merged 14 commits into from
Jun 21, 2024
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@maptalks/feature-filter": "^1.3.0",
"@maptalks/function-type": "^1.3.1",
"frustum-intersects": "^0.1.0",
"lineclip": "^1.1.5",
"rbush": "^2.0.2",
"simplify-js": "^1.2.1"
},
Expand All @@ -63,7 +64,6 @@
"@rollup/plugin-typescript": "^11.1.6",
"@types/node": "^20.11.30",
"@types/offscreencanvas": "^2019.7.3",
"rollup-plugin-dts": "^6.1.0",
"eslint": "^8.57.0",
"eslint-plugin-mocha": "^10.4.1",
"expect-maptalks": "^0.4.1",
Expand All @@ -80,6 +80,7 @@
"karma-sinon": "^1.0.5",
"mocha": "^10.3.0",
"rollup": "^4.13.0",
"rollup-plugin-dts": "^6.1.0",
"sinon": "^3.2.1",
"tslib": "^2.6.2",
"typedoc": "^0.25.13",
Expand Down
66 changes: 66 additions & 0 deletions src/core/util/bbox.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Coordinate from '../../geo/Coordinate';
import lineclip from 'lineclip';

const minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;

Expand Down Expand Up @@ -87,3 +88,68 @@ export function bufferBBOX(bbox: BBOX, bufferSize = 0) {
bbox[2] += bufferSize;
bbox[3] += bufferSize;
}

export function bboxIntersect(bbox1: BBOX, bbox2: BBOX) {
if (bbox1[2] < bbox2[0]) {
return false;
}
if (bbox1[1] > bbox2[3]) {
return false;
}
if (bbox1[0] > bbox2[2]) {
return false;
}
if (bbox1[3] < bbox2[1]) {
return false;
}
return true;
}

/**
* bbox Intersect Mask
* apply on TileLayer,VectorTileLayer,Geo3DTileLayer Layers
* @param bbox
* @param maskGeoJSON(Polygon/MultiPolygon GeoJSON)
* @returns
*/
export function bboxInMask(bbox: BBOX, maskGeoJSON: Record<string, any>): boolean {
//geojson bbox
const maskBBOX = maskGeoJSON.bbox;
if (!maskBBOX) {
console.error('maskGeoJSON bbox is null:', maskGeoJSON);
return false;
}
if (!bboxIntersect(maskBBOX, bbox)) {
return false;
} else {
const geometry = maskGeoJSON.geometry;
if (!geometry) {
console.error('maskGeoJSON is error,not find geometry.', maskGeoJSON);
return false;
}
let { coordinates } = geometry;
const type = geometry.type;
if (type === 'Polygon') {
coordinates = [coordinates];
}
for (let i = 0, len = coordinates.length; i < len; i++) {
const rings = coordinates[i];
const outRing = rings[0];
const result = lineclip.polygon(outRing, bbox);
if (result.length > 0) {
let minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
for (let j = 0, len1 = result.length; j < len1; j++) {
const [x, y] = result[j];
minx = Math.min(x, minx);
miny = Math.min(y, miny);
maxx = Math.max(x, maxx);
maxy = Math.max(y, maxy);
}
if (minx !== maxx && miny !== maxy) {
return true;
}
}
}
return false;
}
}
15 changes: 14 additions & 1 deletion src/layer/Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { CommonProjectionType } from '../geo/projection';
* @property options.forceRenderOnZooming=false - force to render layer when map is zooming
* @property options.forceRenderOnRotating=false - force to render layer when map is Rotating
* @property options.collisionScope=layer - layer's collision scope: layer or map
* @property options.maskClip=true - clip layer by mask(Polygon/MultiPolygon)
* @memberOf Layer
* @instance
*/
Expand All @@ -52,7 +53,8 @@ const options: LayerOptionsType = {
'collisionScope': 'layer',
'hitDetect': (function () {
return !Browser.mobile;
})()
})(),
'maskClip': true
Copy link
Collaborator Author

@deyihu deyihu May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

mask不在是单纯的剪裁了,也可能用于tile filter

  • maskClip 开启剪裁
    一切交给用户来决定

};

/**
Expand All @@ -79,6 +81,7 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) {
_drawTime: number
map: Map
_mask: Polygon | MultiPolygon | Marker;
_maskGeoJSON: Record<string, any>;
_loaded: boolean
_collisionIndex: CollisionIndex
_optionsHook?(conf?: any): void
Expand Down Expand Up @@ -550,6 +553,14 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) {
});
}
this._mask = mask;
if (mask && mask.toGeoJSON) {
try {
this._maskGeoJSON = mask.toGeoJSON();
} catch (error) {
delete this._maskGeoJSON;
console.error(error);
}
}
this.options.mask = mask.toJSON();
if (!this.getMap() || this.getMap().isZooming()) {
return this;
Expand All @@ -570,6 +581,7 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) {
*/
removeMask(): this {
delete this._mask;
delete this._maskGeoJSON;
delete this.options.mask;
if (!this.getMap() || this.getMap().isZooming()) {
return this;
Expand Down Expand Up @@ -863,6 +875,7 @@ export type LayerOptionsType = {
drawImmediate?: boolean,
geometryEvents?: boolean,
geometryEventTolerance?: number,
maskClip?: boolean;
}

export type LayerJSONType = {
Expand Down
96 changes: 88 additions & 8 deletions src/layer/tile/TileLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as vec3 from '../../core/util/vec3';
import { formatResourceUrl } from '../../core/ResourceProxy';
import { Coordinate, Extent } from '../../geo';
import { type TileLayerCanvasRenderer } from '../../renderer';
import { BBOX, bboxInMask } from '../../core/util/bbox';

const DEFAULT_MAXERROR = 1;
const TEMP_POINT = new Point(0, 0);
Expand Down Expand Up @@ -94,7 +95,7 @@ class TileHashset {
* @property [options.fetchOptions=object] - fetch params,such as fetchOptions: { 'headers': { 'accept': '' } }, about accept value more info https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
* @property [options.awareOfTerrain=true] - if the tile layer is aware of terrain.
* @property [options.bufferPixel=0.5] - tile buffer size,the unit is pixel
* @property [options.depthMask=true] - mask to decide whether to write depth buffer
* @property [options.depthMask=true] - mask to decide whether to write depth buffe
* @memberOf TileLayer
* @instance
*/
Expand Down Expand Up @@ -160,7 +161,6 @@ const options: TileLayerOptionsType = {
'mipmapTexture': true,
'depthMask': true,
'currentTilesFirst': true,

'forceRenderOnMoving': true
};

Expand Down Expand Up @@ -206,6 +206,7 @@ class TileLayer extends Layer {
_tileConfig: TileConfig;
_polygonOffset: number;
_renderer: TileLayerCanvasRenderer;
options: TileLayerOptionsType;

/**
*
Expand Down Expand Up @@ -265,13 +266,37 @@ class TileLayer extends Layer {

getTiles(z: number, parentLayer: Layer) {
this._coordCache = {};
let result;
if (this._isPyramidMode()) {
return this._getPyramidTiles(z, parentLayer);
result = this._getPyramidTiles(z, parentLayer);
} else {
return this._getCascadeTiles(z, parentLayer);
result = this._getCascadeTiles(z, parentLayer);
}
if (!this._maskGeoJSON) {
return result;
}
const tileGrids: Array<TileGridType> = result.tileGrids || [];
let count = 0;
//filter tiles by mask
tileGrids.forEach(tileGrid => {
const tiles = tileGrid.tiles || [];
tileGrid.tiles = tiles.filter(tile => {
return this._tileInMask(tile);
});
count += tileGrid.tiles.length;
const parentIds = tileGrid.tiles.map(tile => {
return tile.parent;
});
tileGrid.parents = tileGrid.parents.filter(parent => {
return parentIds.indexOf(parent.id) > -1;
});
});
result.count = count;
return result;
}



_isPyramidMode() {
const sr = this.getSpatialReference();
return !this._disablePyramid && !this._hasOwnSR && this.options['pyramidMode'] && sr && sr.isPyramid();
Expand Down Expand Up @@ -1285,7 +1310,7 @@ class TileLayer extends Layer {
if (isNumber(offset)) {
offset = [offset, offset];
}
return offset || [0, 0];
return (offset as [number, number]) || [0, 0];
}

getTileId(x: number, y: number, zoom: number, id: string): string {
Expand Down Expand Up @@ -1358,8 +1383,8 @@ class TileLayer extends Layer {
}
return this._tileConfig || this._defaultTileConfig;
}

_bindMap() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_bindMap(args?: any) {
this._onSpatialReferenceChange();
// eslint-disable-next-line prefer-rest-params
return super._bindMap.apply(this, arguments);
Expand Down Expand Up @@ -1447,6 +1472,61 @@ class TileLayer extends Layer {
getRenderer() {
return super.getRenderer() as TileLayerCanvasRenderer;
}

_getTileBBox(tile: TileNodeType): BBOX | null {
const map = this.getMap();
if (!map) {
return;
}

const extent2d = tile.extent2d;
if (!extent2d) {
return;
}
const res = tile.res;

const { xmin, ymin, xmax, ymax } = extent2d;
const pmin = new Point(xmin, ymin),
pmax = new Point(xmax, ymax);
const min = map.pointAtResToCoordinate(pmin, res, TEMP_POINT0),
max = map.pointAtResToCoordinate(pmax, res, TEMP_POINT1);
return [min.x, min.y, max.x, max.y];

}

_tileInMask(tile: TileNodeType): boolean {
const mask = this.getMask();
if (!mask) {
return true;
}
const maskType = mask.type;
if (!maskType || maskType.indexOf('Polygon') === -1) {
return true;
}
const maskGeoJSON = this._maskGeoJSON;
if (!maskGeoJSON || !maskGeoJSON.geometry) {
return true;
}
const { coordinates, type } = maskGeoJSON.geometry;
if (!coordinates || !type) {
return true;
}
if (type.indexOf('Polygon') === -1) {
return true;
}
if (!maskGeoJSON.bbox) {
const extent = mask.getExtent();
if (!extent) {
return true;
}
maskGeoJSON.bbox = [extent.xmin, extent.ymin, extent.xmax, extent.ymax];
}
const tileBBOX = this._getTileBBox(tile);
if (!tileBBOX) {
return true;
}
return bboxInMask(tileBBOX, this._maskGeoJSON);
}
}

TileLayer.registerJSONType('TileLayer');
Expand Down Expand Up @@ -1530,7 +1610,7 @@ export type TileLayerOptionsType = LayerOptionsType & {
urlTemplate: string | ((...args) => string);
subdomains?: string[];
spatialReference?: SpatialReferenceType;
tileSize?: number[] | number;
tileSize?: number | [number, number];
offset?: number[] | ((...args) => number[]);
tileSystem?: [number, number, number, number];
maxAvailableZoom?: number;
Expand Down
1 change: 1 addition & 0 deletions src/map/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const options: MapOptionsType = {
'switchDragButton': false,
'mousemoveThrottleTime': MOUSEMOVE_THROTTLE_TIME,
'maxFPS': 0,
'cameraInfiniteFar': false,
'debug': false
};

Expand Down
6 changes: 5 additions & 1 deletion src/renderer/layer/CanvasRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,8 @@ class CanvasRenderer extends Class {
return null;
}
const maskExtent2D = this._maskExtent = mask._getMaskPainter().get2DExtent();
if (!maskExtent2D.intersects(this._extent2D)) {
//fix vt _extent2D is null
if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) {
this.layer.fire('renderstart', {
'context': this.context,
'gl': this.gl
Expand Down Expand Up @@ -599,6 +600,9 @@ class CanvasRenderer extends Class {
if (!mask) {
return false;
}
if (!this.layer.options.maskClip) {
return false;
}
const old = this.middleWest;
const map = this.getMap();
//when clipping, layer's middleWest needs to be reset for mask's containerPoint conversion
Expand Down