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

Commit

Permalink
HARP-9673: Snap zoom level to nearest integer if close enough to it.
Browse files Browse the repository at this point in the history
Conversions from zoom level to camera distance and back to zoom level cause a loss of
precision that makes the actual zoom level at each frame fall sometimes below and others
above a given integer zoom level, causing flickering of some map objects (e.g. extruded buildings).

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>
  • Loading branch information
atomicsulfate committed May 29, 2020
1 parent 4a74f82 commit b871482
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 36 deletions.
46 changes: 22 additions & 24 deletions @here/harp-mapview/lib/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ const cache = {
]
};

/**
* Rounds a given zoom level up to the nearest integer value if it's close enough.
*
* The zoom level set in [[MapView]] after a zoom level target is given to [[MapView.lookAt]] or
* [[MapControls]] never matches exactly the target due to the precision loss caused by the
* conversion from zoom level to camera distance (done in [[MapView.lookAt]] and [[MapControls]])
* and from distance back to zoom level (done at every frame on camera update).
* As a result, given a fixed integer zoom level input, the final zoom level computed at every frame
* may fall sometimes below the integer value and others above. This causes flickering since each
* frame will use different tile levels and different style evaluations for object visibility.
* See HARP-9673 and HARP-8523.
* @param zoomLevel Input zoom level
* @return The ceiling zoom level if input zoom level is close enough, otherwise the unmodified
* input zoom level.
*/
function snapToCeilingZoomLevel(zoomLevel: number) {
const eps = 1e-6;
const ceiling = Math.ceil(zoomLevel);
return ceiling - zoomLevel < eps ? ceiling : zoomLevel;
}

export namespace MapViewUtils {
export const MAX_TILT_DEG = 89;
export const MAX_TILT_RAD = MAX_TILT_DEG * THREE.MathUtils.DEG2RAD;
Expand Down Expand Up @@ -1228,10 +1249,7 @@ export namespace MapViewUtils {
options.minZoomLevel,
options.maxZoomLevel
);
// Round to avoid modify the zoom level without distance change, with the imprecision
// introduced by ray-casting and distance calculus.
// NOTE: Using 10 fractional digits as rounding precision, this solves HARP-8523.
return roundZoomLevel(zoomLevel);
return snapToCeilingZoomLevel(zoomLevel);
}

/**
Expand Down Expand Up @@ -1337,26 +1355,6 @@ export namespace MapViewUtils {
return (distance * screenSize) / focalLength;
}

/**
* Function performs zoom level rounding to 10-th place after comma.
*
* Inaccuracies on the 13-th fractional digit may be observed when doing small
* tilt changes, thus causing the zoom level to be discretized to smaller value then real
* one, for example when acquiring tiles storage level or visibility level.
* This causes zoom level jitter and displaying wrong tile set (with different zoom level)
* for a certain camera arrangements (angles).
*
* @note Rounding function is used to limit zoom level jitter and fluctuations.
*
* @param zoomLevel Input zoom level from based on camera distance.
* @return The resulting zoom level rounded to 10-th place after comma.
*/
export function roundZoomLevel(zoomLevel: number) {
// Here 10 digits gives quite big safety margin, yet still giving enough precision for
// zoom level based interpolations.
return Math.round(zoomLevel * 10e10) / 10e10;
}

/**
* Computes estimate for size of a THREE.Object3D object and its children. Shared materials
* and/or attributes will be counted multiple times.
Expand Down
35 changes: 23 additions & 12 deletions @here/harp-mapview/test/UtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// Mocha discourages using arrow functions, see https://mochajs.org/#arrow-functions

import {
EarthConstants,
GeoCoordinates,
mercatorProjection,
Projection,
Expand All @@ -31,7 +32,7 @@ const cameraMock = {
};

describe("map-view#Utils", function() {
it("calculates zoom level", function() {
describe("calculateZoomLevelFromDistance", function() {
const mapViewMock = {
maxZoomLevel: 20,
minZoomLevel: 1,
Expand All @@ -41,18 +42,28 @@ describe("map-view#Utils", function() {
pixelRatio: 1.0
};
const mapView = (mapViewMock as any) as MapView;
it("calculates zoom level", function() {
let result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 0);
expect(result).to.be.equal(20);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000000000000);
expect(result).to.be.equal(1);
/*
* 23.04.2018 - Zoom level outputs come from HARP
*/
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 10000);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000000);
expect(result).to.be.closeTo(5.32, 0.05);
});

let result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 0);
expect(result).to.be.equal(20);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000000000000);
expect(result).to.be.equal(1);
/*
* 23.04.2018 - Zoom level outputs come from HARP
*/
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 10000);
result = MapViewUtils.calculateZoomLevelFromDistance(mapView, 1000000);
expect(result).to.be.closeTo(5.32, 0.05);
it("snaps zoom level to ceiling integer if close enough to it", function() {
const eps = 1e-10;
const result = MapViewUtils.calculateZoomLevelFromDistance(
mapView,
EarthConstants.EQUATORIAL_CIRCUMFERENCE * (0.25 + eps)
);
expect(result).equals(2);
});
});

it("converts target coordinates from XYZ to camera coordinates", function() {
Expand Down

0 comments on commit b871482

Please sign in to comment.