Skip to content

Commit ca8f97f

Browse files
authored
Optimize geometry pick performance by render bbox (#1998)
* Optimize geometry pick performance by render bbox * update * resetbbox when geom not in current view * update * fix * update * update * update * update * update
1 parent 4afd9e5 commit ca8f97f

File tree

12 files changed

+220
-17
lines changed

12 files changed

+220
-17
lines changed

src/core/Canvas.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { createEl } from './util/dom';
1313
import Browser from './Browser';
1414
import Point from '../geo/Point';
1515
import { getFont, getAlignPoint } from './util/strings';
16+
import { BBOX_TEMP, resetBBOX, setBBOX } from './util/bbox';
1617

1718
const DEFAULT_STROKE_COLOR = '#000';
1819
const DEFAULT_FILL_COLOR = 'rgba(255,255,255,0)';
@@ -312,7 +313,7 @@ const Canvas = {
312313
},
313314

314315
text(ctx, text, pt, style, textDesc) {
315-
Canvas._textOnMultiRow(ctx, textDesc['rows'], style, pt, textDesc['size'], textDesc['rawSize']);
316+
return Canvas._textOnMultiRow(ctx, textDesc['rows'], style, pt, textDesc['size'], textDesc['rawSize']);
316317
},
317318

318319
_textOnMultiRow(ctx, texts, style, point, splitTextSize, textSize) {
@@ -321,17 +322,23 @@ const Canvas = {
321322
basePoint = point.add(0, ptAlign.y),
322323
maxHeight = style['textMaxHeight'];
323324
let text, rowAlign, height = 0;
325+
resetBBOX(BBOX_TEMP);
324326
for (let i = 0, len = texts.length; i < len; i++) {
325327
text = texts[i]['text'];
326328
rowAlign = getAlignPoint(texts[i]['size'], style['textHorizontalAlignment'], style['textVerticalAlignment']);
327-
Canvas._textOnLine(ctx, text, basePoint.add(rowAlign.x, i * lineHeight), style['textHaloRadius'], style['textHaloFill'], style['textHaloOpacity']);
329+
const point = basePoint.add(rowAlign.x, i * lineHeight);
330+
Canvas._textOnLine(ctx, text, point, style['textHaloRadius'], style['textHaloFill'], style['textHaloOpacity']);
331+
const textSize = texts[i].size;
332+
const minx = point.x, miny = point.y, maxx = minx + textSize.width, maxy = miny + textSize.height;
333+
setBBOX(BBOX_TEMP, minx, miny, maxx, maxy);
328334
if (maxHeight > 0) {
329335
height += lineHeight;
330336
if (height + textSize['height'] >= maxHeight) {
331337
break;
332338
}
333339
}
334340
}
341+
return BBOX_TEMP;
335342
},
336343

337344
_textOnLine(ctx, text, pt, textHaloRadius, textHaloFill, textHaloAlpha) {

src/core/util/bbox.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const minx = Infinity, miny = Infinity, maxx = -Infinity, maxy = -Infinity;
2+
3+
4+
export function getDefaultBBOX() {
5+
return [minx, miny, maxx, maxy];
6+
}
7+
8+
export const BBOX_TEMP = getDefaultBBOX();
9+
10+
//reset bbox
11+
export function resetBBOX(bbox) {
12+
bbox[0] = minx;
13+
bbox[1] = miny;
14+
bbox[2] = maxx;
15+
bbox[3] = maxy;
16+
}
17+
18+
//cal points bbox:linestring,polygon etc
19+
export function pointsBBOX(points, out) {
20+
if (!points) {
21+
return;
22+
}
23+
if (Array.isArray(points[0])) {
24+
for (let i = 0, len = points.length; i < len; i++) {
25+
pointsBBOX(points[i], out);
26+
}
27+
} else if (Array.isArray(points)) {
28+
for (let i = 0, len = points.length; i < len; i++) {
29+
const { x, y } = points[i];
30+
out[0] = Math.min(x, out[0]);
31+
out[1] = Math.min(y, out[1]);
32+
out[2] = Math.max(x, out[2]);
33+
out[3] = Math.max(y, out[3]);
34+
}
35+
} else {
36+
const { x, y } = points;
37+
out[0] = Math.min(x, out[0]);
38+
out[1] = Math.min(y, out[1]);
39+
out[2] = Math.max(x, out[2]);
40+
out[3] = Math.max(y, out[3]);
41+
}
42+
}
43+
44+
export function setBBOX(bbox, x1, y1, x2, y2) {
45+
if (x1 !== 0 && !x1) {
46+
return;
47+
}
48+
//x1 is bbox array
49+
if (Array.isArray(x1)) {
50+
y1 = x1[1];
51+
x2 = x1[2];
52+
y2 = x1[3];
53+
x1 = x1[0];
54+
}
55+
bbox[0] = Math.min(x1, bbox[0]);
56+
bbox[1] = Math.min(y1, bbox[1]);
57+
bbox[2] = Math.max(x2, bbox[2]);
58+
bbox[3] = Math.max(y2, bbox[3]);
59+
}
60+
61+
export function validateBBOX(bbox) {
62+
return bbox && bbox[0] !== Infinity && bbox[0] !== undefined;
63+
}

src/layer/VectorLayer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ class VectorLayer extends OverlayLayer {
168168
if (!geo || !geo.isVisible() || !geo._getPainter() || !geo.options['interactive']) {
169169
continue;
170170
}
171+
const painter = geo._getPainter();
172+
const bbox = painter.getRenderBBOX && painter.getRenderBBOX();
173+
if (bbox) {
174+
const { x, y } = cp;
175+
if (x < bbox[0] || y < bbox[1] || x > bbox[2] || y > bbox[3]) {
176+
continue;
177+
}
178+
}
171179
if (!(geo instanceof LineString) || (!geo._getArrowStyle() && !(geo instanceof Curve))) {
172180
// Except for LineString with arrows or curves
173181
let extent = geo.getContainerExtent(TEMP_EXTENT);

src/renderer/geometry/CollectionPainter.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Class from '../../core/Class';
22
import PointExtent from '../../geo/PointExtent';
3+
import { getDefaultBBOX, resetBBOX, setBBOX, validateBBOX } from '../../core/util/bbox';
34

45
const TEMP_EXTENT = new PointExtent();
56

@@ -17,8 +18,38 @@ export default class CollectionPainter extends Class {
1718
super();
1819
this.geometry = geometry;
1920
this.isMask = isMask;
21+
this.bbox = getDefaultBBOX();
22+
this._drawTime = 0;
2023
}
2124

25+
_setDrawTime(time) {
26+
this._drawTime = time;
27+
this._eachPainter((painter) => {
28+
painter._setDrawTime(time);
29+
});
30+
return this;
31+
}
32+
33+
getRenderBBOX() {
34+
const layer = this.getLayer();
35+
if (layer && layer._drawTime !== this._drawTime) {
36+
return null;
37+
}
38+
resetBBOX(this.bbox);
39+
this._eachPainter((painter) => {
40+
const bbox = painter.getRenderBBOX();
41+
if (!validateBBOX(bbox)) {
42+
return;
43+
}
44+
setBBOX(this.bbox, bbox);
45+
});
46+
if (validateBBOX(this.bbox)) {
47+
return this.bbox;
48+
}
49+
return null;
50+
}
51+
52+
2253
_eachPainter(fn) {
2354
const geometries = this.geometry.getGeometries();
2455
let painter;
@@ -35,6 +66,11 @@ export default class CollectionPainter extends Class {
3566
}
3667
}
3768

69+
getLayer() {
70+
return this.geometry && this.geometry.getLayer();
71+
}
72+
73+
3874
paint(extent) {
3975
if (!this.geometry) {
4076
return;

src/renderer/geometry/Painter.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import PointExtent from '../../geo/PointExtent';
77
import Canvas from '../../core/Canvas';
88
import * as Symbolizers from './symbolizers';
99
import { interpolate } from '../../core/util/util';
10+
import { getDefaultBBOX, resetBBOX, setBBOX, validateBBOX } from '../../core/util/bbox';
1011

1112
//registered symbolizers
1213
//the latter will paint at the last
@@ -54,14 +55,41 @@ class Painter extends Class {
5455
this.geometry = geometry;
5556
this.symbolizers = this._createSymbolizers();
5657
this._altAtGL = this._getGeometryAltitude();
58+
this.bbox = getDefaultBBOX();
59+
this._drawTime = 0;
60+
}
61+
62+
_setDrawTime(time) {
63+
this._drawTime = time;
64+
return this;
65+
}
66+
67+
getRenderBBOX() {
68+
const layer = this.getLayer();
69+
if (layer && layer._drawTime !== this._drawTime) {
70+
return null;
71+
}
72+
resetBBOX(this.bbox);
73+
for (let i = this.symbolizers.length - 1; i >= 0; i--) {
74+
const symbolizer = this.symbolizers[i];
75+
const bbox = symbolizer.bbox;
76+
if (!validateBBOX(bbox)) {
77+
continue;
78+
}
79+
setBBOX(this.bbox, bbox);
80+
}
81+
if (validateBBOX(this.bbox)) {
82+
return this.bbox;
83+
}
84+
return null;
5785
}
5886

5987
getMap() {
6088
return this.geometry.getMap();
6189
}
6290

6391
getLayer() {
64-
return this.geometry.getLayer();
92+
return this.geometry && this.geometry.getLayer();
6593
}
6694

6795
/**
@@ -535,6 +563,16 @@ class Painter extends Class {
535563
return this.geometry._getInternalSymbol();
536564
}
537565

566+
_resetSymbolizersBBOX() {
567+
//reset all symbolizers render bbox
568+
for (let i = this.symbolizers.length - 1; i >= 0; i--) {
569+
const symbolizer = this.symbolizers[i];
570+
const bbox = symbolizer.bbox;
571+
resetBBOX(bbox);
572+
}
573+
return this;
574+
}
575+
538576
paint(extent, context, offset) {
539577
if (!this.symbolizers) {
540578
return;
@@ -559,6 +597,9 @@ class Painter extends Class {
559597
this.containerOffset = offset || mapStateCache.offset || map._pointToContainerPoint(renderer.southWest)._add(0, -map.height);
560598
this._beforePaint();
561599
const ctx = context || renderer.context;
600+
if (!ctx.isHitTesting) {
601+
this._resetSymbolizersBBOX();
602+
}
562603
const contexts = [ctx, renderer.resources];
563604
for (let i = this.symbolizers.length - 1; i >= 0; i--) {
564605
// reduce function call
@@ -633,6 +674,7 @@ class Painter extends Class {
633674
Canvas.setHitTesting(true);
634675
testCanvas.width = testCanvas.height = 2 * tolerance;
635676
const ctx = Canvas.getCanvas2DContext(testCanvas);
677+
ctx.isHitTesting = true;
636678
try {
637679
this.paint(null, ctx, this._hitPoint);
638680
} catch (e) {

src/renderer/geometry/VectorRenderer.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Rectangle from '../../geometry/Rectangle';
99
import Path from '../../geometry/Path';
1010
import LineString from '../../geometry/LineString';
1111
import Polygon from '../../geometry/Polygon';
12+
import { BBOX_TEMP, pointsBBOX, resetBBOX } from '../../core/util/bbox';
1213

1314
const TEMP_WITHIN = {
1415
within: false,
@@ -36,7 +37,16 @@ function isWithinPixel(painter) {
3637
Geometry.include({
3738
_redrawWhenPitch: () => false,
3839

39-
_redrawWhenRotate: () => false
40+
_redrawWhenRotate: () => false,
41+
42+
_getRenderBBOX(ctx, points) {
43+
if (!ctx.isHitTesting) {
44+
resetBBOX(BBOX_TEMP);
45+
pointsBBOX(points, BBOX_TEMP);
46+
return BBOX_TEMP;
47+
}
48+
return null;
49+
}
4050
});
4151

4252
const el = {
@@ -187,6 +197,7 @@ LineString.include({
187197
Canvas.path(ctx, points, lineOpacity, null, dasharray);
188198
}
189199
this._paintArrow(ctx, points, lineOpacity);
200+
return this._getRenderBBOX(ctx, points);
190201
},
191202

192203
_getArrowPlacement() {
@@ -311,6 +322,7 @@ Polygon.include({
311322
} else {
312323
Canvas.polygon(ctx, points, lineOpacity, fillOpacity, dasharray, this.options['smoothness']);
313324
}
325+
return this._getRenderBBOX(ctx, points);
314326
}
315327
});
316328

src/renderer/geometry/symbolizers/ImageMarkerSymbolizer.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ export default class ImageMarkerSymbolizer extends PointSymbolizer {
7070
if (origin) {
7171
p = origin;
7272
}
73+
const x = p.x + alignPoint.x, y = p.y + alignPoint.y;
7374
Canvas.image(ctx, img,
74-
p.x + alignPoint.x,
75-
p.y + alignPoint.y,
75+
x,
76+
y,
7677
width, height);
7778
if (origin) {
7879
ctx.restore();
7980
}
81+
this._setBBOX(ctx, x, y, x + width, y + height);
8082
}
8183
if (alpha !== undefined) {
8284
ctx.globalAlpha = alpha;

src/renderer/geometry/symbolizers/StrokeAndFillSymbolizer.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default class StrokeAndFillSymbolizer extends CanvasSymbolizer {
5757

5858
const points = paintParams[0],
5959
isSplitted = (this.geometry.getJSONType() === 'Polygon' && points.length > 0 && Array.isArray(points[0][0])) ||
60-
(this.geometry.type === 'LineString' && points.length > 0 && Array.isArray(points[0]));
60+
(this.geometry.type === 'LineString' && points.length > 0 && Array.isArray(points[0]));
6161

6262
if (isSplitted) {
6363
for (let i = 0; i < points.length; i++) {
@@ -70,7 +70,8 @@ export default class StrokeAndFillSymbolizer extends CanvasSymbolizer {
7070
params.push.apply(params, paintParams.slice(1));
7171
}
7272
params.push(style['lineOpacity'], style['polygonOpacity'], style['lineDasharray']);
73-
this.geometry._paintOn.apply(this.geometry, params);
73+
const bbox = this.geometry._paintOn.apply(this.geometry, params);
74+
this._setBBOX(ctx, bbox);
7475
}
7576
} else {
7677
this.prepareCanvas(ctx, style, resources);
@@ -80,7 +81,8 @@ export default class StrokeAndFillSymbolizer extends CanvasSymbolizer {
8081
const params = [ctx];
8182
params.push.apply(params, paintParams);
8283
params.push(style['lineOpacity'], style['polygonOpacity'], style['lineDasharray']);
83-
this.geometry._paintOn.apply(this.geometry, params);
84+
const bbox = this.geometry._paintOn.apply(this.geometry, params);
85+
this._setBBOX(ctx, bbox);
8486
}
8587

8688
if (ctx.setLineDash && Array.isArray(style['lineDasharray'])) {
@@ -138,15 +140,15 @@ export default class StrokeAndFillSymbolizer extends CanvasSymbolizer {
138140
'lineCap': getValueOrDefault(s['lineCap'], 'butt'), //“butt”, “square”, “round”
139141
'lineJoin': getValueOrDefault(s['lineJoin'], 'miter'), //“bevel”, “round”, “miter”
140142
'linePatternFile': getValueOrDefault(s['linePatternFile'], null),
141-
'lineDx' : getValueOrDefault(s['lineDx'], 0),
142-
'lineDy' : getValueOrDefault(s['lineDy'], 0),
143+
'lineDx': getValueOrDefault(s['lineDx'], 0),
144+
'lineDy': getValueOrDefault(s['lineDy'], 0),
143145
'polygonFill': getValueOrDefault(s['polygonFill'], null),
144146
'polygonOpacity': getValueOrDefault(s['polygonOpacity'], 1),
145147
'polygonPatternFile': getValueOrDefault(s['polygonPatternFile'], null),
146-
'polygonPatternDx' : getValueOrDefault(s['polygonPatternDx'], 0),
147-
'polygonPatternDy' : getValueOrDefault(s['polygonPatternDy'], 0),
148-
'linePatternDx' : getValueOrDefault(s['linePatternDx'], 0),
149-
'linePatternDy' : getValueOrDefault(s['linePatternDy'], 0)
148+
'polygonPatternDx': getValueOrDefault(s['polygonPatternDx'], 0),
149+
'polygonPatternDy': getValueOrDefault(s['polygonPatternDy'], 0),
150+
'linePatternDx': getValueOrDefault(s['linePatternDx'], 0),
151+
'linePatternDy': getValueOrDefault(s['linePatternDy'], 0)
150152
};
151153
if (result['lineWidth'] === 0) {
152154
result['lineOpacity'] = 0;

0 commit comments

Comments
 (0)