Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
HARP-9898: Solver screen space text offsets for alternative text plac…
Browse files Browse the repository at this point in the history
…ements.

This is follow-up commit that provides support for screen space defined
(not icon relative) text offsets defined for point labels:
- xOffset,
- yOffset
attributes in theme (style) definition.
Provided solution ensures that the icon-text relation (manhattan distance)
is preserved regardless of placement used. So in simplest case offset is
mirrored (left-right alternative placement), but it may be also projected
on Y axis when alternative placement changes text position vertically.

Signed-off-by: Krystian Kostecki <ext-krystian.kostecki@here.com>
  • Loading branch information
kkostecki committed Apr 23, 2020
1 parent 053fec0 commit 99ab770
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 22 deletions.
94 changes: 72 additions & 22 deletions @here/harp-mapview/lib/text/Placement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Env } from "@here/harp-datasource-protocol";
import { Env, getPropertyValue } from "@here/harp-datasource-protocol";
import { ProjectionType } from "@here/harp-geoutils";
import {
HorizontalAlignment,
Expand Down Expand Up @@ -219,43 +219,87 @@ export function checkReadyForPlacement(
* Computes the offset for a point text accordingly to text alignment (and icon, if any).
* @param textElement The text element of which the offset will computed. It must be a point
* label with [[layoutStyle]] and [[bounds]] already computed.
* @param hAlign Text horizontal alignment.
* @param vAlign The vertical alignment.
* @param placement The relative anchor placement (may be different then original alignment).
* @param scale The scaling factor (due to distance, etc.).
* @param env The [[Env]] used to evaluate technique attributes.
* @param offset The offset result.
*/
function computePointTextOffset(
textElement: TextElement,
hAlign: HorizontalAlignment,
vAlign: VerticalAlignment,
placement: AnchorPlacement,
scale: number,
env: Env,
offset: THREE.Vector2 = new THREE.Vector2()
): THREE.Vector2 {
assert(textElement.type === TextElementType.PoiLabel);
assert(textElement.layoutStyle !== undefined);
assert(textElement.bounds !== undefined);

offset.x = textElement.xOffset;
offset.y = textElement.yOffset;

switch (vAlign) {
case VerticalAlignment.Below:
offset.y = textElement.yOffset;
break;
switch (placement.v) {
case VerticalAlignment.Above:
offset.y = textElement.yOffset - textElement.bounds!.min.y;
offset.y -= textElement.bounds!.min.y;
break;
default:
offset.y =
textElement.yOffset - 0.5 * (textElement.bounds!.max.y + textElement.bounds!.min.y);
case VerticalAlignment.Center:
offset.y -= 0.5 * (textElement.bounds!.max.y + textElement.bounds!.min.y);
break;
}

if (textElement.poiInfo !== undefined && poiIsRenderable(textElement.poiInfo)) {
assert(textElement.poiInfo.computedWidth !== undefined);
assert(textElement.poiInfo.computedHeight !== undefined);

offset.x += textElement.poiInfo.computedWidth! * (0.5 + hAlign);
offset.y += textElement.poiInfo.computedHeight! * (0.5 + vAlign);
// Apply offset moving text out of the icon
offset.x += textElement.poiInfo.computedWidth! * (0.5 + placement.h);
offset.y += textElement.poiInfo.computedHeight! * (0.5 + placement.v);

// Reverse, mirror or project offsets on different axis depending on the placement
// required only for alternative placements.
const hAlign = textElement.layoutStyle!.horizontalAlignment;
const vAlign = textElement.layoutStyle!.verticalAlignment;
if (hAlign !== placement.h || vAlign !== placement.v) {
// Read icon offset used.
const technique = textElement.poiInfo.technique;
let iconXOffset = getPropertyValue(technique.iconXOffset, env);
let iconYOffset = getPropertyValue(technique.iconYOffset, env);
iconXOffset = typeof iconXOffset === "number" ? iconXOffset : 0;
iconYOffset = typeof iconYOffset === "number" ? iconYOffset : 0;

// Now mirror the text offset relative to icon so manhattan distance is preserved, when
// alternative position is taken, this ensures that text-icon relative position is
// the same as in base alignment.
const hAlignDiff = hAlign - placement.h;
const vAlignDiff = vAlign - placement.v;
const relOffsetX = iconXOffset - textElement.xOffset;
const relOffsetY = iconYOffset - textElement.yOffset;
const centerBased =
hAlign === HorizontalAlignment.Center || vAlign === VerticalAlignment.Center;
if (centerBased) {
// Center based alternative placements.
offset.x += 2 * Math.abs(hAlignDiff) * relOffsetX;
offset.y -= 2 * vAlignDiff * Math.abs(relOffsetX);

offset.y += 2 * Math.abs(vAlignDiff) * relOffsetY;
offset.x -= 2 * hAlignDiff * Math.abs(relOffsetY);
} else {
// Corner alternative placements
offset.x += 2 * Math.min(Math.abs(hAlignDiff), 0.5) * relOffsetX;
offset.y -=
2 *
Math.sign(vAlignDiff) *
Math.min(Math.abs(vAlignDiff), 0.5) *
Math.abs(relOffsetX);

offset.y += 2 * Math.min(Math.abs(vAlignDiff), 0.5) * relOffsetY;
offset.x -=
2 *
Math.sign(hAlignDiff) *
Math.min(Math.abs(hAlignDiff), 0.5) *
Math.abs(relOffsetY);
}
}
}

offset.multiplyScalar(scale);
Expand Down Expand Up @@ -318,6 +362,7 @@ export function placeIcon(
* @param screenPosition Position of the label in screen coordinates.
* @param scale Scale factor to be applied to label dimensions.
* @param textCanvas The text canvas where the label will be placed.
* @param env The [[Env]] used to evaluate technique attributes.
* @param screenCollisions Used to check collisions with other labels.
* @param isRejected Whether the label is already rejected (e.g. because its icon was rejected). If
* `true`, text won't be checked for collision, result will be either `PlacementResult.Invisible`
Expand All @@ -336,6 +381,7 @@ export function placePointLabel(
screenPosition: THREE.Vector2,
scale: number,
textCanvas: TextCanvas,
env: Env,
screenCollisions: ScreenCollisions,
isRejected: boolean,
outScreenPosition: THREE.Vector3,
Expand Down Expand Up @@ -364,6 +410,7 @@ export function placePointLabel(
screenPosition,
scale,
textCanvas,
env,
screenCollisions,
isRejected,
outScreenPosition
Expand All @@ -376,6 +423,7 @@ export function placePointLabel(
screenPosition,
scale,
textCanvas,
env,
screenCollisions,
outScreenPosition
);
Expand All @@ -392,6 +440,7 @@ export function placePointLabel(
* @param screenPosition Position of the label in screen coordinates.
* @param scale Scale factor to be applied to label dimensions.
* @param textCanvas The text canvas where the label will be placed.
* @param env The [[Env]] used to evaluate technique attributes.
* @param screenCollisions Used to check collisions with other labels.
* @param outScreenPosition The final label screen position after applying any offsets.
* @returns `PlacementResult.Ok` if label can be placed at the base or optional anchor point,
Expand All @@ -406,6 +455,7 @@ function placePointLabelChoosingAnchor(
screenPosition: THREE.Vector2,
scale: number,
textCanvas: TextCanvas,
env: Env,
screenCollisions: ScreenCollisions,
outScreenPosition: THREE.Vector3
): PlacementResult {
Expand Down Expand Up @@ -449,6 +499,7 @@ function placePointLabelChoosingAnchor(
anchorPlacement,
scale,
textCanvas,
env,
screenCollisions,
false,
!isLastPlacement,
Expand Down Expand Up @@ -503,6 +554,7 @@ function placePointLabelChoosingAnchor(
* @param screenPosition Position of the label in screen coordinates.
* @param scale Scale factor to be applied to label dimensions.
* @param textCanvas The text canvas where the label will be placed.
* @param env The [[Env]] used to evaluate technique attributes.
* @param screenCollisions Used to check collisions with other labels.
* @param isRejected Whether the label is already rejected (e.g. because its icon was rejected). If
* `true`, text won't be checked for collision, result will be either `PlacementResult.Invisible` or
Expand All @@ -519,6 +571,7 @@ function placePointLabelAtCurrentAnchor(
screenPosition: THREE.Vector2,
scale: number,
textCanvas: TextCanvas,
env: Env,
screenCollisions: ScreenCollisions,
isRejected: boolean,
outScreenPosition: THREE.Vector3
Expand All @@ -533,6 +586,7 @@ function placePointLabelAtCurrentAnchor(
lastPlacement,
scale,
textCanvas,
env,
screenCollisions,
isRejected,
false,
Expand All @@ -550,6 +604,7 @@ function placePointLabelAtCurrentAnchor(
* @param placement Text placement relative to the label position.
* @param scale Scale factor to be applied to label dimensions.
* @param textCanvas The text canvas where the label will be placed.
* @param env The [[Env]] used to evaluate technique attributes.
* @param screenCollisions Used to check collisions with other labels.
* @param isRejected Whether the label is already rejected (e.g. because its icon was rejected). If
* `true`, text won't be checked for collision, result will be either `PlacementResult.Invisible` or
Expand All @@ -569,6 +624,7 @@ function placePointLabelAtAnchor(
placement: AnchorPlacement,
scale: number,
textCanvas: TextCanvas,
env: Env,
screenCollisions: ScreenCollisions,
isRejected: boolean,
forceInvalidation: boolean,
Expand Down Expand Up @@ -597,13 +653,7 @@ function placePointLabelAtAnchor(
}

// Compute text offset from the anchor point
const textOffset = computePointTextOffset(
label,
placement.h,
placement.v,
scale,
tmpTextOffset
);
const textOffset = computePointTextOffset(label, placement, scale, env, tmpTextOffset);
textOffset.add(screenPosition);
tmpBox.copy(label.bounds!);
tmpBox.min.multiplyScalar(scale);
Expand Down
1 change: 1 addition & 0 deletions @here/harp-mapview/lib/text/TextElementsRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,7 @@ export class TextElementsRenderer {
tempScreenPosition,
distanceScaleFactor,
textCanvas,
this.m_viewState.env,
this.m_screenCollisions,
iconRejected,
tempPosition,
Expand Down
19 changes: 19 additions & 0 deletions @here/harp-mapview/test/PlacementTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
outPosition
Expand Down Expand Up @@ -301,6 +302,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
position
Expand Down Expand Up @@ -334,6 +336,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
position
Expand Down Expand Up @@ -367,6 +370,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
position
Expand Down Expand Up @@ -403,6 +407,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
position
Expand Down Expand Up @@ -439,6 +444,7 @@ describe("Placement", function() {
new THREE.Vector2(0, 0),
0.8,
textCanvas,
new Env(),
screenCollisions,
false,
position
Expand Down Expand Up @@ -598,6 +604,7 @@ describe("Placement", function() {
inPosition,
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
outPosition,
Expand All @@ -616,6 +623,7 @@ describe("Placement", function() {
inPosition,
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
outPosition,
Expand Down Expand Up @@ -798,6 +806,7 @@ describe("Placement", function() {
inPosition,
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
outPosition,
Expand All @@ -821,6 +830,7 @@ describe("Placement", function() {
inPosition,
1.0,
textCanvas,
new Env(),
screenCollisions,
false,
outPosition,
Expand Down Expand Up @@ -865,6 +875,7 @@ describe("Placement", function() {
const offset = 5; // Need to keep some offset because of internal margin
const inPositions = [new THREE.Vector2(-offset, 0), new THREE.Vector2(offset, 0)];
const outPositions = [new THREE.Vector3(), new THREE.Vector3()];
const env = new Env();

// Place each text element sequentially without multi-placement support
const results: PlacementResult[] = [0, 0];
Expand All @@ -876,6 +887,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i]
Expand All @@ -900,6 +912,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i],
Expand Down Expand Up @@ -938,6 +951,7 @@ describe("Placement", function() {
const offset = 5; // Need to keep some offset because of internal margin
const inPositions = [new THREE.Vector2(-offset, 0), new THREE.Vector2(offset, 0)];
const outPositions = [new THREE.Vector3(), new THREE.Vector3()];
const env = new Env();

// Place each text element sequentially without multi-placement support
const results: PlacementResult[] = [0, 0];
Expand All @@ -949,6 +963,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i],
Expand Down Expand Up @@ -984,6 +999,7 @@ describe("Placement", function() {
let offset = 50;
const inPositions = [new THREE.Vector2(-offset, 0), new THREE.Vector2(offset, 0)];
const outPositions = [new THREE.Vector3(), new THREE.Vector3()];
const env = new Env();

// Place each text element sequentially.
const results: PlacementResult[] = [0, 0];
Expand All @@ -995,6 +1011,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i],
Expand Down Expand Up @@ -1033,6 +1050,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i],
Expand Down Expand Up @@ -1074,6 +1092,7 @@ describe("Placement", function() {
inPositions[i],
1.0,
textCanvas,
env,
screenCollisions,
false,
outPositions[i],
Expand Down

0 comments on commit 99ab770

Please sign in to comment.