From 69960eb9df488a53822c648999bb549141bfc548 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Mon, 16 Apr 2018 12:40:12 -0700 Subject: [PATCH 1/5] Make GridIndex query calls take an optional predicate that can filter against the content of keys. --- src/symbol/grid_index.js | 43 +++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/symbol/grid_index.js b/src/symbol/grid_index.js index 811538a1006..3f2e21a9964 100644 --- a/src/symbol/grid_index.js +++ b/src/symbol/grid_index.js @@ -87,7 +87,7 @@ class GridIndex { this.circleCells[cellIndex].push(uid); } - _query(x1: number, y1: number, x2: number, y2: number, hitTest: boolean) { + _query(x1: number, y1: number, x2: number, y2: number, hitTest: boolean, predicate?: any) { if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) { return hitTest ? false : []; } @@ -117,17 +117,20 @@ class GridIndex { y2: y + radius }); } + if (predicate) { + return result.filter(predicate); + } } else { const queryArgs = { hitTest, seenUids: { box: {}, circle: {} } }; - this._forEachCell(x1, y1, x2, y2, this._queryCell, result, queryArgs); + this._forEachCell(x1, y1, x2, y2, this._queryCell, result, queryArgs, predicate); } return hitTest ? result.length > 0 : result; } - _queryCircle(x: number, y: number, radius: number, hitTest: boolean) { + _queryCircle(x: number, y: number, radius: number, hitTest: boolean, predicate?: any) { // Insert circle into grid for all cells in the circumscribing square // It's more than necessary (by a factor of 4/PI), but fast to insert const x1 = x - radius; @@ -147,23 +150,23 @@ class GridIndex { circle: { x: x, y: y, radius: radius }, seenUids: { box: {}, circle: {} } }; - this._forEachCell(x1, y1, x2, y2, this._queryCellCircle, result, queryArgs); + this._forEachCell(x1, y1, x2, y2, this._queryCellCircle, result, queryArgs, predicate); return hitTest ? result.length > 0 : result; } - query(x1: number, y1: number, x2: number, y2: number): Array { - return (this._query(x1, y1, x2, y2, false): any); + query(x1: number, y1: number, x2: number, y2: number, predicate?: any): Array { + return (this._query(x1, y1, x2, y2, false, predicate): any); } - hitTest(x1: number, y1: number, x2: number, y2: number): boolean { - return (this._query(x1, y1, x2, y2, true): any); + hitTest(x1: number, y1: number, x2: number, y2: number, predicate?: any): boolean { + return (this._query(x1, y1, x2, y2, true, predicate): any); } - hitTestCircle(x: number, y: number, radius: number): boolean { - return (this._queryCircle(x, y, radius, true): any); + hitTestCircle(x: number, y: number, radius: number, predicate?: any): boolean { + return (this._queryCircle(x, y, radius, true, predicate): any); } - _queryCell(x1: number, y1: number, x2: number, y2: number, cellIndex: number, result: any, queryArgs: any) { + _queryCell(x1: number, y1: number, x2: number, y2: number, cellIndex: number, result: any, queryArgs: any, predicate?: any) { const seenUids = queryArgs.seenUids; const boxCell = this.boxCells[cellIndex]; if (boxCell !== null) { @@ -175,7 +178,8 @@ class GridIndex { if ((x1 <= bboxes[offset + 2]) && (y1 <= bboxes[offset + 3]) && (x2 >= bboxes[offset + 0]) && - (y2 >= bboxes[offset + 1])) { + (y2 >= bboxes[offset + 1]) && + (!predicate || predicate(this.boxKeys[boxUid]))) { if (queryArgs.hitTest) { result.push(true); return true; @@ -206,7 +210,8 @@ class GridIndex { x1, y1, x2, - y2)) { + y2) && + (!predicate || predicate(this.circleKeys[circleUid]))) { if (queryArgs.hitTest) { result.push(true); return true; @@ -228,7 +233,7 @@ class GridIndex { } } - _queryCellCircle(x1: number, y1: number, x2: number, y2: number, cellIndex: number, result: any, queryArgs: any) { + _queryCellCircle(x1: number, y1: number, x2: number, y2: number, cellIndex: number, result: any, queryArgs: any, predicate?: any) { const circle = queryArgs.circle; const seenUids = queryArgs.seenUids; const boxCell = this.boxCells[cellIndex]; @@ -245,7 +250,8 @@ class GridIndex { bboxes[offset + 0], bboxes[offset + 1], bboxes[offset + 2], - bboxes[offset + 3])) { + bboxes[offset + 3]) && + (!predicate || predicate(this.boxKeys[boxUid]))) { result.push(true); return true; } @@ -266,7 +272,8 @@ class GridIndex { circles[offset + 2], circle.x, circle.y, - circle.radius)) { + circle.radius) && + (!predicate || predicate(this.circleKeys[circleUid]))) { result.push(true); return true; } @@ -275,7 +282,7 @@ class GridIndex { } } - _forEachCell(x1: number, y1: number, x2: number, y2: number, fn: any, arg1: any, arg2?: any) { + _forEachCell(x1: number, y1: number, x2: number, y2: number, fn: any, arg1: any, arg2?: any, predicate?: any) { const cx1 = this._convertToXCellCoord(x1); const cy1 = this._convertToYCellCoord(y1); const cx2 = this._convertToXCellCoord(x2); @@ -284,7 +291,7 @@ class GridIndex { for (let x = cx1; x <= cx2; x++) { for (let y = cy1; y <= cy2; y++) { const cellIndex = this.xCellCount * y + x; - if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2)) return; + if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2, predicate)) return; } } } From 14d740eeb08eeaa751bc5a84720b1b1ab4168ca6 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Mon, 16 Apr 2018 12:44:49 -0700 Subject: [PATCH 2/5] Introduce "collision groups": collision detection happens within groups but not between them. --- flow-typed/style-spec.js | 2 + src/style-spec/reference/v8.json | 26 ++++++++ .../symbol_style_layer_properties.js | 4 ++ src/symbol/collision_index.js | 17 +++--- src/symbol/placement.js | 60 ++++++++++++++++--- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/flow-typed/style-spec.js b/flow-typed/style-spec.js index df6e3260355..88439b72edb 100644 --- a/flow-typed/style-spec.js +++ b/flow-typed/style-spec.js @@ -207,6 +207,7 @@ declare type SymbolLayerSpecification = {| "symbol-avoid-edges"?: PropertyValueSpecification, "icon-allow-overlap"?: PropertyValueSpecification, "icon-ignore-placement"?: PropertyValueSpecification, + "icon-collision-group"?: string, "icon-optional"?: PropertyValueSpecification, "icon-rotation-alignment"?: PropertyValueSpecification<"map" | "viewport" | "auto">, "icon-size"?: DataDrivenPropertyValueSpecification, @@ -237,6 +238,7 @@ declare type SymbolLayerSpecification = {| "text-offset"?: DataDrivenPropertyValueSpecification<[number, number]>, "text-allow-overlap"?: PropertyValueSpecification, "text-ignore-placement"?: PropertyValueSpecification, + "text-collision-group"?: string, "text-optional"?: PropertyValueSpecification, "visibility"?: "visible" | "none" |}, diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index ce51ed31b3e..3d8ea15ff7e 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -975,6 +975,19 @@ }, "property-type": "data-constant" }, + "icon-collision-group": { + "type": "string", + "doc": "Restricts collision detection to other symbols in the same group.", + "requires": [ + "icon-image" + ], + "sdk-support": { + "basic functionality": { + "js": "0.45.0" + }, + "data-driven styling": {} + } + }, "icon-optional": { "type": "boolean", "default": false, @@ -2001,6 +2014,19 @@ }, "property-type": "data-constant" }, + "text-collision-group": { + "type": "string", + "doc": "Restricts collision detection to other symbols in the same group.", + "requires": [ + "text-field" + ], + "sdk-support": { + "basic functionality": { + "js": "0.45.0" + }, + "data-driven styling": {} + } + }, "text-optional": { "type": "boolean", "default": false, diff --git a/src/style/style_layer/symbol_style_layer_properties.js b/src/style/style_layer/symbol_style_layer_properties.js index 2c00d943685..f0e03b611ce 100644 --- a/src/style/style_layer/symbol_style_layer_properties.js +++ b/src/style/style_layer/symbol_style_layer_properties.js @@ -20,6 +20,7 @@ export type LayoutProps = {| "symbol-avoid-edges": DataConstantProperty, "icon-allow-overlap": DataConstantProperty, "icon-ignore-placement": DataConstantProperty, + "icon-collision-group": DataConstantProperty, "icon-optional": DataConstantProperty, "icon-rotation-alignment": DataConstantProperty<"map" | "viewport" | "auto">, "icon-size": DataDrivenProperty, @@ -50,6 +51,7 @@ export type LayoutProps = {| "text-offset": DataDrivenProperty<[number, number]>, "text-allow-overlap": DataConstantProperty, "text-ignore-placement": DataConstantProperty, + "text-collision-group": DataConstantProperty, "text-optional": DataConstantProperty, |}; @@ -59,6 +61,7 @@ const layout: Properties = new Properties({ "symbol-avoid-edges": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-avoid-edges"]), "icon-allow-overlap": new DataConstantProperty(styleSpec["layout_symbol"]["icon-allow-overlap"]), "icon-ignore-placement": new DataConstantProperty(styleSpec["layout_symbol"]["icon-ignore-placement"]), + "icon-collision-group": new DataConstantProperty(styleSpec["layout_symbol"]["icon-collision-group"]), "icon-optional": new DataConstantProperty(styleSpec["layout_symbol"]["icon-optional"]), "icon-rotation-alignment": new DataConstantProperty(styleSpec["layout_symbol"]["icon-rotation-alignment"]), "icon-size": new DataDrivenProperty(styleSpec["layout_symbol"]["icon-size"]), @@ -89,6 +92,7 @@ const layout: Properties = new Properties({ "text-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-offset"]), "text-allow-overlap": new DataConstantProperty(styleSpec["layout_symbol"]["text-allow-overlap"]), "text-ignore-placement": new DataConstantProperty(styleSpec["layout_symbol"]["text-ignore-placement"]), + "text-collision-group": new DataConstantProperty(styleSpec["layout_symbol"]["text-collision-group"]), "text-optional": new DataConstantProperty(styleSpec["layout_symbol"]["text-optional"]), }); diff --git a/src/symbol/collision_index.js b/src/symbol/collision_index.js index 7e85a680815..c1ea7aaf8eb 100644 --- a/src/symbol/collision_index.js +++ b/src/symbol/collision_index.js @@ -58,7 +58,7 @@ class CollisionIndex { this.screenBottomBoundary = transform.height + viewportPadding; } - placeCollisionBox(collisionBox: SingleCollisionBox, allowOverlap: boolean, textPixelRatio: number, posMatrix: mat4): { box: Array, offscreen: boolean } { + placeCollisionBox(collisionBox: SingleCollisionBox, allowOverlap: boolean, textPixelRatio: number, posMatrix: mat4, collisionGroupPredicate?: any): { box: Array, offscreen: boolean } { const projectedPoint = this.projectAndGetPerspectiveRatio(posMatrix, collisionBox.anchorPointX, collisionBox.anchorPointY); const tileToViewport = textPixelRatio * projectedPoint.perspectiveRatio; const tlX = collisionBox.x1 * tileToViewport + projectedPoint.point.x; @@ -67,7 +67,7 @@ class CollisionIndex { const brY = collisionBox.y2 * tileToViewport + projectedPoint.point.y; if (!allowOverlap) { - if (this.grid.hitTest(tlX, tlY, brX, brY)) { + if (this.grid.hitTest(tlX, tlY, brX, brY, collisionGroupPredicate)) { return { box: [], offscreen: false @@ -113,7 +113,8 @@ class CollisionIndex { posMatrix: mat4, labelPlaneMatrix: mat4, showCollisionCircles: boolean, - pitchWithMap: boolean): { circles: Array, offscreen: boolean } { + pitchWithMap: boolean, + collisionGroupPredicate?: any): { circles: Array, offscreen: boolean } { const placedCollisionCircles = []; const projectedAnchor = this.projectAnchor(posMatrix, symbol.anchorX, symbol.anchorY); @@ -208,7 +209,7 @@ class CollisionIndex { entirelyOffscreen = entirelyOffscreen && this.isOffscreen(projectedPoint.x - radius, projectedPoint.y - radius, projectedPoint.x + radius, projectedPoint.y + radius); if (!allowOverlap) { - if (this.grid.hitTestCircle(projectedPoint.x, projectedPoint.y, radius)) { + if (this.grid.hitTestCircle(projectedPoint.x, projectedPoint.y, radius, collisionGroupPredicate)) { if (!showCollisionCircles) { return { circles: [], @@ -296,17 +297,17 @@ class CollisionIndex { return result; } - insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number) { + insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroup: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; - const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex }; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroup: collisionGroup }; grid.insert(key, collisionBox[0], collisionBox[1], collisionBox[2], collisionBox[3]); } - insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number) { + insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroup: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; - const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex }; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroup: collisionGroup }; for (let k = 0; k < collisionCircles.length; k += 4) { grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]); } diff --git a/src/symbol/placement.js b/src/symbol/placement.js index da0454c8531..0f4bb32dff6 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -69,7 +69,6 @@ export class RetainedQueryData { bucketIndex: number; tileID: OverscaledTileID; featureSortOrder: ?Array - constructor(bucketInstanceId: number, featureIndex: FeatureIndex, sourceLayerIndex: number, @@ -83,6 +82,45 @@ export class RetainedQueryData { } } +class CollisionGroups { + collisionGroups: { [layerId: ?string]: { ID: number, predicate?: any }}; + maxGroupID: number; + + constructor() { + this.maxGroupID = 0; + this.collisionGroups = { + undefined: { + ID: 0, + predicate: null + } + }; + } + + get(groupName?: string) { + if (groupName && this.maxGroupID === 0) { + // Keep the predicate null until the first collision + // group gets added to avoid overhead in the case + // everything's in the default group. + this.collisionGroups[undefined].predicate = + (key) => { + return key.collisionGroup === 0; + }; + } + + if (!this.collisionGroups[groupName]) { + const nextGroupID = ++this.maxGroupID; + this.collisionGroups[groupName] = { + ID: nextGroupID, + predicate: (key) => { + return key.collisionGroup === nextGroupID; + } + }; + } + return this.collisionGroups[groupName]; + } +} + + export class Placement { transform: Transform; collisionIndex: CollisionIndex; @@ -93,6 +131,7 @@ export class Placement { stale: boolean; fadeDuration: number; retainedQueryData: {[number]: RetainedQueryData}; + collisionGroups: CollisionGroups; constructor(transform: Transform, fadeDuration: number) { this.transform = transform.clone(); @@ -102,6 +141,7 @@ export class Placement { this.stale = false; this.fadeDuration = fadeDuration; this.retainedQueryData = {}; + this.collisionGroups = new CollisionGroups(); } placeLayerTile(styleLayer: StyleLayer, tile: Tile, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }) { @@ -155,6 +195,11 @@ export class Placement { const iconWithoutText = !bucket.hasTextData() || layout.get('text-optional'); const textWithoutIcon = !bucket.hasIconData() || layout.get('icon-optional'); + const textCollisionGroup = + this.collisionGroups.get(layout.get('text-collision-group')); + const iconCollisionGroup = + this.collisionGroups.get(layout.get('icon-collision-group')); + for (const symbolInstance of bucket.symbolInstances) { if (!seenCrossTileIDs[symbolInstance.crossTileID]) { @@ -180,7 +225,7 @@ export class Placement { } if (symbolInstance.collisionArrays.textBox) { placedGlyphBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.textBox, - layout.get('text-allow-overlap'), textPixelRatio, posMatrix); + layout.get('text-allow-overlap'), textPixelRatio, posMatrix, textCollisionGroup.predicate); placeText = placedGlyphBoxes.box.length > 0; offscreen = offscreen && placedGlyphBoxes.offscreen; } @@ -200,7 +245,8 @@ export class Placement { posMatrix, textLabelPlaneMatrix, showCollisionBoxes, - layout.get('text-pitch-alignment') === 'map'); + layout.get('text-pitch-alignment') === 'map', + textCollisionGroup.predicate); // If text-allow-overlap is set, force "placedCircles" to true // In theory there should always be at least one circle placed // in this case, but for now quirks in text-anchor @@ -214,7 +260,7 @@ export class Placement { } if (symbolInstance.collisionArrays.iconBox) { placedIconBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.iconBox, - layout.get('icon-allow-overlap'), textPixelRatio, posMatrix); + layout.get('icon-allow-overlap'), textPixelRatio, posMatrix, iconCollisionGroup.predicate); placeIcon = placedIconBoxes.box.length > 0; offscreen = offscreen && placedIconBoxes.offscreen; } @@ -230,15 +276,15 @@ export class Placement { if (placeText && placedGlyphBoxes) { this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), - bucket.bucketInstanceId, textFeatureIndex); + bucket.bucketInstanceId, textFeatureIndex, textCollisionGroup.ID); } if (placeIcon && placedIconBoxes) { this.collisionIndex.insertCollisionBox(placedIconBoxes.box, layout.get('icon-ignore-placement'), - bucket.bucketInstanceId, iconFeatureIndex); + bucket.bucketInstanceId, iconFeatureIndex, iconCollisionGroup.ID); } if (placeText && placedGlyphCircles) { this.collisionIndex.insertCollisionCircles(placedGlyphCircles.circles, layout.get('text-ignore-placement'), - bucket.bucketInstanceId, textFeatureIndex); + bucket.bucketInstanceId, textFeatureIndex, textCollisionGroup.ID); } assert(symbolInstance.crossTileID !== 0); From 4f920a5acf8fefd68569343eb7314fc8f0e48dd1 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Mon, 22 Jan 2018 14:32:30 -0800 Subject: [PATCH 3/5] Render tests for text-collision-group and icon-collision-group. --- .../icon-collision-group/default/expected.png | Bin 0 -> 2656 bytes .../icon-collision-group/default/style.json | 128 +++++++++++++++ .../text-collision-group/default/expected.png | Bin 0 -> 6433 bytes .../text-collision-group/default/style.json | 152 ++++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 test/integration/render-tests/icon-collision-group/default/expected.png create mode 100644 test/integration/render-tests/icon-collision-group/default/style.json create mode 100644 test/integration/render-tests/text-collision-group/default/expected.png create mode 100644 test/integration/render-tests/text-collision-group/default/style.json diff --git a/test/integration/render-tests/icon-collision-group/default/expected.png b/test/integration/render-tests/icon-collision-group/default/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3af0d05b2a282791b35283b26722207e19adb250 GIT binary patch literal 2656 zcmbuBdoYi^My zw=SrO5{5EkBtsdQF~tmnGvloFuJ@ev{`0PPt>5q2zrEMq&u_2i`97b$Q%<@%?gA(S z1Oxh=2eyrw zhcTnh4o8-8LK82i?30#2IK)4=pL_D79b)u&D9J0Y=ISQ$V}|j!SAQT*@Oj@3}2c224`hQEuH zYD3$1Yrq7$eR+ypD@tw!x#Jkxc`Ek+Yr3cpw%W*H0yqY6cs|iDw@iwkP^k-1GT`0%VVpk@MiZlmd8mC zCxqSTKfPP4vUvYRv4wS$8yS81)~}GoUQzv>KlXF!qONM3y+SWf1_K%&h(^wd`Pu`# zKm2;|(Bs2kbzhq}bFuUB@$%GDog-VRt42f22aH7NTC$>e*_P(pXwRvv!Sf3X3-_a1 z@33vV@s?#}a&FX`#hj+c8|}F+v~gZ$0`itEn*qGc5HG7$E>=jD#@xtA%a6^@v_rKO zx8T^J%C*^iH>M2oa+HqGTZ@znHuL|EsE$9ae|+g8t9ZOs0X=m3WWje)hr;LJ$}k<} z4)fc*xL23oI^c#YwpigJQr#OcN!0P+E{0xc18hSfFha3Jc)rI>-VGDnUVCCmyik$iE*o#i1F7dy#3(-}2*m z0ZbCc64pOuG}T8-e)7p@5cMK$n|j7YX9Vd6Ck zgOcQYRm>C=qDf?o+?8y&*Z8xD)E@Kou7Z5S!z{+QCpT}=eZM5HDCFjDxkHB^kGXIM z^+HTzzXNWrO>0(H&!d7g^fx6Mnn4G$2Uza=Q@o!VsqrdL;pse3MMrXo<}>z`(J=&a5MyNR1^-j1mdPI1u^rOzdtS7p;&?*dDLy{ zk0?%}4oF<`wnV>p%1VxoyDN$%Z{hKXP&bfwHnF3|XJ;;9tk_F|5*NxNg}KxTLuWHu zSse9Z)8dPii@GMig9q^70l7Jc4mxR89+4?*mOwx7*!x7wv!EgfIt4{H9>#9%E8XDQ zCL!C8$$WwHpZq#kc9{A_(Yw&JSfblM^yQ?`>0i~%F<75H3c=fF!dxt*GuR2KQiIv z6WCPzt*59#-AwrY*SkJokG@F@X+%Yz%f0kAnoB7?h`BTjQk#57d_r;deHN$c?I$}V z%!BXmc`>j`OH~;=bo0bkG*T%Y(v>r znSOhH^2N3|^LJs7F_q$bA*lF6+&#WlvZ@$(O3!JC51gV)yQ_vl#KMeSLa>oW&0Jr4_iHD<))QqDAa z&7-NEwVR>FcVd6Iw~f{2&sql^B4=MYnpWulYjSOj(sE{}ig}_oGPAZ~a!LM0>-{h*r?_uu_$@Pd!8QUsF<0~L z+PaE*`0w>onX5kIReqgMjEVkg(gxANQr?s|APd|(V7d%gJY6uBCK0&R{tX`t*T@v} p=-Hhq8X<&*!~XYc*0$repv5UadgtbXEAPrEfUtM9Yp^+&@F%{f4Z{Ec literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/icon-collision-group/default/style.json b/test/integration/render-tests/icon-collision-group/default/style.json new file mode 100644 index 00000000000..ca88c9c3371 --- /dev/null +++ b/test/integration/render-tests/icon-collision-group/default/style.json @@ -0,0 +1,128 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128, + "description": "Three collision groups of two layers each. Each group should show one label (overlapping with labels from other groups)" + } + }, + "center": [ + 0, + 0 + ], + "zoom": 0, + "sources": { + "point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name": "A" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "B" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1, + 0 + ] + } + } + ] + } + } + }, + "sprite": "local://sprites/sprite", + "layers": [ + { + "id": "defaultGroup1", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "building-12" + } + }, + { + "id": "defaultGroup2", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "night-building-12" + } + }, + { + "id": "firstGroup1", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "restaurant-12", + "icon-collision-group": "group1", + "icon-offset": [ + 7, + 7 + ] + } + }, + { + "id": "firstGroup2", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "night-restaurant-12", + "icon-collision-group": "group1", + "icon-offset": [ + 7, + 7 + ] + } + }, + { + "id": "secondGroup1", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "school-12", + "icon-collision-group": "group2", + "icon-offset": [ + 14, + 14 + ] + } + }, + { + "id": "secondGroup2", + "type": "symbol", + "source": "point", + "layout": { + "symbol-placement": "point", + "icon-image": "night-school-12", + "icon-collision-group": "group2", + "icon-offset": [ + 14, + 14 + ] + } + } + ] +} diff --git a/test/integration/render-tests/text-collision-group/default/expected.png b/test/integration/render-tests/text-collision-group/default/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a84431f4cd518464d084fe73f444f3912f1dc968 GIT binary patch literal 6433 zcmdU!hf|Z;yT=J7kPrfd5CViCRXPMIK{_O%2?S&zDoP3}3Q8iVSw#f`A&o!;k#&)! z>9UGS5D*nHAq^V~R#aR9L1ceez=EKxUi{rM_b<3JcjldE-e=yKGtZoJ-t+l>pTi|4 z#TbFDz*<^bMscyYR4pyQ>Z?|<0dO@=_gwk$K|2l?nZ^XXwmN!WOAGnzNA^bgGqd(n zvy1wmZ;hS-uC&-C6_-*{+Gl7F=DFN4la(HSXa(K6LJJ>*Mr)?a9Sozm^8K zNW;1JkzdfGd8I?CBB#8_C|H-zFTZ?M9sKRp;hDImrW>#Rda&|WhKX+E|Bq+oX7$I3 z^TvyclR2=wJvVQw{Ib8<3#zT?s*gVTE^3Z#!o>@0yL>s_~fH7~?CNpRdo@ zLzM8l1n^Jag22^;q}TK!>zeAGBcRN(xG^*9l&aK1xlaA;G2wxwFsQ78iP+NZ>nirU zO%F#$8zss1^l7gfZM*8|+j~hXNYr4H&@?pc8UZ*}v|rDDQ*d2WP2CBy!#A6`6yao_ z$xRi$zWJ(S*AUlf)cR39FVOE~6fo#J%7V6LJ*D-{SLjsw*5)Y^m?r*6PVRiz zQP%)q>w@3eJ16B%NY;gVkN3g-UK8j(Xj~>MMjocRP6cg{x~*86`5>#*$vLb7hW&a< z=vj?xHj#5UrE&|=q03^gB7VGdwGx{~)xi_p{&tG>Rda8f+%m1N>j)A8#_@6bdVKsc5XR}gkM+BjZrhb9v@T&9)J@*|OgZ|(Td z)OtZdCz_8ja2T}+Y&EE8Ugz9O$PR4a9u(nVM;HO@CYy5%EB0j3-TPgk7>MD#1&Xg3 z)p75!cYU5w%pXNOs7CDKv*vB?Mr>IDYcP-mAMLg3pEgA33FS4h%ZHAZZRy-L?Y072 zJGr;0WOBCT#4JbDE;p>_l=NABcHrhRLI*1KabIXQO0sM3n19a_d;*v3A>BVj^*}VC zlGWNFoP~O}jA1@w&-$5E_Ht-yO@EoF??uLCc*f-Rhm%ohYg(^vizSEka%Eu$PltBB)veWhCY4z*{0J0Y zdYfc%YsrUs6dImD1AHrm?4Czkjl@mII$~^ct7VyYBcDC_;B*{WCW_@2%6EjY&`2*| zB>R7fplUE#Y6tPK(M|l<>ru%@HCCvadao;K<*P zEH<4LuCHy8XJoKY_uHcgcX*=co-uleK!wryOg$*}bJiS5v#V%!Q9dBCP+>9%oYJ{9 zgTIr=hcqhUQG%pX31pAd`U>3{ntM`As{8T#U7$YLF@@tpu75MEl3F=r=XxKX-BV=U zd2je{7L-(cNaWS0_d7K-z|M8Ttk0@-VNDX=tT7_E5NGLZ=hC${qV{K%Ya3~vIT zCURtS{eAXMW1Ik&(XR7!P%4SPzHo5!_t0~qh@Il)KEpQ*qubNxrTdnzaXM=9J-fza z7jxz5VynEUgWb_*ijFW)&oto}5a=}*isTsgz+CTbEw;uOo2A%gL4zDI@MsKDG3(J~ z9#lJ&A!s|j&eQ<|xWvoke!!R;5_u3d*He<^qx)4ux~qB~+hS@xuhW4`@^Gk&ew^{d zIEG>~(|U90Reia$SV0oJ5?2+m7P3g?cfetDh%-SaZHO1GEiH0_QT&wx|x7i~V`w0GS*ErgP|+TEs` zN{-O2!XC!AO$QqXYHY@fdp4(+#bC65av_)wurfMzL6E_kp=QVgPJDV}*)SCZi2!fJ z05uB`7Q*ollilp zsO2xsq1y&??I$8EIxL>D9Ty`&UuYqVc`0sgeZbR1K4JM8+0TvPjNp8d<{~S9 zYbCf>!>oDNQ`nHbweLR1@~4UHuM5be!KzVc2HDAdOGl61QfSo zerYnlU1GWv(FVApxzdM@(deYL>O&>F9jm*JT%PwLIIybdZ~{4CM>D`yOiWOT6Vw~}1))9$G9t^ZNVvbgbl*p%vm+kN@IHqDg-aH&de

169EBD(AZj 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 b>7V=^dMRz*=01);Zb0JjNw|wq Date: Tue, 29 May 2018 15:14:59 -0400 Subject: [PATCH 4/5] Remove icon/text-collision-group from style spec. Re-implement basic collision group support based on a global "crossSourceCollisions" map option that replicates pre-#5150 behavior. Render tests maintain the same structure/results, but are now based on grouping-by-source. --- flow-typed/style-spec.js | 2 - src/data/bucket.js | 3 +- src/data/bucket/symbol_bucket.js | 3 + src/source/worker_tile.js | 3 +- src/style-spec/reference/v8.json | 26 ------ src/style/pauseable_placement.js | 7 +- src/style/style.js | 4 +- .../symbol_style_layer_properties.js | 4 - src/symbol/placement.js | 23 ++--- src/ui/map.js | 8 +- .../icon-collision-group/default/style.json | 55 ++++++++---- .../text-collision-group/default/style.json | 87 ++++++++++++++++--- test/suite_implementation.js | 3 +- test/unit/data/symbol_bucket.test.js | 2 +- 14 files changed, 147 insertions(+), 83 deletions(-) diff --git a/flow-typed/style-spec.js b/flow-typed/style-spec.js index 88439b72edb..df6e3260355 100644 --- a/flow-typed/style-spec.js +++ b/flow-typed/style-spec.js @@ -207,7 +207,6 @@ declare type SymbolLayerSpecification = {| "symbol-avoid-edges"?: PropertyValueSpecification, "icon-allow-overlap"?: PropertyValueSpecification, "icon-ignore-placement"?: PropertyValueSpecification, - "icon-collision-group"?: string, "icon-optional"?: PropertyValueSpecification, "icon-rotation-alignment"?: PropertyValueSpecification<"map" | "viewport" | "auto">, "icon-size"?: DataDrivenPropertyValueSpecification, @@ -238,7 +237,6 @@ declare type SymbolLayerSpecification = {| "text-offset"?: DataDrivenPropertyValueSpecification<[number, number]>, "text-allow-overlap"?: PropertyValueSpecification, "text-ignore-placement"?: PropertyValueSpecification, - "text-collision-group"?: string, "text-optional"?: PropertyValueSpecification, "visibility"?: "visible" | "none" |}, diff --git a/src/data/bucket.js b/src/data/bucket.js index 10d88250e46..d81f1f7a8bf 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -14,7 +14,8 @@ export type BucketParameters = { pixelRatio: number, overscaling: number, collisionBoxArray: CollisionBoxArray, - sourceLayerIndex: number + sourceLayerIndex: number, + sourceID: string } export type PopulateParameters = { diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 473c8348808..45965e348ff 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -293,6 +293,7 @@ class SymbolBucket implements Bucket { collisionCircle: CollisionBuffers; uploaded: boolean; sourceLayerIndex: number; + sourceID: string; constructor(options: BucketParameters) { this.collisionBoxArray = options.collisionBoxArray; @@ -313,6 +314,8 @@ class SymbolBucket implements Bucket { const layout = this.layers[0].layout; this.sortFeaturesByY = layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') || layout.get('text-ignore-placement') || layout.get('icon-ignore-placement'); + + this.sourceID = options.sourceID; } createArrays() { diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index ded6f677767..660cd31de47 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -109,7 +109,8 @@ class WorkerTile { pixelRatio: this.pixelRatio, overscaling: this.overscaling, collisionBoxArray: this.collisionBoxArray, - sourceLayerIndex: sourceLayerIndex + sourceLayerIndex: sourceLayerIndex, + sourceID: this.source }); bucket.populate(features, options); diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 3d8ea15ff7e..ce51ed31b3e 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -975,19 +975,6 @@ }, "property-type": "data-constant" }, - "icon-collision-group": { - "type": "string", - "doc": "Restricts collision detection to other symbols in the same group.", - "requires": [ - "icon-image" - ], - "sdk-support": { - "basic functionality": { - "js": "0.45.0" - }, - "data-driven styling": {} - } - }, "icon-optional": { "type": "boolean", "default": false, @@ -2014,19 +2001,6 @@ }, "property-type": "data-constant" }, - "text-collision-group": { - "type": "string", - "doc": "Restricts collision detection to other symbols in the same group.", - "requires": [ - "text-field" - ], - "sdk-support": { - "basic functionality": { - "js": "0.45.0" - }, - "data-driven styling": {} - } - }, "text-optional": { "type": "boolean", "default": false, diff --git a/src/style/pauseable_placement.js b/src/style/pauseable_placement.js index 9a7c8323602..2417e19c7c6 100644 --- a/src/style/pauseable_placement.js +++ b/src/style/pauseable_placement.js @@ -40,9 +40,12 @@ class PauseablePlacement { _inProgressLayer: ?LayerPlacement; constructor(transform: Transform, order: Array, - forceFullPlacement: boolean, showCollisionBoxes: boolean, fadeDuration: number) { + forceFullPlacement: boolean, + showCollisionBoxes: boolean, + fadeDuration: number, + crossSourceCollisions: boolean) { - this.placement = new Placement(transform, fadeDuration); + this.placement = new Placement(transform, fadeDuration, crossSourceCollisions); this._currentPlacementIndex = order.length - 1; this._forceFullPlacement = forceFullPlacement; this._showCollisionBoxes = showCollisionBoxes; diff --git a/src/style/style.js b/src/style/style.js index 38c93414912..bad95a0c2de 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -1004,7 +1004,7 @@ class Style extends Evented { } } - _updatePlacement(transform: Transform, showCollisionBoxes: boolean, fadeDuration: number) { + _updatePlacement(transform: Transform, showCollisionBoxes: boolean, fadeDuration: number, crossSourceCollisions: boolean) { let symbolBucketsChanged = false; let placementCommitted = false; @@ -1033,7 +1033,7 @@ class Style extends Evented { const forceFullPlacement = this._layerOrderChanged; if (forceFullPlacement || !this.pauseablePlacement || (this.pauseablePlacement.isDone() && !this.placement.stillRecent(browser.now()))) { - this.pauseablePlacement = new PauseablePlacement(transform, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration); + this.pauseablePlacement = new PauseablePlacement(transform, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions); this._layerOrderChanged = false; } diff --git a/src/style/style_layer/symbol_style_layer_properties.js b/src/style/style_layer/symbol_style_layer_properties.js index f0e03b611ce..2c00d943685 100644 --- a/src/style/style_layer/symbol_style_layer_properties.js +++ b/src/style/style_layer/symbol_style_layer_properties.js @@ -20,7 +20,6 @@ export type LayoutProps = {| "symbol-avoid-edges": DataConstantProperty, "icon-allow-overlap": DataConstantProperty, "icon-ignore-placement": DataConstantProperty, - "icon-collision-group": DataConstantProperty, "icon-optional": DataConstantProperty, "icon-rotation-alignment": DataConstantProperty<"map" | "viewport" | "auto">, "icon-size": DataDrivenProperty, @@ -51,7 +50,6 @@ export type LayoutProps = {| "text-offset": DataDrivenProperty<[number, number]>, "text-allow-overlap": DataConstantProperty, "text-ignore-placement": DataConstantProperty, - "text-collision-group": DataConstantProperty, "text-optional": DataConstantProperty, |}; @@ -61,7 +59,6 @@ const layout: Properties = new Properties({ "symbol-avoid-edges": new DataConstantProperty(styleSpec["layout_symbol"]["symbol-avoid-edges"]), "icon-allow-overlap": new DataConstantProperty(styleSpec["layout_symbol"]["icon-allow-overlap"]), "icon-ignore-placement": new DataConstantProperty(styleSpec["layout_symbol"]["icon-ignore-placement"]), - "icon-collision-group": new DataConstantProperty(styleSpec["layout_symbol"]["icon-collision-group"]), "icon-optional": new DataConstantProperty(styleSpec["layout_symbol"]["icon-optional"]), "icon-rotation-alignment": new DataConstantProperty(styleSpec["layout_symbol"]["icon-rotation-alignment"]), "icon-size": new DataDrivenProperty(styleSpec["layout_symbol"]["icon-size"]), @@ -92,7 +89,6 @@ const layout: Properties = new Properties({ "text-offset": new DataDrivenProperty(styleSpec["layout_symbol"]["text-offset"]), "text-allow-overlap": new DataConstantProperty(styleSpec["layout_symbol"]["text-allow-overlap"]), "text-ignore-placement": new DataConstantProperty(styleSpec["layout_symbol"]["text-ignore-placement"]), - "text-collision-group": new DataConstantProperty(styleSpec["layout_symbol"]["text-collision-group"]), "text-optional": new DataConstantProperty(styleSpec["layout_symbol"]["text-optional"]), }); diff --git a/src/symbol/placement.js b/src/symbol/placement.js index 0f4bb32dff6..0a3671d06d8 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -132,8 +132,9 @@ export class Placement { fadeDuration: number; retainedQueryData: {[number]: RetainedQueryData}; collisionGroups: CollisionGroups; + crossSourceCollisions: boolean; - constructor(transform: Transform, fadeDuration: number) { + constructor(transform: Transform, fadeDuration: number, crossSourceCollisions: boolean) { this.transform = transform.clone(); this.collisionIndex = new CollisionIndex(this.transform); this.placements = {}; @@ -142,6 +143,7 @@ export class Placement { this.fadeDuration = fadeDuration; this.retainedQueryData = {}; this.collisionGroups = new CollisionGroups(); + this.crossSourceCollisions = crossSourceCollisions; } placeLayerTile(styleLayer: StyleLayer, tile: Tile, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }) { @@ -195,10 +197,9 @@ export class Placement { const iconWithoutText = !bucket.hasTextData() || layout.get('text-optional'); const textWithoutIcon = !bucket.hasIconData() || layout.get('icon-optional'); - const textCollisionGroup = - this.collisionGroups.get(layout.get('text-collision-group')); - const iconCollisionGroup = - this.collisionGroups.get(layout.get('icon-collision-group')); + const collisionGroup = this.crossSourceCollisions ? + this.collisionGroups.get() : + this.collisionGroups.get(bucket.sourceID); for (const symbolInstance of bucket.symbolInstances) { if (!seenCrossTileIDs[symbolInstance.crossTileID]) { @@ -225,7 +226,7 @@ export class Placement { } if (symbolInstance.collisionArrays.textBox) { placedGlyphBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.textBox, - layout.get('text-allow-overlap'), textPixelRatio, posMatrix, textCollisionGroup.predicate); + layout.get('text-allow-overlap'), textPixelRatio, posMatrix, collisionGroup.predicate); placeText = placedGlyphBoxes.box.length > 0; offscreen = offscreen && placedGlyphBoxes.offscreen; } @@ -246,7 +247,7 @@ export class Placement { textLabelPlaneMatrix, showCollisionBoxes, layout.get('text-pitch-alignment') === 'map', - textCollisionGroup.predicate); + collisionGroup.predicate); // If text-allow-overlap is set, force "placedCircles" to true // In theory there should always be at least one circle placed // in this case, but for now quirks in text-anchor @@ -260,7 +261,7 @@ export class Placement { } if (symbolInstance.collisionArrays.iconBox) { placedIconBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.iconBox, - layout.get('icon-allow-overlap'), textPixelRatio, posMatrix, iconCollisionGroup.predicate); + layout.get('icon-allow-overlap'), textPixelRatio, posMatrix, collisionGroup.predicate); placeIcon = placedIconBoxes.box.length > 0; offscreen = offscreen && placedIconBoxes.offscreen; } @@ -276,15 +277,15 @@ export class Placement { if (placeText && placedGlyphBoxes) { this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), - bucket.bucketInstanceId, textFeatureIndex, textCollisionGroup.ID); + bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); } if (placeIcon && placedIconBoxes) { this.collisionIndex.insertCollisionBox(placedIconBoxes.box, layout.get('icon-ignore-placement'), - bucket.bucketInstanceId, iconFeatureIndex, iconCollisionGroup.ID); + bucket.bucketInstanceId, iconFeatureIndex, collisionGroup.ID); } if (placeText && placedGlyphCircles) { this.collisionIndex.insertCollisionCircles(placedGlyphCircles.circles, layout.get('text-ignore-placement'), - bucket.bucketInstanceId, textFeatureIndex, textCollisionGroup.ID); + bucket.bucketInstanceId, textFeatureIndex, collisionGroup.ID); } assert(symbolInstance.crossTileID !== 0); diff --git a/src/ui/map.js b/src/ui/map.js index 9571fb28b99..7ac4adbc8ba 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -123,7 +123,8 @@ const defaultOptions = { maxTileCacheSize: null, transformRequest: null, - fadeDuration: 300 + fadeDuration: 300, + crossSourceCollisions: true }; /** @@ -194,6 +195,7 @@ const defaultOptions = { * Expected to return an object with a `url` property and optionally `headers` and `credentials` properties. * @param {boolean} [options.collectResourceTiming=false] If `true`, Resource Timing API information will be collected for requests made by GeoJSON and Vector Tile web workers (this information is normally inaccessible from the main Javascript thread). Information will be returned in a `resourceTiming` property of relevant `data` events. * @param {number} [options.fadeDuration=300] Controls the duration of the fade-in/fade-out animation for label collisions, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading. + * @param {boolean} [options.crossSourceCollisions=true] If `true`, symbols from multiple sources can collide with each other during collision detection. If `false`, collision detection is run separately for the symbols in each source. * @example * var map = new mapboxgl.Map({ * container: 'map', @@ -243,6 +245,7 @@ class Map extends Camera { _hash: Hash; _delegatedListeners: any; _fadeDuration: number; + _crossSourceCollisions: boolean; _crossFadingFactor: number; _collectResourceTiming: boolean; _renderTaskQueue: TaskQueue; @@ -302,6 +305,7 @@ class Map extends Camera { this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; this._fadeDuration = options.fadeDuration; + this._crossSourceCollisions = options.crossSourceCollisions; this._crossFadingFactor = 1; this._collectResourceTiming = options.collectResourceTiming; this._renderTaskQueue = new TaskQueue(); @@ -1636,7 +1640,7 @@ class Map extends Camera { this.style._updateSources(this.transform); } - this._placementDirty = this.style && this.style._updatePlacement(this.painter.transform, this.showCollisionBoxes, this._fadeDuration); + this._placementDirty = this.style && this.style._updatePlacement(this.painter.transform, this.showCollisionBoxes, this._fadeDuration, this._crossSourceCollisions); // Actually draw this.painter.render(this.style, { diff --git a/test/integration/render-tests/icon-collision-group/default/style.json b/test/integration/render-tests/icon-collision-group/default/style.json index ca88c9c3371..adf78059c81 100644 --- a/test/integration/render-tests/icon-collision-group/default/style.json +++ b/test/integration/render-tests/icon-collision-group/default/style.json @@ -2,6 +2,7 @@ "version": 8, "metadata": { "test": { + "crossSourceCollisions": false, "height": 128, "width": 128, "description": "Three collision groups of two layers each. Each group should show one label (overlapping with labels from other groups)" @@ -13,16 +14,13 @@ ], "zoom": 0, "sources": { - "point": { + "source1": { "type": "geojson", "data": { "type": "FeatureCollection", "features": [ { "type": "Feature", - "properties": { - "name": "A" - }, "geometry": { "type": "Point", "coordinates": [ @@ -30,16 +28,39 @@ 0 ] } - }, + } + ] + } + }, + "source2": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + ] + } + }, + "source3": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ { "type": "Feature", - "properties": { - "name": "B" - }, "geometry": { "type": "Point", "coordinates": [ - 1, + 0, 0 ] } @@ -53,7 +74,7 @@ { "id": "defaultGroup1", "type": "symbol", - "source": "point", + "source": "source1", "layout": { "symbol-placement": "point", "icon-image": "building-12" @@ -62,7 +83,7 @@ { "id": "defaultGroup2", "type": "symbol", - "source": "point", + "source": "source1", "layout": { "symbol-placement": "point", "icon-image": "night-building-12" @@ -71,11 +92,10 @@ { "id": "firstGroup1", "type": "symbol", - "source": "point", + "source": "source2", "layout": { "symbol-placement": "point", "icon-image": "restaurant-12", - "icon-collision-group": "group1", "icon-offset": [ 7, 7 @@ -85,11 +105,10 @@ { "id": "firstGroup2", "type": "symbol", - "source": "point", + "source": "source2", "layout": { "symbol-placement": "point", "icon-image": "night-restaurant-12", - "icon-collision-group": "group1", "icon-offset": [ 7, 7 @@ -99,11 +118,10 @@ { "id": "secondGroup1", "type": "symbol", - "source": "point", + "source": "source3", "layout": { "symbol-placement": "point", "icon-image": "school-12", - "icon-collision-group": "group2", "icon-offset": [ 14, 14 @@ -113,11 +131,10 @@ { "id": "secondGroup2", "type": "symbol", - "source": "point", + "source": "source3", "layout": { "symbol-placement": "point", "icon-image": "night-school-12", - "icon-collision-group": "group2", "icon-offset": [ 14, 14 diff --git a/test/integration/render-tests/text-collision-group/default/style.json b/test/integration/render-tests/text-collision-group/default/style.json index 4630ecf9e56..8415c3258ac 100644 --- a/test/integration/render-tests/text-collision-group/default/style.json +++ b/test/integration/render-tests/text-collision-group/default/style.json @@ -2,6 +2,7 @@ "version": 8, "metadata": { "test": { + "crossSourceCollisions": false, "height": 128, "width": 256, "description": "Three collision groups of two layers each. Each group should show one label (overlapping with labels from other groups)" @@ -13,7 +14,75 @@ ], "zoom": 0, "sources": { - "point": { + "source1": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name": "A" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -20, + 0 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "B" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 20, + 0 + ] + } + } + ] + } + }, + "source2": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name": "A" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -20, + 0 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "B" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 20, + 0 + ] + } + } + ] + } + }, + "source3": { "type": "geojson", "data": { "type": "FeatureCollection", @@ -53,7 +122,7 @@ { "id": "defaultGroup1", "type": "symbol", - "source": "point", + "source": "source1", "layout": { "text-field": "Default Group {name}", "text-max-width": 30, @@ -66,7 +135,7 @@ { "id": "defaultGroup2", "type": "symbol", - "source": "point", + "source": "source1", "layout": { "text-field": "2nd Layer Default Group {name}", "text-max-width": 30, @@ -79,7 +148,7 @@ { "id": "firstGroup1", "type": "symbol", - "source": "point", + "source": "source2", "layout": { "text-field": "First Group {name}", "text-max-width": 30, @@ -87,7 +156,6 @@ "Open Sans Semibold", "Arial Unicode MS Bold" ], - "text-collision-group": "group1", "text-offset": [ 0, 0.5 @@ -97,7 +165,7 @@ { "id": "firstGroup2", "type": "symbol", - "source": "point", + "source": "source2", "layout": { "text-field": "2nd Layer First Group {name}", "text-max-width": 30, @@ -105,7 +173,6 @@ "Open Sans Semibold", "Arial Unicode MS Bold" ], - "text-collision-group": "group1", "text-offset": [ 0, 0.5 @@ -115,7 +182,7 @@ { "id": "secondGroup1", "type": "symbol", - "source": "point", + "source": "source3", "layout": { "text-field": "Second Group {name}", "text-max-width": 30, @@ -123,7 +190,6 @@ "Open Sans Semibold", "Arial Unicode MS Bold" ], - "text-collision-group": "group2", "text-offset": [ 0, 1 @@ -133,7 +199,7 @@ { "id": "secondGroup2", "type": "symbol", - "source": "point", + "source": "source3", "layout": { "text-field": "2nd Layer Second Group {name}", "text-max-width": 30, @@ -141,7 +207,6 @@ "Open Sans Semibold", "Arial Unicode MS Bold" ], - "text-collision-group": "group2", "text-offset": [ 0, 1 diff --git a/test/suite_implementation.js b/test/suite_implementation.js index 2c4cd91fb02..aca3e7354e4 100644 --- a/test/suite_implementation.js +++ b/test/suite_implementation.js @@ -44,7 +44,8 @@ module.exports = function(style, options, _callback) { // eslint-disable-line im preserveDrawingBuffer: true, axonometric: options.axonometric || false, skew: options.skew || [0, 0], - fadeDuration: options.fadeDuration || 0 + fadeDuration: options.fadeDuration || 0, + crossSourceCollisions: typeof options.crossSourceCollisions === "undefined" ? true : options.crossSourceCollisions }); // Configure the map to never stop the render loop diff --git a/test/unit/data/symbol_bucket.test.js b/test/unit/data/symbol_bucket.test.js index 3ec8b6b583a..6ba28cb31e5 100644 --- a/test/unit/data/symbol_bucket.test.js +++ b/test/unit/data/symbol_bucket.test.js @@ -50,7 +50,7 @@ test('SymbolBucket', (t) => { const bucketA = bucketSetup(); const bucketB = bucketSetup(); const options = {iconDependencies: {}, glyphDependencies: {}}; - const placement = new Placement(transform, 0); + const placement = new Placement(transform, 0, true); const tileID = new OverscaledTileID(0, 0, 0, 0, 0); const crossTileSymbolIndex = new CrossTileSymbolIndex(); From e9dd1b9756b51845dcd90cb36c630c585e6b2676 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Tue, 29 May 2018 16:30:05 -0400 Subject: [PATCH 5/5] "collision group" -> "cross source collisions" conversion: - Rename tests - Remove dead code from CollisionGroups, update parameter names - Simplify return pathways for _query --- src/symbol/collision_index.js | 8 +-- src/symbol/grid_index.js | 6 +- src/symbol/placement.js | 56 +++++++----------- .../default/expected.png | Bin .../default/style.json | 12 ++-- .../text-collision-group/default/expected.png | Bin 6433 -> 0 bytes .../default/expected.png | Bin 0 -> 6614 bytes .../default/style.json | 24 ++++---- 8 files changed, 47 insertions(+), 59 deletions(-) rename test/integration/render-tests/{icon-collision-group => icon-no-cross-source-collision}/default/expected.png (100%) rename test/integration/render-tests/{icon-collision-group => icon-no-cross-source-collision}/default/style.json (94%) delete mode 100644 test/integration/render-tests/text-collision-group/default/expected.png create mode 100644 test/integration/render-tests/text-no-cross-source-collision/default/expected.png rename test/integration/render-tests/{text-collision-group => text-no-cross-source-collision}/default/style.json (91%) diff --git a/src/symbol/collision_index.js b/src/symbol/collision_index.js index c1ea7aaf8eb..587f5a27a94 100644 --- a/src/symbol/collision_index.js +++ b/src/symbol/collision_index.js @@ -297,17 +297,17 @@ class CollisionIndex { return result; } - insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroup: number) { + insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroupID: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; - const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroup: collisionGroup }; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroupID: collisionGroupID }; grid.insert(key, collisionBox[0], collisionBox[1], collisionBox[2], collisionBox[3]); } - insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroup: number) { + insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number, collisionGroupID: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; - const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroup: collisionGroup }; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex, collisionGroupID: collisionGroupID }; for (let k = 0; k < collisionCircles.length; k += 4) { grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]); } diff --git a/src/symbol/grid_index.js b/src/symbol/grid_index.js index 3f2e21a9964..174d71fa0dd 100644 --- a/src/symbol/grid_index.js +++ b/src/symbol/grid_index.js @@ -117,17 +117,15 @@ class GridIndex { y2: y + radius }); } - if (predicate) { - return result.filter(predicate); - } + return predicate ? result.filter(predicate) : result; } else { const queryArgs = { hitTest, seenUids: { box: {}, circle: {} } }; this._forEachCell(x1, y1, x2, y2, this._queryCell, result, queryArgs, predicate); + return hitTest ? result.length > 0 : result; } - return hitTest ? result.length > 0 : result; } _queryCircle(x: number, y: number, radius: number, hitTest: boolean, predicate?: any) { diff --git a/src/symbol/placement.js b/src/symbol/placement.js index 0a3671d06d8..76ccba9de80 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -83,40 +83,34 @@ export class RetainedQueryData { } class CollisionGroups { - collisionGroups: { [layerId: ?string]: { ID: number, predicate?: any }}; + collisionGroups: { [groupName: string]: { ID: number, predicate?: any }}; maxGroupID: number; + crossSourceCollisions: boolean; - constructor() { + constructor(crossSourceCollisions: boolean) { + this.crossSourceCollisions = crossSourceCollisions; this.maxGroupID = 0; - this.collisionGroups = { - undefined: { - ID: 0, - predicate: null - } - }; + this.collisionGroups = {}; } - get(groupName?: string) { - if (groupName && this.maxGroupID === 0) { - // Keep the predicate null until the first collision - // group gets added to avoid overhead in the case - // everything's in the default group. - this.collisionGroups[undefined].predicate = - (key) => { - return key.collisionGroup === 0; + get(sourceID: string) { + // The predicate/groupID mechanism allows for arbitrary grouping, + // but the current interface defines one source == one group when + // crossSourceCollisions == true. + if (!this.crossSourceCollisions) { + if (!this.collisionGroups[sourceID]) { + const nextGroupID = ++this.maxGroupID; + this.collisionGroups[sourceID] = { + ID: nextGroupID, + predicate: (key) => { + return key.collisionGroupID === nextGroupID; + } }; + } + return this.collisionGroups[sourceID]; + } else { + return { ID: 0, predicate: null }; } - - if (!this.collisionGroups[groupName]) { - const nextGroupID = ++this.maxGroupID; - this.collisionGroups[groupName] = { - ID: nextGroupID, - predicate: (key) => { - return key.collisionGroup === nextGroupID; - } - }; - } - return this.collisionGroups[groupName]; } } @@ -132,7 +126,6 @@ export class Placement { fadeDuration: number; retainedQueryData: {[number]: RetainedQueryData}; collisionGroups: CollisionGroups; - crossSourceCollisions: boolean; constructor(transform: Transform, fadeDuration: number, crossSourceCollisions: boolean) { this.transform = transform.clone(); @@ -142,8 +135,7 @@ export class Placement { this.stale = false; this.fadeDuration = fadeDuration; this.retainedQueryData = {}; - this.collisionGroups = new CollisionGroups(); - this.crossSourceCollisions = crossSourceCollisions; + this.collisionGroups = new CollisionGroups(crossSourceCollisions); } placeLayerTile(styleLayer: StyleLayer, tile: Tile, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }) { @@ -197,9 +189,7 @@ export class Placement { const iconWithoutText = !bucket.hasTextData() || layout.get('text-optional'); const textWithoutIcon = !bucket.hasIconData() || layout.get('icon-optional'); - const collisionGroup = this.crossSourceCollisions ? - this.collisionGroups.get() : - this.collisionGroups.get(bucket.sourceID); + const collisionGroup = this.collisionGroups.get(bucket.sourceID); for (const symbolInstance of bucket.symbolInstances) { if (!seenCrossTileIDs[symbolInstance.crossTileID]) { diff --git a/test/integration/render-tests/icon-collision-group/default/expected.png b/test/integration/render-tests/icon-no-cross-source-collision/default/expected.png similarity index 100% rename from test/integration/render-tests/icon-collision-group/default/expected.png rename to test/integration/render-tests/icon-no-cross-source-collision/default/expected.png diff --git a/test/integration/render-tests/icon-collision-group/default/style.json b/test/integration/render-tests/icon-no-cross-source-collision/default/style.json similarity index 94% rename from test/integration/render-tests/icon-collision-group/default/style.json rename to test/integration/render-tests/icon-no-cross-source-collision/default/style.json index adf78059c81..b88ab61d71b 100644 --- a/test/integration/render-tests/icon-collision-group/default/style.json +++ b/test/integration/render-tests/icon-no-cross-source-collision/default/style.json @@ -72,7 +72,7 @@ "sprite": "local://sprites/sprite", "layers": [ { - "id": "defaultGroup1", + "id": "source1Group1", "type": "symbol", "source": "source1", "layout": { @@ -81,7 +81,7 @@ } }, { - "id": "defaultGroup2", + "id": "source1Group2", "type": "symbol", "source": "source1", "layout": { @@ -90,7 +90,7 @@ } }, { - "id": "firstGroup1", + "id": "source2Group1", "type": "symbol", "source": "source2", "layout": { @@ -103,7 +103,7 @@ } }, { - "id": "firstGroup2", + "id": "source2Group2", "type": "symbol", "source": "source2", "layout": { @@ -116,7 +116,7 @@ } }, { - "id": "secondGroup1", + "id": "source3Group1", "type": "symbol", "source": "source3", "layout": { @@ -129,7 +129,7 @@ } }, { - "id": "secondGroup2", + "id": "source3Group2", "type": "symbol", "source": "source3", "layout": { diff --git a/test/integration/render-tests/text-collision-group/default/expected.png b/test/integration/render-tests/text-collision-group/default/expected.png deleted file mode 100644 index a84431f4cd518464d084fe73f444f3912f1dc968..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6433 zcmdU!hf|Z;yT=J7kPrfd5CViCRXPMIK{_O%2?S&zDoP3}3Q8iVSw#f`A&o!;k#&)! z>9UGS5D*nHAq^V~R#aR9L1ceez=EKxUi{rM_b<3JcjldE-e=yKGtZoJ-t+l>pTi|4 z#TbFDz*<^bMscyYR4pyQ>Z?|<0dO@=_gwk$K|2l?nZ^XXwmN!WOAGnzNA^bgGqd(n zvy1wmZ;hS-uC&-C6_-*{+Gl7F=DFN4la(HSXa(K6LJJ>*Mr)?a9Sozm^8K zNW;1JkzdfGd8I?CBB#8_C|H-zFTZ?M9sKRp;hDImrW>#Rda&|WhKX+E|Bq+oX7$I3 z^TvyclR2=wJvVQw{Ib8<3#zT?s*gVTE^3Z#!o>@0yL>s_~fH7~?CNpRdo@ zLzM8l1n^Jag22^;q}TK!>zeAGBcRN(xG^*9l&aK1xlaA;G2wxwFsQ78iP+NZ>nirU zO%F#$8zss1^l7gfZM*8|+j~hXNYr4H&@?pc8UZ*}v|rDDQ*d2WP2CBy!#A6`6yao_ z$xRi$zWJ(S*AUlf)cR39FVOE~6fo#J%7V6LJ*D-{SLjsw*5)Y^m?r*6PVRiz zQP%)q>w@3eJ16B%NY;gVkN3g-UK8j(Xj~>MMjocRP6cg{x~*86`5>#*$vLb7hW&a< z=vj?xHj#5UrE&|=q03^gB7VGdwGx{~)xi_p{&tG>Rda8f+%m1N>j)A8#_@6bdVKsc5XR}gkM+BjZrhb9v@T&9)J@*|OgZ|(Td z)OtZdCz_8ja2T}+Y&EE8Ugz9O$PR4a9u(nVM;HO@CYy5%EB0j3-TPgk7>MD#1&Xg3 z)p75!cYU5w%pXNOs7CDKv*vB?Mr>IDYcP-mAMLg3pEgA33FS4h%ZHAZZRy-L?Y072 zJGr;0WOBCT#4JbDE;p>_l=NABcHrhRLI*1KabIXQO0sM3n19a_d;*v3A>BVj^*}VC zlGWNFoP~O}jA1@w&-$5E_Ht-yO@EoF??uLCc*f-Rhm%ohYg(^vizSEka%Eu$PltBB)veWhCY4z*{0J0Y zdYfc%YsrUs6dImD1AHrm?4Czkjl@mII$~^ct7VyYBcDC_;B*{WCW_@2%6EjY&`2*| zB>R7fplUE#Y6tPK(M|l<>ru%@HCCvadao;K<*P zEH<4LuCHy8XJoKY_uHcgcX*=co-uleK!wryOg$*}bJiS5v#V%!Q9dBCP+>9%oYJ{9 zgTIr=hcqhUQG%pX31pAd`U>3{ntM`As{8T#U7$YLF@@tpu75MEl3F=r=XxKX-BV=U zd2je{7L-(cNaWS0_d7K-z|M8Ttk0@-VNDX=tT7_E5NGLZ=hC${qV{K%Ya3~vIT zCURtS{eAXMW1Ik&(XR7!P%4SPzHo5!_t0~qh@Il)KEpQ*qubNxrTdnzaXM=9J-fza z7jxz5VynEUgWb_*ijFW)&oto}5a=}*isTsgz+CTbEw;uOo2A%gL4zDI@MsKDG3(J~ z9#lJ&A!s|j&eQ<|xWvoke!!R;5_u3d*He<^qx)4ux~qB~+hS@xuhW4`@^Gk&ew^{d zIEG>~(|U90Reia$SV0oJ5?2+m7P3g?cfetDh%-SaZHO1GEiH0_QT&wx|x7i~V`w0GS*ErgP|+TEs` zN{-O2!XC!AO$QqXYHY@fdp4(+#bC65av_)wurfMzL6E_kp=QVgPJDV}*)SCZi2!fJ z05uB`7Q*ollilp zsO2xsq1y&??I$8EIxL>D9Ty`&UuYqVc`0sgeZbR1K4JM8+0TvPjNp8d<{~S9 zYbCf>!>oDNQ`nHbweLR1@~4UHuM5be!KzVc2HDAdOGl61QfSo zerYnlU1GWv(FVApxzdM@(deYL>O&>F9jm*JT%PwLIIybdZ~{4CM>D`yOiWOT6Vw~}1))9$G9t^ZNVvbgbl*p%vm+kN@IHqDg-aH&de

169EBD(AZj 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�yA;)@=<>;hwUr{h%8WOaO#6TReubd(XMtVEWA(h@j1k-EIG1GW|t0JVN?GKJbWS zcY5k$V@0}Tn3YlIp{Yn8?Wgw*>QI$@lL>r`kqKwS zM$M=o0evKcCCjF{E%1#**m*`(Qe$-qrR9HbmiQf96lfgzYT)ur_1mKJNpY_y4DQ(ULD?I|F3Rp zCtw}Hp+;MFKQGSwnfiJg^kwh+l7-mZdvT~+@`2L?%ge@KluzPQiZVfR@22NCr*oV$4XbGLUd!``=; zwf#T&tDXfv5V`ab^0!9&0OXS-Ys3u%P|XySu$piRFa*`m?# zhT5N6P2C7^KL<64068#b`D)N_h4?opFKQEL+Vu&aWN|*Hm}{@MIYcJ87#Z0yc(PG+ zXzA*xZ*R86lWgb{CiKZVg2#+!nqN;I8F;B)#Y5C0vO|&?RUzp~f^)2Y12W0wlsdd% zguada*E=f$%EIyYn1&YA{oexO9s6ZgMILkC9Oo<$e;A9YGmz|FkxlzwyPO`3!&FTx;c2zEYAolzk@9TOI$Qr?TjjvY zTKfZX+9qC2+H|(%Xs<~|7 zeBMw=@94+y-8#=@&_)Hy2NHw+oa^K{br=#K92o2BG_=_$bi_t}7y;;3fAT&3g>)AQ z{$Ij~d;RUe$A7c$C7s_tk4g4!^lC_y`u2pa^Sd&3aF>eIBnt^*@Ymokcf{T*xvPi1sLhwh+Rcv)87=xdvQEUtwAnMq>|81pZ&Zo%*5A>5I^g2m?E`!)K(`i))%;x5h=d8%NrZ zYgl{|$e5&Gn~|1k+e&ZA>#vv|qA_hY7|Rk3Ey34CXJxqtcwk&=zgv>bSQ?em)^4Cn zC3R0tOOuS*7FoIojYpT%ew_BxL?g284!5BnYJDEsU+x(1k6oaqmDJug*5T}GT5Z-T z09bj^LUiujuxH4#^}{nY$sujF&&S-8D~m#!H-FwhFd=Izai5MfhXpa}{ExJn9wJ$7 z)Kg0wUyfP5Z%h6BT^nY{wJQ!D9TZKB@8rZd;HqI);?UK~2jUa-iuIGRIe^a|(^s#o zo~L9rm48|~>#`~;>5i(!FP&&#*PNpN>S1+?wz6~LfS0gg;zOr7ywYXW|73%yznGr= Wwbj13KPKm|-!=*4&BlGlto%O_#T~u? literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/text-collision-group/default/style.json b/test/integration/render-tests/text-no-cross-source-collision/default/style.json similarity index 91% rename from test/integration/render-tests/text-collision-group/default/style.json rename to test/integration/render-tests/text-no-cross-source-collision/default/style.json index 8415c3258ac..2a3db9fd462 100644 --- a/test/integration/render-tests/text-collision-group/default/style.json +++ b/test/integration/render-tests/text-no-cross-source-collision/default/style.json @@ -120,11 +120,11 @@ "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "layers": [ { - "id": "defaultGroup1", + "id": "source1Group1", "type": "symbol", "source": "source1", "layout": { - "text-field": "Default Group {name}", + "text-field": "Source1 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold", @@ -133,11 +133,11 @@ } }, { - "id": "defaultGroup2", + "id": "source1Group2", "type": "symbol", "source": "source1", "layout": { - "text-field": "2nd Layer Default Group {name}", + "text-field": "2nd Layer Source1 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold", @@ -146,11 +146,11 @@ } }, { - "id": "firstGroup1", + "id": "source2Group1", "type": "symbol", "source": "source2", "layout": { - "text-field": "First Group {name}", + "text-field": "Source2 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold", @@ -163,11 +163,11 @@ } }, { - "id": "firstGroup2", + "id": "source2Group2", "type": "symbol", "source": "source2", "layout": { - "text-field": "2nd Layer First Group {name}", + "text-field": "2nd Layer Source2 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold", @@ -180,11 +180,11 @@ } }, { - "id": "secondGroup1", + "id": "source3Group1", "type": "symbol", "source": "source3", "layout": { - "text-field": "Second Group {name}", + "text-field": "Source3 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold", @@ -197,11 +197,11 @@ } }, { - "id": "secondGroup2", + "id": "ssource3Group2", "type": "symbol", "source": "source3", "layout": { - "text-field": "2nd Layer Second Group {name}", + "text-field": "2nd Layer Source3 Group {name}", "text-max-width": 30, "text-font": [ "Open Sans Semibold",