UNPKG

mapillary-js

Version:

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

690 lines (607 loc) 25.1 kB
import {GeometryTagError, VertexGeometry} from "../../../Component"; import {Transform} from "../../../Geo"; /** * @class RectGeometry * * @classdesc Represents a rectangle geometry in the 2D basic image coordinate system. * * @example * ``` * var basicRect = [0.5, 0.3, 0.7, 0.4]; * var rectGeometry = new Mapillary.TagComponent.RectGeometry(basicRect); * ``` */ export class RectGeometry extends VertexGeometry { private _anchorIndex: number; private _inverted: boolean; private _rect: number[]; /** * Create a rectangle geometry. * * @constructor * @param {Array<number>} rect - An array representing the top-left and bottom-right * corners of the rectangle in basic coordinates. Ordered according to [x0, y0, x1, y1]. * * @throws {GeometryTagError} Rectangle coordinates must be valid basic coordinates. */ constructor(rect: number[]) { super(); if (rect[1] > rect[3]) { throw new GeometryTagError("Basic Y coordinates values can not be inverted."); } for (let coord of rect) { if (coord < 0 || coord > 1) { throw new GeometryTagError("Basic coordinates must be on the interval [0, 1]."); } } this._anchorIndex = undefined; this._rect = rect.slice(0, 4); this._inverted = this._rect[0] > this._rect[2]; } /** * Get anchor index property. * * @returns {number} Index representing the current anchor property if * achoring indexing has been initialized. If anchor indexing has not been * initialized or has been terminated undefined will be returned. * @ignore */ public get anchorIndex(): number { return this._anchorIndex; } /** * Get inverted property. * * @returns {boolean} Boolean determining whether the rect geometry is * inverted. For panoramas the rect geometrye may be inverted. * @ignore */ public get inverted(): boolean { return this._inverted; } /** * Get rect property. * * @returns {Array<number>} Array representing the top-left and bottom-right * corners of the rectangle in basic coordinates. */ public get rect(): number[] { return this._rect; } /** * Initialize anchor indexing to enable setting opposite vertex. * * @param {number} [index] - The index of the vertex to use as anchor. * * @throws {Error} If anchor indexing has already been initialized. * @throws {Error} If index is not valid (0 to 3). * @ignore */ public initializeAnchorIndexing(index?: number): void { if (this._anchorIndex !== undefined) { throw new Error("Anchor indexing is already initialized."); } if (index < 0 || index > 3) { throw new Error(`Invalid anchor index: ${index}.`); } this._anchorIndex = index === undefined ? 0 : index; } /** * Terminate anchor indexing to disable setting pposite vertex. * @ignore */ public terminateAnchorIndexing(): void { this._anchorIndex = undefined; } /** * Set the value of the vertex opposite to the anchor in the polygon * representation of the rectangle. * * @description Setting the opposite vertex may change the anchor index. * * @param {Array<number>} opposite - The new value of the vertex opposite to the anchor. * @param {Transform} transform - The transform of the node related to the rectangle. * * @throws {Error} When anchor indexing has not been initialized. * @ignore */ public setOppositeVertex2d(opposite: number[], transform: Transform): void { if (this._anchorIndex === undefined) { throw new Error("Anchor indexing needs to be initialized."); } const changed: number[] = [ Math.max(0, Math.min(1, opposite[0])), Math.max(0, Math.min(1, opposite[1])), ]; const original: number[] = this._rect.slice(); const anchor: number[] = this._anchorIndex === 0 ? [original[0], original[3]] : this._anchorIndex === 1 ? [original[0], original[1]] : this._anchorIndex === 2 ? [original[2], original[1]] : [original[2], original[3]]; if (transform.fullPano) { const deltaX: number = this._anchorIndex < 2 ? changed[0] - original[2] : changed[0] - original[0]; if (!this._inverted && this._anchorIndex < 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) { // right side passes boundary rightward this._inverted = true; this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } else if (!this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) { // left side passes right side and boundary rightward this._inverted = true; this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } else if (this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[0] > 0.75 && deltaX < -0.5) { this._inverted = false; if (anchor[0] > changed[0]) { // left side passes boundary rightward this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } else { // left side passes right side and boundary rightward this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } } else if (!this._inverted && this._anchorIndex >= 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) { // left side passes boundary leftward this._inverted = true; this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } else if (!this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) { // right side passes left side and boundary leftward this._inverted = true; this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } else if (this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[2] < 0.25 && deltaX > 0.5) { this._inverted = false; if (anchor[0] > changed[0]) { // right side passes boundary leftward this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } else { // right side passes left side and boundary leftward this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } } else if (this._inverted && this._anchorIndex < 2 && changed[0] > original[0]) { // inverted and right side passes left side completing a loop this._inverted = false; this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } else if (this._inverted && this._anchorIndex >= 2 && changed[0] < original[2]) { // inverted and left side passes right side completing a loop this._inverted = false; this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } else if (this._inverted) { // if still inverted only top and bottom can switch if (this._anchorIndex < 2) { this._anchorIndex = anchor[1] > changed[1] ? 0 : 1; } else { this._anchorIndex = anchor[1] > changed[1] ? 3 : 2; } } else { // if still not inverted treat as non full pano if (anchor[0] <= changed[0] && anchor[1] > changed[1]) { this._anchorIndex = 0; } else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) { this._anchorIndex = 1; } else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) { this._anchorIndex = 2; } else { this._anchorIndex = 3; } } const rect: number[] = []; if (this._anchorIndex === 0) { rect[0] = anchor[0]; rect[1] = changed[1]; rect[2] = changed[0]; rect[3] = anchor[1]; } else if (this._anchorIndex === 1) { rect[0] = anchor[0]; rect[1] = anchor[1]; rect[2] = changed[0]; rect[3] = changed[1]; } else if (this._anchorIndex === 2) { rect[0] = changed[0]; rect[1] = anchor[1]; rect[2] = anchor[0]; rect[3] = changed[1]; } else { rect[0] = changed[0]; rect[1] = changed[1]; rect[2] = anchor[0]; rect[3] = anchor[1]; } if (!this._inverted && rect[0] > rect[2] || this._inverted && rect[0] < rect[2]) { rect[0] = original[0]; rect[2] = original[2]; } if (rect[1] > rect[3]) { rect[1] = original[1]; rect[3] = original[3]; } this._rect[0] = rect[0]; this._rect[1] = rect[1]; this._rect[2] = rect[2]; this._rect[3] = rect[3]; } else { if (anchor[0] <= changed[0] && anchor[1] > changed[1]) { this._anchorIndex = 0; } else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) { this._anchorIndex = 1; } else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) { this._anchorIndex = 2; } else { this._anchorIndex = 3; } const rect: number[] = []; if (this._anchorIndex === 0) { rect[0] = anchor[0]; rect[1] = changed[1]; rect[2] = changed[0]; rect[3] = anchor[1]; } else if (this._anchorIndex === 1) { rect[0] = anchor[0]; rect[1] = anchor[1]; rect[2] = changed[0]; rect[3] = changed[1]; } else if (this._anchorIndex === 2) { rect[0] = changed[0]; rect[1] = anchor[1]; rect[2] = anchor[0]; rect[3] = changed[1]; } else { rect[0] = changed[0]; rect[1] = changed[1]; rect[2] = anchor[0]; rect[3] = anchor[1]; } if (rect[0] > rect[2]) { rect[0] = original[0]; rect[2] = original[2]; } if (rect[1] > rect[3]) { rect[1] = original[1]; rect[3] = original[3]; } this._rect[0] = rect[0]; this._rect[1] = rect[1]; this._rect[2] = rect[2]; this._rect[3] = rect[3]; } this._notifyChanged$.next(this); } /** * Set the value of a vertex in the polygon representation of the rectangle. * * @description The polygon is defined to have the first vertex at the * bottom-left corner with the rest of the vertices following in clockwise order. * * @param {number} index - The index of the vertex to be set. * @param {Array<number>} value - The new value of the vertex. * @param {Transform} transform - The transform of the node related to the rectangle. * @ignore */ public setVertex2d(index: number, value: number[], transform: Transform): void { let original: number[] = this._rect.slice(); let changed: number[] = [ Math.max(0, Math.min(1, value[0])), Math.max(0, Math.min(1, value[1])), ]; let rect: number[] = []; if (index === 0) { rect[0] = changed[0]; rect[1] = original[1]; rect[2] = original[2]; rect[3] = changed[1]; } else if (index === 1) { rect[0] = changed[0]; rect[1] = changed[1]; rect[2] = original[2]; rect[3] = original[3]; } else if (index === 2) { rect[0] = original[0]; rect[1] = changed[1]; rect[2] = changed[0]; rect[3] = original[3]; } else if (index === 3) { rect[0] = original[0]; rect[1] = original[1]; rect[2] = changed[0]; rect[3] = changed[1]; } if (transform.fullPano) { let passingBoundaryLeftward: boolean = index < 2 && changed[0] > 0.75 && original[0] < 0.25 || index >= 2 && this._inverted && changed[0] > 0.75 && original[2] < 0.25; let passingBoundaryRightward: boolean = index < 2 && this._inverted && changed[0] < 0.25 && original[0] > 0.75 || index >= 2 && changed[0] < 0.25 && original[2] > 0.75; if (passingBoundaryLeftward || passingBoundaryRightward) { this._inverted = !this._inverted; } else { if (rect[0] - original[0] < -0.25) { rect[0] = original[0]; } if (rect[2] - original[2] > 0.25) { rect[2] = original[2]; } } if (!this._inverted && rect[0] > rect[2] || this._inverted && rect[0] < rect[2]) { rect[0] = original[0]; rect[2] = original[2]; } } else { if (rect[0] > rect[2]) { rect[0] = original[0]; rect[2] = original[2]; } } if (rect[1] > rect[3]) { rect[1] = original[1]; rect[3] = original[3]; } this._rect[0] = rect[0]; this._rect[1] = rect[1]; this._rect[2] = rect[2]; this._rect[3] = rect[3]; this._notifyChanged$.next(this); } /** @ignore */ public setCentroid2d(value: number[], transform: Transform): void { let original: number[] = this._rect.slice(); let x0: number = original[0]; let x1: number = this._inverted ? original[2] + 1 : original[2]; let y0: number = original[1]; let y1: number = original[3]; let centerX: number = x0 + (x1 - x0) / 2; let centerY: number = y0 + (y1 - y0) / 2; let translationX: number = 0; if (transform.gpano != null && transform.gpano.CroppedAreaImageWidthPixels === transform.gpano.FullPanoWidthPixels) { translationX = this._inverted ? value[0] + 1 - centerX : value[0] - centerX; } else { let minTranslationX: number = -x0; let maxTranslationX: number = 1 - x1; translationX = Math.max(minTranslationX, Math.min(maxTranslationX, value[0] - centerX)); } let minTranslationY: number = -y0; let maxTranslationY: number = 1 - y1; let translationY: number = Math.max(minTranslationY, Math.min(maxTranslationY, value[1] - centerY)); this._rect[0] = original[0] + translationX; this._rect[1] = original[1] + translationY; this._rect[2] = original[2] + translationX; this._rect[3] = original[3] + translationY; if (this._rect[0] < 0) { this._rect[0] += 1; this._inverted = !this._inverted; } else if (this._rect[0] > 1) { this._rect[0] -= 1; this._inverted = !this._inverted; } if (this._rect[2] < 0) { this._rect[2] += 1; this._inverted = !this._inverted; } else if (this._rect[2] > 1) { this._rect[2] -= 1; this._inverted = !this._inverted; } this._notifyChanged$.next(this); } /** * Get the 3D coordinates for the vertices of the rectangle with * interpolated points along the lines. * * @param {Transform} transform - The transform of the node related to * the rectangle. * @returns {Array<Array<number>>} Polygon array of 3D world coordinates * representing the rectangle. * @ignore */ public getPoints3d(transform: Transform): number[][] { return this._getPoints2d() .map( (point: number[]) => { return transform.unprojectBasic(point, 200); }); } /** * Get the coordinates of a vertex from the polygon representation of the geometry. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. The method shifts the right side * coordinates of the rectangle by one unit to ensure that the vertices are ordered * clockwise. * * @param {number} index - Vertex index. * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex. * @ignore */ public getVertex2d(index: number): number[] { return this._rectToVertices2d(this._rect)[index]; } /** * Get the coordinates of a vertex from the polygon representation of the geometry. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. The coordinates will not be shifted * so they may not appear in clockwise order when layed out on the plane. * * @param {number} index - Vertex index. * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex. * @ignore */ public getNonAdjustedVertex2d(index: number): number[] { return this._rectToNonAdjustedVertices2d(this._rect)[index]; } /** * Get a vertex from the polygon representation of the 3D coordinates for the * vertices of the geometry. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. * * @param {number} index - Vertex index. * @param {Transform} transform - The transform of the node related to the geometry. * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing * the vertices of the geometry. * @ignore */ public getVertex3d(index: number, transform: Transform): number[] { return transform.unprojectBasic(this._rectToVertices2d(this._rect)[index], 200); } /** * Get a polygon representation of the 2D basic coordinates for the vertices of the rectangle. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. * * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates representing * the rectangle vertices. * @ignore */ public getVertices2d(): number[][] { return this._rectToVertices2d(this._rect); } /** * Get a polygon representation of the 3D coordinates for the vertices of the rectangle. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. * * @param {Transform} transform - The transform of the node related to the rectangle. * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing * the rectangle vertices. * @ignore */ public getVertices3d(transform: Transform): number[][] { return this._rectToVertices2d(this._rect) .map( (vertex: number[]) => { return transform.unprojectBasic(vertex, 200); }); } /** @ignore */ public getCentroid2d(): number[] { const rect: number[] = this._rect; const x0: number = rect[0]; const x1: number = this._inverted ? rect[2] + 1 : rect[2]; const y0: number = rect[1]; const y1: number = rect[3]; const centroidX: number = (x0 + x1) / 2; const centroidY: number = (y0 + y1) / 2; return [centroidX, centroidY]; } /** @ignore */ public getCentroid3d(transform: Transform): number[] { const centroid2d: number[] = this.getCentroid2d(); return transform.unprojectBasic(centroid2d, 200); } /** * @ignore */ public getPoleOfInaccessibility2d(): number[] { return this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect)); } /** @ignore */ public getPoleOfInaccessibility3d(transform: Transform): number[] { let pole2d: number[] = this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect)); return transform.unprojectBasic(pole2d, 200); } /** @ignore */ public getTriangles3d(transform: Transform): number[] { return transform.fullPano ? [] : this._triangulate( this._project(this._getPoints2d(), transform), this.getPoints3d(transform)); } /** * Check if a particular bottom-right value is valid according to the current * rectangle coordinates. * * @param {Array<number>} bottomRight - The bottom-right coordinates to validate * @returns {boolean} Value indicating whether the provided bottom-right coordinates * are valid. * @ignore */ public validate(bottomRight: number[]): boolean { let rect: number[] = this._rect; if (!this._inverted && bottomRight[0] < rect[0] || bottomRight[0] - rect[2] > 0.25 || bottomRight[1] < rect[1]) { return false; } return true; } /** * Get the 2D coordinates for the vertices of the rectangle with * interpolated points along the lines. * * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates * representing the rectangle. */ private _getPoints2d(): number[][] { let vertices2d: number[][] = this._rectToVertices2d(this._rect); let sides: number = vertices2d.length - 1; let sections: number = 10; let points2d: number[][] = []; for (let i: number = 0; i < sides; ++i) { let startX: number = vertices2d[i][0]; let startY: number = vertices2d[i][1]; let endX: number = vertices2d[i + 1][0]; let endY: number = vertices2d[i + 1][1]; let intervalX: number = (endX - startX) / (sections - 1); let intervalY: number = (endY - startY) / (sections - 1); for (let j: number = 0; j < sections; ++j) { let point: number[] = [ startX + j * intervalX, startY + j * intervalY, ]; points2d.push(point); } } return points2d; } /** * Convert the top-left, bottom-right representation of a rectangle to a polygon * representation of the vertices starting at the bottom-left corner going * clockwise. * * @description The method shifts the right side coordinates of the rectangle * by one unit to ensure that the vertices are ordered clockwise. * * @param {Array<number>} rect - Top-left, bottom-right representation of a * rectangle. * @returns {Array<Array<number>>} Polygon representation of the vertices of the * rectangle. */ private _rectToVertices2d(rect: number[]): number[][] { return [ [rect[0], rect[3]], [rect[0], rect[1]], [this._inverted ? rect[2] + 1 : rect[2], rect[1]], [this._inverted ? rect[2] + 1 : rect[2], rect[3]], [rect[0], rect[3]], ]; } /** * Convert the top-left, bottom-right representation of a rectangle to a polygon * representation of the vertices starting at the bottom-left corner going * clockwise. * * @description The first vertex represents the bottom-left corner with the rest of * the vertices following in clockwise order. The coordinates will not be shifted * to ensure that the vertices are ordered clockwise when layed out on the plane. * * @param {Array<number>} rect - Top-left, bottom-right representation of a * rectangle. * @returns {Array<Array<number>>} Polygon representation of the vertices of the * rectangle. */ private _rectToNonAdjustedVertices2d(rect: number[]): number[][] { return [ [rect[0], rect[3]], [rect[0], rect[1]], [rect[2], rect[1]], [rect[2], rect[3]], [rect[0], rect[3]], ]; } } export default RectGeometry;