refactor: provider get vertices returns polygon

This commit is contained in:
Oscar Lorentzon 2021-03-26 20:06:44 +01:00
parent 2eed0b3c9f
commit 4891291c7c
9 changed files with 153 additions and 161 deletions

View File

@ -48,6 +48,4 @@ const bundles = [
},
];
bundles.unshift(esm);
export default bundles;

View File

@ -1,9 +1,9 @@
import { S2 } from "s2-geometry";
import { CellCorners } from "../../src/api/interfaces/CellCorners";
import { LatLon } from "../../src/api/interfaces/LatLon";
import { S2GeometryProvider } from "../../src/api/S2GeometryProvider";
import { MapillaryError } from "../../src/error/MapillaryError";
import * as GeoCoords from "../../src/geo/GeoCoords";
import { isClockwise } from "../helper/TestMath";
describe("S2GeometryProvider.ctor", () => {
it("should be defined", () => {
@ -231,20 +231,17 @@ describe("S2GeometryProvider.getCorners", () => {
];
for (let latLon of latLons) {
const cellId: string = geometry.latLonToCellId(latLon);
const corners: CellCorners = geometry.getCorners(cellId);
const cellId = geometry.latLonToCellId(latLon);
const vertices = geometry.getVertices(cellId);
expect(vertices.length).toBe(4);
expect(corners.se.lat).toBeLessThan(corners.ne.lat);
expect(corners.se.lat).toBeLessThan(corners.nw.lat);
const polygon = vertices
.map(
(ll: LatLon): number[] => {
return [ll.lon, ll.lat];
});
expect(corners.sw.lat).toBeLessThan(corners.ne.lat);
expect(corners.sw.lat).toBeLessThan(corners.nw.lat);
expect(corners.sw.lon).toBeLessThan(corners.se.lon);
expect(corners.sw.lon).toBeLessThan(corners.ne.lon);
expect(corners.nw.lon).toBeLessThan(corners.se.lon);
expect(corners.nw.lon).toBeLessThan(corners.ne.lon);
expect(isClockwise(polygon)).toBe(true);
}
});
});

10
spec/helper/TestMath.ts Normal file
View File

@ -0,0 +1,10 @@
export function isClockwise(polygon: number[][]): boolean {
if (polygon.length < 3) { return false; };
let edgeSum = 0;
for (let i = 0; i < polygon.length; ++i) {
const [x1, y1] = polygon[i];
const [x2, y2] = polygon[(i + 1) % polygon.length];
edgeSum += (x2 - x1) * (y2 + y1);
}
return edgeSum > 0;
}

View File

@ -2,7 +2,7 @@ import * as geohash from "latlon-geohash";
import { geodeticToEnu } from "../geo/GeoCoords";
import { GeometryProviderBase } from "./GeometryProviderBase";
import { CellCorners, CellNeighbors } from "./interfaces/CellCorners";
import { CellNeighbors } from "./interfaces/CellCorners";
import { LatLon } from "./interfaces/LatLon";
@ -51,17 +51,17 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
}
/** @inheritdoc */
public getCorners(cellId: string): CellCorners {
const bounds: geohash.Bounds = geohash.bounds(cellId);
const nw: LatLon = { lat: bounds.ne.lat, lon: bounds.sw.lon };
const ne: LatLon = { lat: bounds.ne.lat, lon: bounds.ne.lon };
const se: LatLon = { lat: bounds.ne.lat, lon: bounds.sw.lon };
const sw: LatLon = { lat: bounds.sw.lat, lon: bounds.sw.lon };
return { nw, ne, se, sw };
public getVertices(cellId: string): LatLon[] {
const bounds = geohash.bounds(cellId);
const nw = { lat: bounds.ne.lat, lon: bounds.sw.lon };
const ne = { lat: bounds.ne.lat, lon: bounds.ne.lon };
const se = { lat: bounds.sw.lat, lon: bounds.ne.lon };
const sw = { lat: bounds.sw.lat, lon: bounds.sw.lon };
return [nw, ne, se, sw];
}
/** @inheritdoc */
public getNeighbors(cellId: string): CellNeighbors {
public getAdjacent(cellId: string): CellNeighbors {
return geohash.neighbours(cellId);
}
@ -73,13 +73,11 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
*
* @returns {string} The geohash tile for the lat, lon and precision.
*/
public latLonToCellId(
latLon: LatLon,
relativeLevel: number = 0): string {
public latLonToCellId(latLon: LatLon): string {
return geohash.encode(
latLon.lat,
latLon.lon,
this._level + relativeLevel);
this._level);
}
/**
@ -95,27 +93,20 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
*/
public latLonToCellIds(
latLon: LatLon,
threshold: number,
relativeLevel: number = 0): string[] {
threshold: number)
: string[] {
const h: string = geohash.encode(
latLon.lat, latLon.lon, this._level + relativeLevel);
const h = geohash.encode(
latLon.lat, latLon.lon, this._level);
const bounds: geohash.Bounds = geohash.bounds(h);
const corners: CellCorners = {
ne: { lat: bounds.ne.lat, lon: bounds.ne.lon },
nw: { lat: bounds.ne.lat, lon: bounds.sw.lon },
se: { lat: bounds.sw.lat, lon: bounds.ne.lon },
sw: { lat: bounds.sw.lat, lon: bounds.sw.lon },
};
const neighbours: CellNeighbors = this.getNeighbors(h);
const bounds = geohash.bounds(h);
const neighbours = this.getAdjacent(h);
return this._filterNeighbors(
latLon,
threshold,
h,
corners,
bounds,
neighbours);
}
@ -123,17 +114,17 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
latLon: LatLon,
threshold: number,
cellId: string,
corners: CellCorners,
bounds: geohash.Bounds,
neighbors: CellNeighbors): string[] {
const bl = [0, 0, 0];
const tr =
geodeticToEnu(
corners.ne.lat,
corners.ne.lon,
bounds.ne.lat,
bounds.ne.lon,
0,
corners.sw.lat,
corners.sw.lon,
bounds.sw.lat,
bounds.sw.lon,
0);
const position =
@ -141,8 +132,8 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
latLon.lat,
latLon.lon,
0,
corners.sw.lat,
corners.sw.lon,
bounds.sw.lat,
bounds.sw.lon,
0);
const left = position[0] - bl[0];
@ -150,12 +141,12 @@ export class GeohashGeometryProvider extends GeometryProviderBase {
const bottom = position[1] - bl[1];
const top = tr[1] - position[1];
const l: boolean = left < threshold;
const r: boolean = right < threshold;
const b: boolean = bottom < threshold;
const t: boolean = top < threshold;
const l = left < threshold;
const r = right < threshold;
const b = bottom < threshold;
const t = top < threshold;
const cellIds: string[] = [cellId];
const cellIds = [cellId];
if (t) {
cellIds.push(neighbors.n);

View File

@ -1,4 +1,4 @@
import { CellCorners, CellNeighbors } from "./interfaces/CellCorners";
import { CellNeighbors } from "./interfaces/CellCorners";
import { LatLon } from "./interfaces/LatLon";
import { MapillaryError } from "../error/MapillaryError";
@ -41,12 +41,17 @@ export abstract class GeometryProviderBase {
}
/**
* Get the corners of a cell.
* Get the vertices of a cell.
*
* @description The vertices form a clockwise polygon
* in the 2D latitude, longitude space. No assumption
* on the position of the first vertex relative to the
* others can be made.
*
* @param {string} cellId - Id of cell.
* @returns {CellCorners} Cell corners struct.
* @returns {Array<LatLon>} Clockwise polygon.
*/
public getCorners(cellId: string): CellCorners {
public getVertices(cellId: string): LatLon[] {
throw new MapillaryError("Not implemented");
}
@ -57,7 +62,7 @@ export abstract class GeometryProviderBase {
* @param {LatLon} ne - North east corner of the bounding box.
* @returns {CellCorners} Cell corners struct.
*/
public getNeighbors(cellId: string): CellNeighbors {
public getAdjacent(cellId: string): CellNeighbors {
throw new MapillaryError("Not implemented");
}
@ -68,8 +73,7 @@ export abstract class GeometryProviderBase {
* @returns {string} Cell id for the latitude, longitude.
*/
public latLonToCellId(
latLon: LatLon,
relativeLevel?: number)
latLon: LatLon)
: string {
throw new MapillaryError("Not implemented");
}
@ -88,8 +92,7 @@ export abstract class GeometryProviderBase {
*/
public latLonToCellIds(
latLon: LatLon,
threshold: number,
relativeLevel?: number)
threshold: number)
: string[] {
throw new MapillaryError("Not implemented");
}

View File

@ -2,10 +2,9 @@ import { S2 } from "s2-geometry";
import { enuToGeodetic } from "../geo/GeoCoords";
import { GeometryProviderBase } from "./GeometryProviderBase";
import { CellCorners, CellNeighbors } from "./interfaces/CellCorners";
import { CellNeighbors } from "./interfaces/CellCorners";
import { LatLon } from "./interfaces/LatLon";
/**
* @class S2GeometryProvider
*
@ -38,7 +37,7 @@ export class S2GeometryProvider extends GeometryProviderBase {
}
/** @inheritdoc */
public getNeighbors(cellId: string): CellNeighbors {
public getAdjacent(cellId: string): CellNeighbors {
const s2key = S2.idToKey(cellId);
const position = s2key.split('/')[1];
const level = position.length;
@ -60,29 +59,15 @@ export class S2GeometryProvider extends GeometryProviderBase {
}
/** @inheritdoc */
public getCorners(cellId: string): CellCorners {
public getVertices(cellId: string): LatLon[] {
const key = S2.idToKey(cellId);
const cell = S2.S2Cell.FromHilbertQuadKey(key);
const corners = cell.getCornerLatLngs();
let south = Number.POSITIVE_INFINITY;
let north = Number.NEGATIVE_INFINITY;
let west = Number.POSITIVE_INFINITY;
let east = Number.NEGATIVE_INFINITY;
for (let c of corners) {
if (c.lat < south) { south = c.lat; }
if (c.lat > north) { north = c.lat; }
if (c.lng < west) { west = c.lng; }
if (c.lng > east) { east = c.lng; }
}
return {
ne: { lat: north, lon: east },
nw: { lat: north, lon: west },
se: { lat: south, lon: east },
sw: { lat: south, lon: west },
};
return cell
.getCornerLatLngs()
.map(
(c: S2.ILatLng): LatLon => {
return { lat: c.lat, lon: c.lng };
});
}
/** @inheritdoc */
@ -93,7 +78,7 @@ export class S2GeometryProvider extends GeometryProviderBase {
/** @inheritdoc */
public latLonToCellIds(latLon: LatLon, threshold: number): string[] {
const cellId = this._latLonToId(latLon, this._level);
const neighbors = this.getNeighbors(cellId);
const neighbors = this.getAdjacent(cellId);
const corners =
this._getLatLonBoundingBoxCorners(latLon, threshold);
@ -129,7 +114,10 @@ export class S2GeometryProvider extends GeometryProviderBase {
}
private _getLatLonBoundingBoxCorners(
latLon: LatLon, threshold: number): LatLon[] {
latLon: LatLon,
threshold: number)
: LatLon[] {
return [
[-threshold, threshold, 0],
[threshold, threshold, 0],

View File

@ -31,7 +31,6 @@ import { Container } from "../../viewer/Container";
import { Navigator } from "../../viewer/Navigator";
import {
CellNeighbors,
CellCorners,
} from "../../api/interfaces/CellCorners";
import { ClusterReconstructionContract }
from "../../api/contracts/ClusterReconstructionContract";
@ -56,6 +55,7 @@ import { SpatialDataScene } from "./SpatialDataScene";
import { SpatialDataCache } from "./SpatialDataCache";
import { CameraType } from "../../geo/interfaces/CameraType";
import { geodeticToEnu } from "../../geo/GeoCoords";
import { LatLon } from "../../api/interfaces/LatLon";
type IntersectEvent = MouseEvent | FocusEvent;
@ -205,7 +205,7 @@ export class SpatialDataComponent extends Component<SpatialDataConfiguration> {
observableOf([
hash,
this._navigator.api.data.geometry
.getNeighbors(hash)[<keyof CellNeighbors>direction]]) :
.getAdjacent(hash)[<keyof CellNeighbors>direction]]) :
observableOf(this._computeTiles(hash, direction));
}),
publish<string[]>(),
@ -580,7 +580,7 @@ export class SpatialDataComponent extends Component<SpatialDataConfiguration> {
for (const hash of currentHashes) {
const hashNeighbours: CellNeighbors =
this._navigator.api.data.geometry.getNeighbors(hash);
this._navigator.api.data.geometry.getAdjacent(hash);
for (const direction in hashNeighbours) {
if (!hashNeighbours.hasOwnProperty(direction)) {
@ -613,26 +613,21 @@ export class SpatialDataComponent extends Component<SpatialDataConfiguration> {
}
private _computeTileBBox(hash: string, reference: LatLonAlt): number[][] {
const corners: CellCorners =
this._navigator.api.data.geometry.getCorners(hash);
const vertices =
this._navigator.api.data.geometry
.getVertices(hash)
.map(
(vertex: LatLon): number[] => {
return geodeticToEnu(
vertex.lat,
vertex.lon,
0,
reference.lat,
reference.lon,
reference.alt);
});
const sw = geodeticToEnu(
corners.sw.lat,
corners.sw.lon,
0,
reference.lat,
reference.lon,
reference.alt);
const ne = geodeticToEnu(
corners.ne.lat,
corners.ne.lon,
0,
reference.lat,
reference.lon,
reference.alt);
return [sw, ne];
return vertices;
}
private _createTransform(node: Node, reference: LatLonAlt): Transform {
@ -680,7 +675,7 @@ export class SpatialDataComponent extends Component<SpatialDataConfiguration> {
}
const neighbours: CellNeighbors =
this._navigator.api.data.geometry.getNeighbors(currentHash);
this._navigator.api.data.geometry.getAdjacent(currentHash);
const directionIndex: number = directions.indexOf(direction);
const length: number = directions.length;

View File

@ -1,12 +1,14 @@
import * as THREE from "three";
import { PointContract } from "../../api/contracts/PointContract";
import { ClusterReconstructionContract } from "../../api/contracts/ClusterReconstructionContract";
import { ClusterReconstructionContract }
from "../../api/contracts/ClusterReconstructionContract";
import { MapillaryError } from "../../error/MapillaryError";
import { isSpherical } from "../../geo/Geo";
import { Transform } from "../../geo/Transform";
import { FilterFunction } from "../../graph/FilterCreator";
import { Node } from "../../graph/Node";
import { SpatialDataConfiguration } from "../interfaces/SpatialDataConfiguration";
import { SpatialDataConfiguration }
from "../interfaces/SpatialDataConfiguration";
import { CameraVisualizationMode } from "./CameraVisualizationMode";
import { OriginalPositionMode } from "./OriginalPositionMode";
@ -65,11 +67,14 @@ abstract class CameraFrameBase extends THREE.Object3D {
}
protected _createBufferGeometry(
positions: number[][]): THREE.BufferGeometry {
const positionAttribute = new THREE.BufferAttribute(
new Float32Array(3 * positions.length), 3)
const colorAttribute = new THREE.BufferAttribute(
new Float32Array(3 * positions.length), 3)
positions: number[][])
: THREE.BufferGeometry {
const positionAttribute =
new THREE.BufferAttribute(
new Float32Array(3 * positions.length), 3);
const colorAttribute =
new THREE.BufferAttribute(
new Float32Array(3 * positions.length), 3);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", positionAttribute);
geometry.setAttribute("color", colorAttribute);
@ -80,8 +85,8 @@ abstract class CameraFrameBase extends THREE.Object3D {
origin: number[],
relativePositions: number[][],
scale: number,
color: string):
CameraFrameLine {
color: string)
: CameraFrameLine {
const geometry = this._createBufferGeometry(relativePositions);
const material = new THREE.LineBasicMaterial({
vertexColors: true,
@ -97,7 +102,8 @@ abstract class CameraFrameBase extends THREE.Object3D {
protected _updateColorAttribute(
frame: CameraFrameLine | CameraFrameLineSegments,
color: string): void {
color: string)
: void {
const [r, g, b] = new THREE.Color(color).toArray();
const colorAttribute =
<THREE.BufferAttribute>frame.geometry.attributes.color;
@ -122,7 +128,8 @@ abstract class CameraFrameBase extends THREE.Object3D {
protected _updatePositionAttribute(
frame: CameraFrameLine | CameraFrameLineSegments,
scale: number): void {
scale: number)
: void {
const positionAttribute =
<THREE.BufferAttribute>frame.geometry.attributes.position;
const positions = <Float32Array>positionAttribute.array;
@ -188,7 +195,8 @@ class PerspectiveCameraFrame extends CameraFrameBase {
private _calculateRelativeDiagonals(
transform: Transform,
origin: number[]): number[][] {
origin: number[])
: number[][] {
const depth = this._originalSize;
const [topLeft, topRight, bottomRight, bottomLeft] =
this._makeRelative(
@ -211,8 +219,10 @@ class PerspectiveCameraFrame extends CameraFrameBase {
return vertices;
}
private _calculateRelativeFrame(transform: Transform, origin: number[]):
number[][] {
private _calculateRelativeFrame(
transform: Transform,
origin: number[])
: number[][] {
const vertices2d: number[][] = [];
const vertical = this._verticalFrameSamples;
const horizontal = this._horizontalFrameSamples;
@ -235,7 +245,8 @@ class PerspectiveCameraFrame extends CameraFrameBase {
transform: Transform,
scale: number,
origin: number[],
color: string): CameraFrameLineSegments {
color: string)
: CameraFrameLineSegments {
const positions = this._calculateRelativeDiagonals(transform, origin);
const geometry = this._createBufferGeometry(positions);
const material = new THREE.LineBasicMaterial({
@ -256,12 +267,12 @@ class PerspectiveCameraFrame extends CameraFrameBase {
transform: Transform,
scale: number,
origin: number[],
color: string): CameraFrameLine {
color: string)
: CameraFrameLine {
const positions = this._calculateRelativeFrame(transform, origin);
return this._createCameraFrame(origin, positions, scale, color);
}
private _interpolate(a: number, b: number, alpha: number): number {
return a + alpha * (b - a);
}
@ -269,7 +280,8 @@ class PerspectiveCameraFrame extends CameraFrameBase {
private _subsample(
p1: number[],
p2: number[],
subsamples: number): number[][] {
subsamples: number)
: number[][] {
if (subsamples < 1) {
return [p1, p2];
}
@ -330,8 +342,10 @@ class SphericalCameraFrame extends CameraFrameBase {
this.add(axis, lat, lon1, lon2, lon3, lon4);
}
private _calculateRelativeAxis(transform: Transform, origin: number[]):
number[][] {
private _calculateRelativeAxis(
transform: Transform,
origin: number[])
: number[][] {
const depth = this._originalSize;
const north: number[] = transform.unprojectBasic([0.5, 0], depth * 1.1);
const south: number[] = transform.unprojectBasic([0.5, 1], depth * 0.8);
@ -343,7 +357,8 @@ class SphericalCameraFrame extends CameraFrameBase {
basicY: number,
numVertices: number,
transform: Transform,
origin: number[]): number[][] {
origin: number[])
: number[][] {
const depth = 0.8 * this._originalSize;
const positions: number[][] = [];
@ -362,7 +377,8 @@ class SphericalCameraFrame extends CameraFrameBase {
basicX: number,
numVertices: number,
transform: Transform,
origin: number[]): number[][] {
origin: number[])
: number[][] {
const scaledDepth = 0.8 * this._originalSize;
const positions: number[][] = [];
@ -381,7 +397,8 @@ class SphericalCameraFrame extends CameraFrameBase {
transform: Transform,
scale: number,
origin: number[],
color: string): CameraFrameLine {
color: string)
: CameraFrameLine {
const positions = this._calculateRelativeAxis(transform, origin);
return this._createCameraFrame(origin, positions, scale, color);
}
@ -392,7 +409,8 @@ class SphericalCameraFrame extends CameraFrameBase {
transform: Transform,
scale: number,
origin: number[],
color: string): CameraFrameLine {
color: string)
: CameraFrameLine {
const positions = this._calculateRelativeLatitude(
basicY, numVertices, transform, origin);
return this._createCameraFrame(origin, positions, scale, color);
@ -404,7 +422,8 @@ class SphericalCameraFrame extends CameraFrameBase {
transform: Transform,
scale: number,
origin: number[],
color: string): CameraFrameLine {
color: string)
: CameraFrameLine {
const positions = this._calculateRelativeLongitude(
basicX, numVertices, transform, origin);
return this._createCameraFrame(origin, positions, scale, color);
@ -450,7 +469,8 @@ class ClusterPoints extends THREE.Points {
private _getArrays(
reconstruction: ClusterReconstructionContract,
translation: number[]): [Float32Array, Float32Array] {
translation: number[])
: [Float32Array, Float32Array] {
const points = Object
.keys(reconstruction.points)
.map(
@ -482,9 +502,9 @@ class ClusterPoints extends THREE.Points {
}
class TileLine extends THREE.Line {
constructor(bbox: number[][]) {
constructor(vertices: number[][]) {
super();
this.geometry = this._createGeometry(bbox);
this.geometry = this._createGeometry(vertices);
this.material = new THREE.LineBasicMaterial();
}
@ -493,27 +513,18 @@ class TileLine extends THREE.Line {
(<THREE.Material>this.material).dispose();
}
private _createGeometry(bbox: number[][]): THREE.BufferGeometry {
const sw: number[] = bbox[0];
const ne: number[] = bbox[1];
const vertices = [
sw.slice(),
[sw[0], ne[1], (sw[2] + ne[2]) / 2],
ne.slice(),
[ne[0], sw[1], (sw[2] + ne[2]) / 2],
sw.slice(),
];
const positions = new Float32Array(3 * vertices.length);
private _createGeometry(vertices: number[][]): THREE.BufferGeometry {
const polygon = vertices.slice()
polygon.push(vertices[0]);
const positions = new Float32Array(3 * (vertices.length + 1));
let index = 0;
for (const vertex of vertices) {
for (const vertex of polygon) {
positions[index++] = vertex[0];
positions[index++] = vertex[1];
positions[index++] = vertex[2];
}
const geometry: THREE.BufferGeometry = new THREE.BufferGeometry();
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3));
@ -565,8 +576,8 @@ class PositionLine extends THREE.Line {
private _createGeometry(
transform: Transform,
originalPosition: number[],
altitude: number):
THREE.BufferGeometry {
altitude: number)
: THREE.BufferGeometry {
const vertices = [
[
originalPosition[0],
@ -583,7 +594,7 @@ class PositionLine extends THREE.Line {
positions[index++] = vertex[2];
}
const geometry: THREE.BufferGeometry = new THREE.BufferGeometry();
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3));
@ -1112,12 +1123,12 @@ export class SpatialDataScene {
this._needsRender = true;
}
public addTile(bbox: number[][], cellId: string): void {
public addTile(vertices: number[][], cellId: string): void {
if (this.hasTile(cellId)) {
return;
}
const tile = new TileLine(bbox);
const tile = new TileLine(vertices);
this._tiles[cellId] = new THREE.Object3D();
this._tiles[cellId].visible = this._tilesVisible;
this._tiles[cellId].add(tile);

View File

@ -41,6 +41,5 @@ export { SpatialImageEnt } from "../api/ents/SpatialImageEnt";
export { URLEnt } from "../api/ents/URLEnt";
// Type
export { CellCorners } from "../api/interfaces/CellCorners";
export { LatLon } from "../api/interfaces/LatLon";
export { LatLonAlt } from "../api/interfaces/LatLonAlt";