@deck.gl/core
Version:
deck.gl core library
145 lines (128 loc) • 4.25 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import Viewport from '../viewports/viewport';
import {Matrix4, clamp, vec2} from '@math.gl/core';
import {pixelsToWorld} from '@math.gl/web-mercator';
import type {Padding} from './viewport';
const viewMatrix = new Matrix4().lookAt({eye: [0, 0, 1]});
function getProjectionMatrix({
width,
height,
near,
far,
padding
}: {
width: number;
height: number;
near: number;
far: number;
padding: Padding | null;
}) {
let left = -width / 2;
let right = width / 2;
let bottom = -height / 2;
let top = height / 2;
if (padding) {
const {left: l = 0, right: r = 0, top: t = 0, bottom: b = 0} = padding;
const offsetX = clamp((l + width - r) / 2, 0, width) - width / 2;
const offsetY = clamp((t + height - b) / 2, 0, height) - height / 2;
left -= offsetX;
right -= offsetX;
bottom += offsetY;
top += offsetY;
}
return new Matrix4().ortho({
left,
right,
bottom,
top,
near,
far
});
}
export type OrthographicViewportOptions = {
/** Name of the viewport */
id?: string;
/** Left offset from the canvas edge, in pixels */
x?: number;
/** Top offset from the canvas edge, in pixels */
y?: number;
/** Viewport width in pixels */
width?: number;
/** Viewport height in pixels */
height?: number;
/** The world position at the center of the viewport. Default `[0, 0, 0]`. */
target?: [number, number, number] | [number, number];
/** The zoom level of the viewport. `zoom: 0` maps one unit distance to one pixel on screen, and increasing `zoom` by `1` scales the same object to twice as large.
* To apply independent zoom levels to the X and Y axes, supply an array `[zoomX, zoomY]`. Default `0`. */
zoom?: number | [number, number];
/** Padding around the viewport, in pixels. */
padding?: Padding | null;
/** Distance of near clipping plane. Default `0.1`. */
near?: number;
/** Distance of far clipping plane. Default `1000`. */
far?: number;
/** Whether to use top-left coordinates (`true`) or bottom-left coordinates (`false`). Default `true`. */
flipY?: boolean;
};
export default class OrthographicViewport extends Viewport {
constructor(props: OrthographicViewportOptions) {
const {
width,
height,
near = 0.1,
far = 1000,
zoom = 0,
target = [0, 0, 0],
padding = null,
flipY = true
} = props;
const zoomX = Array.isArray(zoom) ? zoom[0] : zoom;
const zoomY = Array.isArray(zoom) ? zoom[1] : zoom;
const zoom_ = Math.min(zoomX, zoomY);
const scale = Math.pow(2, zoom_);
let distanceScales;
if (zoomX !== zoomY) {
const scaleX = Math.pow(2, zoomX);
const scaleY = Math.pow(2, zoomY);
distanceScales = {
unitsPerMeter: [scaleX / scale, scaleY / scale, 1],
metersPerUnit: [scale / scaleX, scale / scaleY, 1]
};
}
super({
...props,
// in case viewState contains longitude/latitude values,
// make sure that the base Viewport class does not treat this as a geospatial viewport
longitude: undefined,
position: target,
viewMatrix: viewMatrix.clone().scale([scale, scale * (flipY ? -1 : 1), scale]),
projectionMatrix: getProjectionMatrix({
width: width || 1,
height: height || 1,
padding,
near,
far
}),
zoom: zoom_,
distanceScales
});
}
projectFlat([X, Y]: number[]): [number, number] {
const {unitsPerMeter} = this.distanceScales;
return [X * unitsPerMeter[0], Y * unitsPerMeter[1]];
}
unprojectFlat([x, y]: number[]): [number, number] {
const {metersPerUnit} = this.distanceScales;
return [x * metersPerUnit[0], y * metersPerUnit[1]];
}
/* Needed by LinearInterpolator */
panByPosition(coords: number[], pixel: number[]): OrthographicViewportOptions {
const fromLocation = pixelsToWorld(pixel, this.pixelUnprojectionMatrix);
const toLocation = this.projectFlat(coords);
const translate = vec2.add([], toLocation, vec2.negate([], fromLocation));
const newCenter = vec2.add([], this.center, translate);
return {target: this.unprojectFlat(newCenter)};
}
}