UNPKG

mapillary-js

Version:

WebGL JavaScript library for displaying street level imagery from mapillary.com

236 lines (202 loc) 7.18 kB
import * as geohash from "latlon-geohash"; import * as THREE from "three"; import {ILatLon} from "../API"; import {GraphMapillaryError} from "../Error"; import {GeoCoords} from "../Geo"; /** * @class GraphCalculator * * @classdesc Represents a calculator for graph entities. */ export class GraphCalculator { private _geoCoords: GeoCoords; /** * Create a new graph calculator instance. * * @param {GeoCoords} geoCoords - Geo coords instance. */ constructor(geoCoords?: GeoCoords) { this._geoCoords = geoCoords != null ? geoCoords : new GeoCoords(); } /** * Encode the geohash tile for geodetic coordinates. * * @param {ILatLon} latlon - Latitude and longitude to encode. * @param {number} precision - Precision of the encoding. * * @returns {string} The geohash tile for the lat, lon and precision. */ public encodeH(latLon: ILatLon, precision: number = 7): string { return geohash.encode(latLon.lat, latLon.lon, precision); } /** * Encode the geohash tiles within a threshold from a position * using Manhattan distance. * * @param {ILatLon} latlon - Latitude and longitude to encode. * @param {number} precision - Precision of the encoding. * @param {number} threshold - Threshold of the encoding in meters. * * @returns {string} The geohash tiles reachable within the threshold. */ public encodeHs(latLon: ILatLon, precision: number = 7, threshold: number = 20): string[] { let h: string = geohash.encode(latLon.lat, latLon.lon, precision); let bounds: geohash.Bounds = geohash.bounds(h); let ne: geohash.Point = bounds.ne; let sw: geohash.Point = bounds.sw; let neighbours: geohash.Neighbours = geohash.neighbours(h); let bl: number[] = [0, 0, 0]; let tr: number[] = this._geoCoords.geodeticToEnu( ne.lat, ne.lon, 0, sw.lat, sw.lon, 0); let position: number[] = this._geoCoords.geodeticToEnu( latLon.lat, latLon.lon, 0, sw.lat, sw.lon, 0); let left: number = position[0] - bl[0]; let right: number = tr[0] - position[0]; let bottom: number = position[1] - bl[1]; let top: number = tr[1] - position[1]; let l: boolean = left < threshold; let r: boolean = right < threshold; let b: boolean = bottom < threshold; let t: boolean = top < threshold; let hs: string[] = [h]; if (t) { hs.push(neighbours.n); } if (t && l) { hs.push(neighbours.nw); } if (l) { hs.push(neighbours.w); } if (l && b) { hs.push(neighbours.sw); } if (b) { hs.push(neighbours.s); } if (b && r) { hs.push(neighbours.se); } if (r) { hs.push(neighbours.e); } if (r && t) { hs.push(neighbours.ne); } return hs; } /** * Encode the minimum set of geohash tiles containing a bounding box. * * @description The current algorithm does expect the bounding box * to be sufficiently small to be contained in an area with the size * of maximally four tiles. Up to nine adjacent tiles may be returned. * The method currently uses the largest side as the threshold leading to * more tiles being returned than needed in edge cases. * * @param {ILatLon} sw - South west corner of bounding box. * @param {ILatLon} ne - North east corner of bounding box. * @param {number} precision - Precision of the encoding. * * @returns {string} The geohash tiles containing the bounding box. */ public encodeHsFromBoundingBox(sw: ILatLon, ne: ILatLon, precision: number = 7): string[] { if (ne.lat <= sw.lat || ne.lon <= sw.lon) { throw new GraphMapillaryError("North east needs to be top right of south west"); } const centerLat: number = (sw.lat + ne.lat) / 2; const centerLon: number = (sw.lon + ne.lon) / 2; const enu: number[] = this._geoCoords.geodeticToEnu( ne.lat, ne.lon, 0, centerLat, centerLon, 0); const threshold: number = Math.max(enu[0], enu[1]); return this.encodeHs({ lat: centerLat, lon: centerLon }, precision, threshold); } /** * Get the bounding box corners for a circle with radius of a threshold * with center in a geodetic position. * * @param {ILatLon} latlon - Latitude and longitude to encode. * @param {number} threshold - Threshold distance from the position in meters. * * @returns {Array<ILatLon>} The south west and north east corners of the * bounding box. */ public boundingBoxCorners(latLon: ILatLon, threshold: number): [ILatLon, ILatLon] { let bl: number[] = this._geoCoords.enuToGeodetic( -threshold, -threshold, 0, latLon.lat, latLon.lon, 0); let tr: number[] = this._geoCoords.enuToGeodetic( threshold, threshold, 0, latLon.lat, latLon.lon, 0); return [ { lat: bl[0], lon: bl[1] }, { lat: tr[0], lon: tr[1] }, ]; } /** * Convert a compass angle to an angle axis rotation vector. * * @param {number} compassAngle - The compass angle in degrees. * @param {number} orientation - The orientation of the original image. * * @returns {Array<number>} Angle axis rotation vector. */ public rotationFromCompass(compassAngle: number, orientation: number): number[] { let x: number = 0; let y: number = 0; let z: number = 0; switch (orientation) { case 1: x = Math.PI / 2; break; case 3: x = -Math.PI / 2; z = Math.PI; break; case 6: y = -Math.PI / 2; z = -Math.PI / 2; break; case 8: y = Math.PI / 2; z = Math.PI / 2; break; default: break; } let rz: THREE.Matrix4 = new THREE.Matrix4().makeRotationZ(z); let euler: THREE.Euler = new THREE.Euler(x, y, compassAngle * Math.PI / 180, "XYZ"); let re: THREE.Matrix4 = new THREE.Matrix4().makeRotationFromEuler(euler); let rotation: THREE.Vector4 = new THREE.Vector4().setAxisAngleFromRotationMatrix(<any>re.multiply(rz)); return rotation.multiplyScalar(rotation.w).toArray().slice(0, 3); } } export default GraphCalculator;