mapillary-js
Version:
A WebGL interactive street imagery library
173 lines (152 loc) • 5.84 kB
text/typescript
import { TileBoundingBox } from "./interfaces/TileBoundingBox";
import { TileRegionOfInterest } from "./interfaces/TileRegionOfInterest";
import { Transform } from "../geo/Transform";
import { ViewportCoords } from "../geo/ViewportCoords";
import { RenderCamera } from "../render/RenderCamera";
import { ViewportSize } from "../render/interfaces/ViewportSize";
import { isSpherical } from "../geo/Geo";
/**
* @class RegionOfInterestCalculator
*
* @classdesc Represents a calculator for regions of interest.
*/
export class RegionOfInterestCalculator {
private _viewportCoords: ViewportCoords = new ViewportCoords();
/**
* Compute a region of interest based on the current render camera
* and the viewport size.
*
* @param {RenderCamera} renderCamera - Render camera used for unprojections.
* @param {ViewportSize} size - Viewport size in pixels.
* @param {Transform} transform - Transform used for projections.
*
* @returns {TileRegionOfInterest} A region of interest.
*/
public computeRegionOfInterest(
renderCamera: RenderCamera,
size: ViewportSize,
transform: Transform): TileRegionOfInterest {
const viewportBoundaryPoints = this._viewportBoundaryPoints(4);
const bbox = this._viewportPointsBoundingBox(
viewportBoundaryPoints,
renderCamera,
transform);
this._clipBoundingBox(bbox);
const viewportPixelWidth = 2 / size.width;
const viewportPixelHeight = 2 / size.height;
const centralViewportPixel = [
[-0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
[0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
[0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
[-0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
];
const cpbox =
this._viewportPointsBoundingBox(
centralViewportPixel,
renderCamera,
transform);
const inverted = cpbox.minX < cpbox.maxX;
return {
bbox: bbox,
pixelHeight: cpbox.maxY - cpbox.minY,
pixelWidth: cpbox.maxX - cpbox.minX + (inverted ? 0 : 1),
};
}
private _viewportBoundaryPoints(pointsPerSide: number): number[][] {
const points: number[][] = [];
const os = [[-1, 1], [1, 1], [1, -1], [-1, -1]];
const ds = [[2, 0], [0, -2], [-2, 0], [0, 2]];
for (let side = 0; side < 4; ++side) {
const o = os[side];
const d = ds[side];
for (let i = 0; i < pointsPerSide; ++i) {
points.push([o[0] + d[0] * i / pointsPerSide,
o[1] + d[1] * i / pointsPerSide]);
}
}
return points;
}
private _viewportPointsBoundingBox(
viewportPoints: number[][],
renderCamera: RenderCamera,
transform: Transform): TileBoundingBox {
const basicPoints = viewportPoints
.map(
(point: number[]): number[] => {
return this._viewportCoords
.viewportToBasic(
point[0],
point[1],
transform,
renderCamera.perspective);
});
if (isSpherical(transform.cameraType)) {
return this._boundingBoxSpherical(basicPoints);
} else {
return this._boundingBox(basicPoints);
}
}
private _boundingBox(points: number[][]): TileBoundingBox {
const bbox: TileBoundingBox = {
maxX: Number.NEGATIVE_INFINITY,
maxY: Number.NEGATIVE_INFINITY,
minX: Number.POSITIVE_INFINITY,
minY: Number.POSITIVE_INFINITY,
};
for (let i = 0; i < points.length; ++i) {
bbox.minX = Math.min(bbox.minX, points[i][0]);
bbox.maxX = Math.max(bbox.maxX, points[i][0]);
bbox.minY = Math.min(bbox.minY, points[i][1]);
bbox.maxY = Math.max(bbox.maxY, points[i][1]);
}
return bbox;
}
private _boundingBoxSpherical(points: number[][]): TileBoundingBox {
const xs: number[] = [];
const ys: number[] = [];
for (let i = 0; i < points.length; ++i) {
xs.push(points[i][0]);
ys.push(points[i][1]);
}
xs.sort((a, b) => { return this._sign(a - b); });
ys.sort((a, b) => { return this._sign(a - b); });
const intervalX = this._intervalSpherical(xs);
return {
maxX: intervalX[1],
maxY: ys[ys.length - 1],
minX: intervalX[0],
minY: ys[0],
};
}
/**
* Find the max interval between consecutive numbers.
* Assumes numbers are between 0 and 1, sorted and that
* x is equivalent to x + 1.
*/
private _intervalSpherical(xs: number[]): number[] {
let maxdx = 0;
let maxi = -1;
for (let i = 0; i < xs.length - 1; ++i) {
const dx = xs[i + 1] - xs[i];
if (dx > maxdx) {
maxdx = dx;
maxi = i;
}
}
const loopdx = xs[0] + 1 - xs[xs.length - 1];
if (loopdx > maxdx) {
return [xs[0], xs[xs.length - 1]];
} else {
return [xs[maxi + 1], xs[maxi]];
}
}
private _clipBoundingBox(bbox: TileBoundingBox): void {
bbox.minX = Math.max(0, Math.min(1, bbox.minX));
bbox.maxX = Math.max(0, Math.min(1, bbox.maxX));
bbox.minY = Math.max(0, Math.min(1, bbox.minY));
bbox.maxY = Math.max(0, Math.min(1, bbox.maxY));
}
private _sign(n: number): number {
return n > 0 ? 1 : n < 0 ? -1 : 0;
}
}