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

Fix: #11516, #11041. Terrain: buildings on tile borders. #11530

Merged
merged 2 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type BucketParameters<Layer: TypedStyleLayer> = {
index: number,
layers: Array<Layer>,
zoom: number,
canonical: CanonicalTileID,
pixelRatio: number,
overscaling: number,
collisionBoxArray: CollisionBoxArray,
Expand Down
19 changes: 6 additions & 13 deletions src/data/bucket/fill_extrusion_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,32 +95,23 @@ class PartMetadata {
this.currentPolyCount.edges++;

this.acc._add(p);
let checkBorders = !!this.borders;

const min = this.min, max = this.max;
if (p.x < min.x) {
min.x = p.x;
checkBorders = true;
} else if (p.x > max.x) {
max.x = p.x;
checkBorders = true;
}
if (p.y < min.y) {
min.y = p.y;
checkBorders = true;
} else if (p.y > max.y) {
max.y = p.y;
checkBorders = true;
}
if (((p.x === 0 || p.x === EXTENT) && p.x === prev.x) !== ((p.y === 0 || p.y === EXTENT) && p.y === prev.y)) {
// Custom defined geojson buildings are cut on borders. Points are
// repeated when edge cuts tile corner (reason for using xor).
this.processBorderOverlap(p, prev);
}
if (checkBorders) this.checkBorderIntersection(p, prev);
}

checkBorderIntersection(p: Point, prev: Point) {
// check border intersection
if ((prev.x < 0) !== (p.x < 0)) {
this.addBorderIntersection(0, interpolate(prev.y, p.y, (0 - prev.x) / (p.x - prev.x)));
}
Expand Down Expand Up @@ -180,6 +171,7 @@ class PartMetadata {
class FillExtrusionBucket implements Bucket {
index: number;
zoom: number;
canonical: CanonicalTileID;
overscaling: number;
enableTerrain: boolean;
layers: Array<FillExtrusionStyleLayer>;
Expand All @@ -206,15 +198,16 @@ class FillExtrusionBucket implements Bucket {
features: Array<BucketFeature>;

featuresOnBorder: Array<PartMetadata>;
// borders / borderDone: 0 - left, 1, right, 2 - top, 3 - bottom
// borders / borderDoneWithNeighborZ: 0 - left, 1, right, 2 - top, 3 - bottom
borders: Array<Array<number>>; // For each side, indices into featuresOnBorder array.
borderDone: Array<boolean>;
borderDoneWithNeighborZ: Array<number>;
needsCentroidUpdate: boolean;
tileToMeter: number; // cache conversion.
projection: string;

constructor(options: BucketParameters<FillExtrusionStyleLayer>) {
this.zoom = options.zoom;
this.canonical = options.canonical;
this.overscaling = options.overscaling;
this.layers = options.layers;
this.layerIds = this.layers.map(layer => layer.id);
Expand All @@ -236,7 +229,7 @@ class FillExtrusionBucket implements Bucket {
this.hasPattern = hasPattern('fill-extrusion', this.layers, options);
this.featuresOnBorder = [];
this.borders = [[], [], [], []];
this.borderDone = [false, false, false, false];
this.borderDoneWithNeighborZ = [-1, -1, -1, -1];
this.tileToMeter = tileToMeter(canonical);

for (const {feature, id, index, sourceLayerIndex} of features) {
Expand Down
70 changes: 50 additions & 20 deletions src/render/draw_fill_extrusion.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,23 +180,26 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {
];

const getLoadedBucket = (nid) => {
const maxzoom = source.getSource().maxzoom;
const minzoom = source.getSource().minzoom;
const getBucket = (key) => {
const n = source.getTileByID(key);
if (n && n.hasData()) {
return n.getBucket(layer);
}
};
// In overscale range, we look one tile zoom above and under. We do this to avoid
// flickering and use the content in Z-1 and Z+1 buckets until Z bucket is loaded.
let b0, b1, b2;
if (nid.overscaledZ === nid.canonical.z || nid.overscaledZ >= maxzoom)
b0 = getBucket(nid.key);
if (nid.overscaledZ >= maxzoom)
b1 = getBucket(nid.calculateScaledKey(nid.overscaledZ + 1));
if (nid.overscaledZ > maxzoom)
b2 = getBucket(nid.calculateScaledKey(nid.overscaledZ - 1));
return b0 || b1 || b2;
// Look one tile zoom above and under. We do this to avoid flickering and
// use the content in Z-1 and Z+1 buckets until Z bucket is loaded or handle
// behavior on borders between different zooms.
const zoomLevels = [0, -1, 1];
for (const i of zoomLevels) {
const z = nid.overscaledZ + i;
if (z < minzoom) continue;
const key = nid.calculateScaledKey(nid.overscaledZ + i);
const b = getBucket(key);
if (b) {
return b;
}
}
};

const projectedToBorder = [0, 0, 0]; // [min, max, maxOffsetFromBorder]
Expand Down Expand Up @@ -241,13 +244,19 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {

// Process all four borders: get neighboring tile
for (let i = 0; i < 4; i++) {
// borders / borderDoneWithNeighborZ: 0 - left, 1, right, 2 - top, 3 - bottom
// bucket's border i is neighboring bucket's border j:
const j = (i < 2 ? 1 : 5) - i;
// Sort by border intersection area minimums, ascending.
const a = bucket.borders[i];
if (a.length === 0) { bucket.borderDone[i] = true; }
if (bucket.borderDone[i]) continue;
if (a.length === 0) continue;
const nid = neighborTileID = neighborCoord[i](coord);
const nBucket = getLoadedBucket(nid);
if (!nBucket || !(nBucket instanceof FillExtrusionBucket) || !nBucket.enableTerrain) continue;
if (bucket.borderDoneWithNeighborZ[i] === nBucket.canonical.z &&
nBucket.borderDoneWithNeighborZ[j] === bucket.canonical.z) {
continue;
}

neighborDEMTile = terrain.findDEMTileFor(nid);
if (!neighborDEMTile || !neighborDEMTile.dem) continue;
Expand All @@ -256,9 +265,28 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {
if (!(dem && dem.dem)) return; // defer update until an elevation tile is available.
demTile = dem;
}
const j = (i < 2 ? 1 : 5) - i;
const b = nBucket.borders[j];
let ib = 0;

const updateNeighbor = nBucket.borderDoneWithNeighborZ[j] !== bucket.canonical.z;
// If neighbors are of different canonical z, we cannot join parts but show
// all without flat roofs.
if (bucket.canonical.z !== nBucket.canonical.z) {
for (const index of a) {
bucket.encodeCentroid(undefined, bucket.featuresOnBorder[index], false);
}
if (updateNeighbor) {
for (const index of b) {
nBucket.encodeCentroid(undefined, nBucket.featuresOnBorder[index], false);
}
nBucket.borderDoneWithNeighborZ[j] = bucket.canonical.z;
nBucket.needsCentroidUpdate = true;
}
bucket.borderDoneWithNeighborZ[i] = nBucket.canonical.z;
bucket.needsCentroidUpdate = true;
continue;
}

for (let ia = 0; ia < a.length; ia++) {
const parta = bucket.featuresOnBorder[a[ia]];
const partABorderRange = parta.borders[i];
Expand All @@ -269,7 +297,7 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {
partb = nBucket.featuresOnBorder[b[ib]];
const partBBorderRange = partb.borders[j];
if (partBBorderRange[1] > partABorderRange[0] + error) break;
if (!nBucket.borderDone[j]) nBucket.encodeCentroid(undefined, partb, false);
if (updateNeighbor) nBucket.encodeCentroid(undefined, partb, false);
ib++;
}
if (partb && ib < b.length) {
Expand All @@ -292,7 +320,7 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {
}

bucket.encodeCentroid(undefined, parta, false);
if (!nBucket.borderDone[j]) nBucket.encodeCentroid(undefined, partb, false);
if (updateNeighbor) nBucket.encodeCentroid(undefined, partb, false);
continue;
}

Expand All @@ -307,16 +335,18 @@ function flatRoofsUpdate(context, source, coord, bucket, layer, terrain) {
bucket.encodeCentroid(centroid, parta, false);

assert(partb.vertexArrayOffset !== undefined && partb.vertexArrayOffset < nBucket.layoutVertexArray.length);
if (!nBucket.borderDone[j]) nBucket.encodeCentroid(centroid, partb, false);
if (updateNeighbor) nBucket.encodeCentroid(centroid, partb, false);
} else {
assert(parta.intersectsCount() > 1 || (partb && partb.intersectsCount() > 1)); // expected at the end of border, when buildings cover corner (show building w/o flat roof).
bucket.encodeCentroid(undefined, parta, false);
}
}

bucket.borderDone[i] = bucket.needsCentroidUpdate = true;
if (!nBucket.borderDone[j]) {
nBucket.borderDone[j] = nBucket.needsCentroidUpdate = true;
bucket.borderDoneWithNeighborZ[i] = nBucket.canonical.z;
bucket.needsCentroidUpdate = true;
if (updateNeighbor) {
nBucket.borderDoneWithNeighborZ[j] = bucket.canonical.z;
nBucket.needsCentroidUpdate = true;
}
}

Expand Down
2 changes: 0 additions & 2 deletions test/ignores.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
"render-tests/distance/layout-text-size": "skip - distance expression is not implemented",
"render-tests/skybox/atmosphere-padding": "skip - https://github.com/mapbox/mapbox-gl-js/issues/10314",
"render-tests/terrain/symbol-draping/style.json": "skip - https://github.com/mapbox/mapbox-gl-js/issues/10365",
"render-tests/fill-extrusion-terrain/flat-roof-over-border-of-different-zoom-zoomin": "skip - https://github.com/mapbox/mapbox-gl-js/issues/11041",
"render-tests/fill-extrusion-terrain/flat-roof-over-border-of-different-zoom": "skip - https://github.com/mapbox/mapbox-gl-js/issues/11041",
"render-tests/text-variable-anchor/pitched": "skip - non-deterministic symbol placement on tile boundaries",
"render-tests/video/projected": "https://github.com/mapbox/mapbox-gl-js/issues/11234",
"query-tests/terrain/draped/lines/slope-occlusion-box-query": "skip - non-deterministic",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"version": 8,
"metadata": {
"description": "Verifies fix for bug https://github.com/mapbox/mapbox-gl-js/issues/11516",
"test": {
"height": 512,
"width": 512,
"operations": [
["wait"]
]
}
},
"sources": {
"rgbterrain": {
"type": "raster-dem",
"tiles": [
"local://tiles/{z}-{x}-{y}.terrain.512.png"
],
"maxzoom": 14,
"tileSize": 512
},
"country-boundaries": {
"type": "vector",
"maxzoom": 2,
"tiles": [
"local://tiles/mapbox.country-boundaries-v1/{z}-{x}-{y}.mvt"
]
}
},
"terrain": {
"source": "rgbterrain",
"exaggeration": ["interpolate",["linear"],["zoom"],0,50,11,1,12,0]
},
"zoom": 2.18,
"center": [
-100,
19
],
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "lightblue"
}
},
{
"id": "country-boundaries",
"type": "fill-extrusion",
"paint": {
"fill-extrusion-color": [
"interpolate",
[
"linear"
],
[
"get",
"color_group"
],
1,
"hsl(0, 87%, 68%)",
6,
"hsl(43, 88%, 58%)"
],
"fill-extrusion-height": [
"interpolate",
[
"linear"
],
[
"get",
"color_group"
],
1,
1000000,
6,
100000
],
"fill-extrusion-opacity": 0.8
},
"source": "country-boundaries",
"source-layer": "country_boundaries"
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,6 @@
"id": "building",
"source": "mapbox",
"paint": {
"fill-opacity": [
"interpolate",
[
"linear"
],
[
"zoom"
],
15.0,
0.0,
16.0,
1.0
],
"fill-outline-color": [
"rgba",
205.00001525878907,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/integration/tiles/15-5237-12668.mvt
Binary file not shown.
Binary file added test/integration/tiles/15-5238-12669.mvt
Binary file not shown.
Binary file added test/integration/tiles/15-5239-12668.mvt
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.