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 issue #5112 (line labels can render incorrectly on overzoomed tiles) #5120

Merged
merged 3 commits into from
Aug 23, 2017
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
66 changes: 52 additions & 14 deletions src/symbol/projection.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,14 @@ function getGlCoordMatrix(posMatrix: mat4,
return m;
}

function project(point: Point, matrix: mat4): Point {
function project(point: Point, matrix: mat4) {
const pos = [point.x, point.y, 0, 1];
vec4.transformMat4(pos, pos, matrix);
return new Point(pos[0] / pos[3], pos[1] / pos[3]);
const w = pos[3];
return {
point: new Point(pos[0] / w, pos[1] / w),
signedDistanceFromCamera: w
};
}

function isVisible(anchorPos: [number, number, number, number],
Expand Down Expand Up @@ -175,16 +179,17 @@ function updateLineLabels(bucket: SymbolBucket,
fontSize * perspectiveRatio :
fontSize / perspectiveRatio;

const anchorPoint = project(new Point(symbol.anchorX, symbol.anchorY), labelPlaneMatrix);
const tileAnchorPoint = new Point(symbol.anchorX, symbol.anchorY);
const anchorPoint = project(tileAnchorPoint, labelPlaneMatrix).point;
const projectionCache = {};

const placeUnflipped = placeGlyphsAlongLine(symbol, pitchScaledFontSize, false /*unflipped*/, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix,
bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, projectionCache);
bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, tileAnchorPoint, projectionCache);

if (placeUnflipped.notEnoughRoom ||
(placeUnflipped.needsFlipping &&
placeGlyphsAlongLine(symbol, pitchScaledFontSize, true /*flipped*/, keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix,
bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, projectionCache).notEnoughRoom)) {
bucket.glyphOffsetArray, lineVertexArray, dynamicLayoutVertexArray, anchorPoint, tileAnchorPoint, projectionCache).notEnoughRoom)) {
hideGlyphs(symbol.numGlyphs, dynamicLayoutVertexArray);
}
}
Expand All @@ -207,6 +212,7 @@ function placeGlyphsAlongLine(symbol,
lineVertexArray: any,
dynamicLayoutVertexArray,
anchorPoint: Point,
tileAnchorPoint: Point,
projectionCache: {[number]: Point}) {
const fontScale = fontSize / 24;
const lineOffsetX = symbol.lineOffsetX * fontSize;
Expand All @@ -223,18 +229,18 @@ function placeGlyphsAlongLine(symbol,
const lineStartIndex = symbol.lineStartIndex;
const lineEndIndex = symbol.lineStartIndex + symbol.lineLength;

const firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment,
const firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment,
lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache);
if (!firstPlacedGlyph)
return { notEnoughRoom: true };

const lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment,
const lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment,
lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache);
if (!lastPlacedGlyph)
return { notEnoughRoom: true };

const firstPoint = project(firstPlacedGlyph.point, glCoordMatrix);
const lastPoint = project(lastPlacedGlyph.point, glCoordMatrix);
const firstPoint = project(firstPlacedGlyph.point, glCoordMatrix).point;
const lastPoint = project(lastPlacedGlyph.point, glCoordMatrix).point;

if (keepUpright && !flip &&
(symbol.vertical ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x)) {
Expand All @@ -246,22 +252,30 @@ function placeGlyphsAlongLine(symbol,
const glyph = glyphOffsetArray.get(glyphIndex);

// Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed
placedGlyphs.push(placeGlyphAlongLine(fontScale * glyph.offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment,
placedGlyphs.push(placeGlyphAlongLine(fontScale * glyph.offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment,
lineStartIndex, lineEndIndex, lineVertexArray, labelPlaneMatrix, projectionCache));
}
placedGlyphs.push(lastPlacedGlyph);
} else {
// Only a single glyph to place
// So, determine whether to flip based on projected angle of the line segment it's on
if (keepUpright && !flip) {
const a = project(lineVertexArray.get(symbol.lineStartIndex + symbol.segment), posMatrix);
const b = project(lineVertexArray.get(symbol.lineStartIndex + symbol.segment + 1), posMatrix);
const a = project(tileAnchorPoint, posMatrix).point;
const tileSegmentEnd = lineVertexArray.get(symbol.lineStartIndex + symbol.segment + 1);
const projectedVertex = project(tileSegmentEnd, posMatrix);
// We know the anchor will be in the viewport, but the end of the line segment may be
// behind the plane of the camera, in which case we can use a point at any arbitrary (closer)
// point on the segment.
const b = (projectedVertex.signedDistanceFromCamera > 0) ?
projectedVertex.point :
projectTruncatedLineSegment(tileAnchorPoint, new Point(tileSegmentEnd.x, tileSegmentEnd.y), a, 1, posMatrix);

if (symbol.vertical ? b.y > a.y : b.x < a.x) {
return { needsFlipping: true };
}
}
const glyph = glyphOffsetArray.get(symbol.glyphStartIndex);
const singleGlyph = placeGlyphAlongLine(fontScale * glyph.offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment,
const singleGlyph = placeGlyphAlongLine(fontScale * glyph.offsetX, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment,
symbol.lineStartIndex, symbol.lineStartIndex + symbol.lineLength, lineVertexArray, labelPlaneMatrix, projectionCache);
if (!singleGlyph)
return { notEnoughRoom: true };
Expand All @@ -276,11 +290,23 @@ function placeGlyphsAlongLine(symbol,
return {};
}

function projectTruncatedLineSegment(previousTilePoint: Point, currentTilePoint: Point, previousProjectedPoint: Point, minimumLength: number, projectionMatrix: mat4) {
// We are assuming "previousTilePoint" won't project to a point within one unit of the camera plane
// If it did, that would mean our label extended all the way out from within the viewport to a (very distant)
// point near the plane of the camera. We wouldn't be able to render the label anyway once it crossed the
// plane of the camera.
const projectedUnitVertex = project(previousTilePoint.add(previousTilePoint.sub(currentTilePoint)._unit()), projectionMatrix).point;
const projectedUnitSegment = previousProjectedPoint.sub(projectedUnitVertex);

return previousProjectedPoint.add(projectedUnitSegment._mult(minimumLength / projectedUnitSegment.mag()));
}

function placeGlyphAlongLine(offsetX: number,
lineOffsetX: number,
lineOffsetY: number,
flip: boolean,
anchorPoint: Point,
tileAnchorPoint: Point,
anchorSegment: number,
lineStartIndex: number,
lineEndIndex: number,
Expand Down Expand Up @@ -325,7 +351,19 @@ function placeGlyphAlongLine(offsetX: number,

current = projectionCache[currentIndex];
if (current === undefined) {
current = projectionCache[currentIndex] = project(lineVertexArray.get(currentIndex), labelPlaneMatrix);
const projection = project(lineVertexArray.get(currentIndex), labelPlaneMatrix);
if (projection.signedDistanceFromCamera > 0) {
current = projectionCache[currentIndex] = projection.point;
} else {
// The vertex is behind the plane of the camera, so we can't project it
// Instead, we'll create a vertex along the line that's far enough to include the glyph
const previousTilePoint = distanceToPrev === 0 ?
tileAnchorPoint :
new Point(lineVertexArray.get(currentIndex - dir).x, lineVertexArray.get(currentIndex - dir).y);
const currentTilePoint = new Point(lineVertexArray.get(currentIndex).x, lineVertexArray.get(currentIndex).y);
// Don't cache because the new vertex might not be far enough out for future glyphs on the same segment
current = projectTruncatedLineSegment(previousTilePoint, currentTilePoint, prev, absOffsetX - distanceToPrev + 1, labelPlaneMatrix);
}
}

distanceToPrev += currentSegmentDistance;
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,56 @@
{
"version": 8,
"center": [
-118.28162,
33.86852
],
"zoom": 21,
"pitch": 60,
"bearing": 161.5,
"sources": {
"mapbox": {
"type": "vector",
"maxzoom": 16,
"tiles": [
"local://tiles/mapbox.mapbox-streets-v7/16-11235-26208.mvt"
]
}
},
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "white"
}
},
{
"id": "road-label-large",
"type": "symbol",
"source": "mapbox",
"source-layer": "road_label",
"layout": {
"text-size": { "base": 1, "stops": [ [ 9, 10 ], [ 20, 16 ] ] },
"text-max-angle": 30,
"symbol-spacing": 250,
"text-font": [
"Open Sans Semibold",
"Arial Unicode MS Bold"
],
"symbol-placement": "line",
"text-padding": 1,
"text-rotation-alignment": "map",
"text-pitch-alignment": "viewport",
"text-field": "C",
"text-letter-spacing": 0.01
},
"paint": {
"text-color": "hsl(0, 0%, 0%)",
"text-halo-color": "hsla(0, 0%, 100%, 0.75)",
"text-halo-width": 1,
"text-halo-blur": 1
}
}
]
}
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,56 @@
{
"version": 8,
"metadata": {
"test": {
"height": 1024
}
},
"center": [
50,
0
],
"zoom": 18,
"pitch": 60,
"sources": {
"geojson": {
"type": "geojson",
"maxzoom": 16,
"data": {
"type": "LineString",
"coordinates": [
[ 50, -90 ],
[ 50, 90 ]
]
}
}
},
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "white"
}
},
{
"id": "text",
"type": "symbol",
"source": "geojson",
"layout": {
"symbol-placement": "line",
"symbol-spacing": 10,
"text-rotation-alignment": "map",
"text-pitch-alignment": "viewport",
"text-field": "Figueroa St.",
"text-font": [
"Open Sans Semibold",
"Arial Unicode MS Bold"
]
},
"paint": {
"text-opacity": 1
}
}
]
}
Binary file not shown.