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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
"dependencies": {
"@maptalks/feature-filter": "^1.3.0",
"@maptalks/function-type": "^1.3.1",
"@maptalks/geojson-bbox": "^1.0.5",
"frustum-intersects": "^0.1.0",
"lineclip": "^1.1.5",
"rbush": "^2.0.2",
"simplify-js": "^1.2.1"
},
Expand All @@ -63,7 +65,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 +81,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
16 changes: 16 additions & 0 deletions src/core/util/bbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,19 @@ 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;
}
14 changes: 13 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,13 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) {
});
}
this._mask = mask;
if (mask && mask.toGeoJSON) {
try {
this._maskGeoJSON = mask.toGeoJSON();
} catch (error) {
console.error(error);
}
}
this.options.mask = mask.toJSON();
if (!this.getMap() || this.getMap().isZooming()) {
return this;
Expand All @@ -570,6 +580,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 +874,7 @@ export type LayerOptionsType = {
drawImmediate?: boolean,
geometryEvents?: boolean,
geometryEventTolerance?: number,
maskClip?: boolean;
}

export type LayerJSONType = {
Expand Down
113 changes: 108 additions & 5 deletions src/layer/tile/TileLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { formatResourceUrl } from '../../core/ResourceProxy';
import { Coordinate, Extent } from '../../geo';
import { type TileLayerCanvasRenderer } from '../../renderer';
import { type Map } from '../../map';
import GeoJSONBBOX from '@maptalks/geojson-bbox';
import { bboxIntersect } from '../../core/util/bbox';
import lineclip from 'lineclip';

const DEFAULT_MAXERROR = 1;
const TEMP_POINT = new Point(0, 0);
Expand Down Expand Up @@ -96,6 +99,7 @@ class TileHashset {
* @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.onlyLoadTilesInMask=false] - only load tiles in mask
* @memberOf TileLayer
* @instance
*/
Expand Down Expand Up @@ -160,7 +164,8 @@ const options: TileLayerOptionsType = {
'bufferPixel': 0.5,
'mipmapTexture': true,
'depthMask': true,
'currentTilesFirst': true
'currentTilesFirst': true,
'onlyLoadTilesInMask': false
};

const URL_PATTERN = /\{ *([\w_]+) *\}/g;
Expand Down Expand Up @@ -205,6 +210,7 @@ class TileLayer extends Layer {
_tileConfig: TileConfig;
_polygonOffset: number;
_renderer: TileLayerCanvasRenderer;
options: TileLayerOptionsType;

/**
*
Expand Down Expand Up @@ -264,13 +270,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.options.onlyLoadTilesInMask) {
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 @@ -1284,7 +1314,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 @@ -1454,6 +1484,78 @@ class TileLayer extends Layer {
getRenderer() {
return super.getRenderer() as TileLayerCanvasRenderer;
}

_getTileBBox(tile: TileNodeType): [number, number, number, number] | null {
const map = this.getMap();
if (!map) {
return;
}
const z = tile.z;
const x = tile.x;
const y = tile.y;
const res = tile.res || map._getResolution(z);
const tileConfig = this._getTileConfig();
if (!tileConfig) {
return;
}
const prjExtent = tileConfig.getTilePrjExtent(x, y, res);
if (!prjExtent) {
return;
}
const { xmin, ymin, xmax, ymax } = prjExtent;
const pmin = new Point(xmin, ymin),
pmax = new Point(xmax, ymax);
const projection = map.getProjection();
const min = projection.unproject(pmin),
max = projection.unproject(pmax);
return [min.x, min.y, max.x, max.y];

}

_tileInMask(tile: TileNodeType): boolean {
const mask = this.getMask();
if (!mask) {
return true;
}
const type = mask.type;
if (!type || type.indexOf('Polygon') === -1) {
return true;
}
if (!this._maskGeoJSON) {
return true;
}
//cal geojson bbox
if (!this._maskGeoJSON.bbox) {
this._maskGeoJSON.bbox = GeoJSONBBOX(this._maskGeoJSON);
}
const tileBBOX = this._getTileBBox(tile);
if (!tileBBOX) {
return true;
}
const maskBBOX = this._maskGeoJSON.bbox;
if (!bboxIntersect(maskBBOX, tileBBOX)) {
return false;
} else {
const geometry = this._maskGeoJSON.geometry;
if (!geometry) {
return true;
}
let { type, coordinates } = geometry;
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, tileBBOX);
if (result.length > 0) {
return true;
}
}
return false;
}

}
}

TileLayer.registerJSONType('TileLayer');
Expand Down Expand Up @@ -1537,7 +1639,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 Expand Up @@ -1571,4 +1673,5 @@ export type TileLayerOptionsType = LayerOptionsType & {
tileStackDepth?: number;
mipmapTexture?: boolean;
currentTilesFirst?: boolean;
onlyLoadTilesInMask?: boolean;
};
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