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

Commit

Permalink
MAPSJS-2660: Clean MapViewUtils namespace (#2302)
Browse files Browse the repository at this point in the history
* MAPSJS-2660: Move TileOffsetUtils to TileKeyUtils in harp-geoutils.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>

* MAPSJS-2660: Move Object3D size estimation to internal namespace.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>

* MAPSJS-2660: Move getBrowserLanguages to harp-utils.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>

* MAPSJS-2660: Add MapViewUtils description.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>

* MAPSJS-2660: Simplify powerOfTwo generation.

Also, move old test to the right place.

Signed-off-by: Andres Mandado <andres.mandado-almajano@here.com>
  • Loading branch information
atomicsulfate committed Sep 3, 2021
1 parent 6935082 commit 4432ea4
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 629 deletions.
121 changes: 116 additions & 5 deletions @here/harp-geoutils/lib/tiling/TileKeyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@ import { Vector3Like } from "../math/Vector3Like";
import { TileKey } from "./TileKey";
import { TilingScheme } from "./TilingScheme";

export class TileKeyUtils {
static geoCoordinatesToTileKey(
export const powerOfTwo = (() => {
let val = 0.5;
return new Array(53).fill(0).map(() => (val *= 2));
})();

export namespace TileKeyUtils {
export function geoCoordinatesToTileKey(
tilingScheme: TilingScheme,
geoPoint: GeoCoordinatesLike,
level: number
): TileKey | null {
const projection = tilingScheme.projection;
const worldPoint = projection.projectPoint(geoPoint);

return this.worldCoordinatesToTileKey(tilingScheme, worldPoint, level);
return worldCoordinatesToTileKey(tilingScheme, worldPoint, level);
}

static worldCoordinatesToTileKey(
export function worldCoordinatesToTileKey(
tilingScheme: TilingScheme,
worldPoint: Vector3Like,
level: number
Expand Down Expand Up @@ -52,7 +57,7 @@ export class TileKeyUtils {
return TileKey.fromRowColumnLevel(row, column, level);
}

static geoRectangleToTileKeys(
export function geoRectangleToTileKeys(
tilingScheme: TilingScheme,
geoBox: GeoBox,
level: number
Expand Down Expand Up @@ -124,4 +129,110 @@ export class TileKeyUtils {

return keys;
}

/**
* Creates a unique key based on the supplied parameters. Note, the uniqueness is bounded by the
* bitshift. The [[TileKey.mortonCode()]] supports currently up to 26 levels (this is because
* 26*2 equals 52, and 2^52 is the highest bit that can be set in an integer in Javascript), the
* bitshift reduces this accordingly, so given the default bitshift of four, we support up to 24
* levels. Given the current support up to level 19 this should be fine.
*
* @param tileKey - The unique {@link @here/harp-geoutils#TileKey}
* from which to compute the unique key.
* @param offset - How much the given {@link @here/harp-geoutils#TileKey} is offset
* @param bitshift - How much space we have to store the offset. The default of 4 means we have
* enough space to store 16 unique tiles in a single view.
*/
export function getKeyForTileKeyAndOffset(
tileKey: TileKey,
offset: number,
bitshift: number = 4
) {
const shiftedOffset = getShiftedOffset(offset, bitshift);
return tileKey.mortonCode() + shiftedOffset;
}

/**
* Extracts the offset and morton key from the given key (must be created by:
* [[getKeyForTileKeyAndOffset]])
*
* Note, we can't use bitshift operators in Javascript because they work on 32-bit integers, and
* would truncate the numbers, hence using powers of two.
*
* @param key - Key to extract offset and morton key.
* @param bitshift - How many bits to shift by, must be the same as was used when creating the
* key.
*/
export function extractOffsetAndMortonKeyFromKey(key: number, bitshift: number = 4) {
let offset = 0;
let mortonCode = key;
let i = 0;
// Compute the offset
for (; i < bitshift; i++) {
// Note, we use 52, because 2^53-1 is the biggest value, the highest value
// that can be set is the bit in the 52th position.
const num = powerOfTwo[52 - i];
if (mortonCode >= num) {
mortonCode -= num;
offset += powerOfTwo[bitshift - 1 - i];
}
}
// We subtract half of the total amount, this undoes what is computed in getShiftedOffset
offset -= powerOfTwo[bitshift - 1];
return { offset, mortonCode };
}

/**
* Returns the key of the parent. Key must have been computed using the function
* [[getKeyForTileKeyAndOffset]].
*
* @param calculatedKey - Key to decompose
* @param bitshift - Bit shift used to create the key
*/
export function getParentKeyFromKey(calculatedKey: number, bitshift: number = 4) {
const { offset, mortonCode } = extractOffsetAndMortonKeyFromKey(calculatedKey, bitshift);
const parentTileKey = TileKey.fromMortonCode(TileKey.parentMortonCode(mortonCode));
return getKeyForTileKeyAndOffset(parentTileKey, offset, bitshift);
}

/**
* Packs the supplied offset into the high bits, where the highbits are between 2^52 and
* 2^(52-bitshift).
*
* Offsets are wrapped around, to fit in the offsetBits. In practice, this doesn't really
* matter, this is primarily used to find a unique id, if there is an offset 10, which is
* wrapped to 2, it doesn't matter, because the offset of 10 is still stored in the tile.
* What can be a problem though is that the cache gets filled up and isn't emptied.
*
* Note, because bit shifting in JavaScript works on 32 bit integers, we use powers of 2 to set
* the high bits instead.
*
* @param offset - Offset to pack into the high bits.
* @param offsetBits - How many bits to use to pack the offset.
*/
function getShiftedOffset(offset: number, offsetBits: number = 4) {
let result = 0;
const totalOffsetsToStore = powerOfTwo[offsetBits];
//Offsets are stored by adding half 2 ^ (bitshift - 1), i.e.half of the max amount stored,
//and then wrapped based on this value.For example, given a bitshift of 3, and an offset -
//3, it would have 4 added(half of 2 ^ 3), and be stored as 1, 3 would have 4 added and be
//stored as 7, 4 would be added with 4 and be stored as 0 (it wraps around).
offset += totalOffsetsToStore / 2;
while (offset < 0) {
offset += totalOffsetsToStore;
}
while (offset >= totalOffsetsToStore) {
offset -= totalOffsetsToStore;
}
// Offset is now a number between >= 0 and < totalOffsetsToStore
for (let i = 0; i < offsetBits && offset > 0; i++) {
// 53 is used because 2^53-1 is the biggest number that Javascript can represent as an
// integer safely.
if (offset & 0x1) {
result += powerOfTwo[53 - offsetBits + i];
}
offset >>>= 1;
}
return result;
}
}
16 changes: 0 additions & 16 deletions @here/harp-geoutils/test/TileKeyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,3 @@ describe("Equirectangular", function () {
assert.strictEqual(tileKey.level, 13);
});
});

describe("TileKeyUtils", function () {
it("geoRectangleToTileKeys", function () {
const geoBox = new GeoBox(
new GeoCoordinates(52.5163, 13.3777), // Brandenburg gate
new GeoCoordinates(52.5309, 13.385) // HERE office
);
const expectedResult = [371506848, 371506849, 371506850, 371506851];

const result = TileKeyUtils.geoRectangleToTileKeys(webMercatorTilingScheme, geoBox, 14);
assert.sameMembers(
result.map(tk => tk.mortonCode()),
expectedResult
);
});
});
68 changes: 68 additions & 0 deletions @here/harp-geoutils/test/TileKeyUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2021 HERE Europe B.V.
* Licensed under Apache 2.0, see full license in LICENSE
* SPDX-License-Identifier: Apache-2.0
*/

import { assert, expect } from "chai";

import { GeoBox } from "../lib/coordinates/GeoBox";
import { GeoCoordinates } from "../lib/coordinates/GeoCoordinates";
import { TileKey } from "../lib/tiling/TileKey";
import { TileKeyUtils } from "../lib/tiling/TileKeyUtils";
import { webMercatorTilingScheme } from "../lib/tiling/WebMercatorTilingScheme";

describe("TileKeyUtils", function () {
it("test getKeyForTileKeyAndOffset and extractOffsetAndMortonKeyFromKey", async function () {
// This allows 8 offsets to be stored, -4 -> 3, we test also outside this range
const bitshift = 3;
const offsets = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5];
// Binary is the easist to read, here you can see the -4 -> 3 is mapped to 0 -> 7
// in the 3 highest bits.
const results = [
0b11100000000000000000000000000000000000000000000000111,
0b00000000000000000000000000000000000000000000000000111,
0b00100000000000000000000000000000000000000000000000111,
0b01000000000000000000000000000000000000000000000000111,
0b01100000000000000000000000000000000000000000000000111,
0b10000000000000000000000000000000000000000000000000111,
0b10100000000000000000000000000000000000000000000000111,
0b11000000000000000000000000000000000000000000000000111,
0b11100000000000000000000000000000000000000000000000111,
// Check that we wrap back around to 0
0b00000000000000000000000000000000000000000000000000111,
0b00100000000000000000000000000000000000000000000000111
];
const offsetResults = [3, -4, -3, -2, -1, 0, 1, 2, 3, -4, -3];
const tileKey = TileKey.fromRowColumnLevel(1, 1, 1);
for (let i = 0; i < offsets.length; i++) {
const keyByTileKeyAndOffset = TileKeyUtils.getKeyForTileKeyAndOffset(
tileKey,
offsets[i],
bitshift
);
expect(keyByTileKeyAndOffset).to.be.equal(results[i]);

const { offset, mortonCode } = TileKeyUtils.extractOffsetAndMortonKeyFromKey(
keyByTileKeyAndOffset,
bitshift
);
expect(offset).to.be.equal(offsetResults[i]);
expect(mortonCode).to.be.equal(tileKey.mortonCode());
}
});

it("geoRectangleToTileKeys", function () {
const geoBox = new GeoBox(
new GeoCoordinates(52.5163, 13.3777), // Brandenburg gate
new GeoCoordinates(52.5309, 13.385) // HERE office
);
const expectedResult = [371506848, 371506849, 371506850, 371506851];

const result = TileKeyUtils.geoRectangleToTileKeys(webMercatorTilingScheme, geoBox, 14);
assert.sameMembers(
result.map(tk => tk.mortonCode()),
expectedResult
);
});
});
9 changes: 5 additions & 4 deletions @here/harp-mapview/lib/FrustumIntersection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Projection,
ProjectionType,
TileKey,
TileKeyUtils,
TilingScheme
} from "@here/harp-geoutils";
import { assert } from "@here/harp-utils";
Expand All @@ -17,7 +18,7 @@ import { DataSource } from "./DataSource";
import { CalculationStatus, ElevationRange, ElevationRangeSource } from "./ElevationRangeSource";
import { MapTileCuller } from "./MapTileCuller";
import { MapView } from "./MapView";
import { MapViewUtils, TileOffsetUtils } from "./Utils";
import { MapViewUtils } from "./Utils";

const tmpVectors3 = [new THREE.Vector3(), new THREE.Vector3()];
const tmpVector4 = new THREE.Vector4();
Expand Down Expand Up @@ -197,7 +198,7 @@ export class FrustumIntersection {
for (const zoomLevel of uniqueZoomLevels) {
const tileKeyEntries = this.m_tileKeyEntries.get(zoomLevel)!;
tileKeyEntries.set(
TileOffsetUtils.getKeyForTileKeyAndOffset(tileKey, offset),
TileKeyUtils.getKeyForTileKeyAndOffset(tileKey, offset),
tileKeyEntry
);
}
Expand Down Expand Up @@ -227,7 +228,7 @@ export class FrustumIntersection {
continue;
}

const tileKeyAndOffset = TileOffsetUtils.getKeyForTileKeyAndOffset(tileKey, offset);
const tileKeyAndOffset = TileKeyUtils.getKeyForTileKeyAndOffset(tileKey, offset);

// delete parent tile key from applicable zoom levels
for (const zoomLevel of uniqueZoomLevels) {
Expand Down Expand Up @@ -257,7 +258,7 @@ export class FrustumIntersection {
continue;
}

const subTileKeyAndOffset = TileOffsetUtils.getKeyForTileKeyAndOffset(
const subTileKeyAndOffset = TileKeyUtils.getKeyForTileKeyAndOffset(
subTileKey,
offset
);
Expand Down
10 changes: 5 additions & 5 deletions @here/harp-mapview/lib/Tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
GeometryType,
TextPathGeometry
} from "@here/harp-datasource-protocol";
import { GeoBox, OrientedBox3, Projection, TileKey } from "@here/harp-geoutils";
import { GeoBox, OrientedBox3, Projection, TileKey, TileKeyUtils } from "@here/harp-geoutils";
import { assert, CachedResource, chainCallbacks, LoggerManager } from "@here/harp-utils";
import * as THREE from "three";

import { CopyrightInfo } from "./copyrights/CopyrightInfo";
import { DataSource } from "./DataSource";
import { ElevationRange } from "./ElevationRangeSource";
import { LodMesh } from "./geometry/LodMesh";
import { Object3DUtils } from "./geometry/Object3DUtils";
import { TileGeometryLoader } from "./geometry/TileGeometryLoader";
import { ITileLoader, TileLoaderState } from "./ITileLoader";
import { MapView } from "./MapView";
Expand All @@ -26,7 +27,6 @@ import { TextElement } from "./text/TextElement";
import { TextElementGroup } from "./text/TextElementGroup";
import { TextElementGroupPriorityList } from "./text/TextElementGroupPriorityList";
import { TileTextStyleCache } from "./text/TileTextStyleCache";
import { MapViewUtils, TileOffsetUtils } from "./Utils";

const logger = LoggerManager.instance.create("Tile");

Expand Down Expand Up @@ -324,7 +324,7 @@ export class Tile implements CachedResource {
this.m_localTangentSpace = localTangentSpace ?? false;
this.m_textStyleCache = new TileTextStyleCache(this);
this.m_offset = offset;
this.m_uniqueKey = TileOffsetUtils.getKeyForTileKeyAndOffset(this.tileKey, this.offset);
this.m_uniqueKey = TileKeyUtils.getKeyForTileKeyAndOffset(this.tileKey, this.offset);
if (dataSource.useGeometryLoader) {
this.m_tileGeometryLoader = new TileGeometryLoader(this, this.mapView.taskQueue);
this.attachGeometryLoadedCallback();
Expand Down Expand Up @@ -434,7 +434,7 @@ export class Tile implements CachedResource {
*/
set offset(offset: number) {
if (this.m_offset !== offset) {
this.m_uniqueKey = TileOffsetUtils.getKeyForTileKeyAndOffset(this.tileKey, offset);
this.m_uniqueKey = TileKeyUtils.getKeyForTileKeyAndOffset(this.tileKey, offset);
}
this.m_offset = offset;
}
Expand Down Expand Up @@ -1167,7 +1167,7 @@ export class Tile implements CachedResource {
if (object.visible) {
num3dObjects++;
}
MapViewUtils.estimateObject3dSize(object, aggregatedObjSize, visitedObjects);
Object3DUtils.estimateSize(object, aggregatedObjSize, visitedObjects);
}

for (const group of this.textElementGroups.groups) {
Expand Down
Loading

1 comment on commit 4432ea4

@zawyeaung-1986
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@here/harp-mapview/test/Object3DUtilsTest.ts

Please sign in to comment.