From 9f8e01f51331a8b3913fd5cc59d94eeaa70bbf79 Mon Sep 17 00:00:00 2001 From: Charles Richardson Date: Fri, 20 Dec 2024 13:16:40 -0500 Subject: [PATCH 1/7] docs: elevation control in MapViewState --- modules/core/src/views/map-view.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/views/map-view.ts b/modules/core/src/views/map-view.ts index 4d80ef73cc3..201b14cc877 100644 --- a/modules/core/src/views/map-view.ts +++ b/modules/core/src/views/map-view.ts @@ -27,8 +27,8 @@ export type MapViewState = { minPitch?: number; /** Max pitch, default `60` */ maxPitch?: number; - /** Viewport center offsets from lng, lat in meters */ - position?: number[]; + /** Viewport center offsets from lng, lat, and elevation in meters */ + position?: [number, number, number]; } & CommonViewState; export type MapViewProps = { From e6b304d31e7d88c19946b65e93ed898e20ee8b3d Mon Sep 17 00:00:00 2001 From: Charles Richardson Date: Sat, 21 Dec 2024 16:11:33 -0500 Subject: [PATCH 2/7] Add MapViewState change documentation --- docs/upgrade-guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 3ef720e4aa8..c24f310b9b3 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -32,6 +32,10 @@ Breaking changes: - `PointLight.attenuation` was previously ignored. To retain old behavior, use the default (`[1, 0, 0]`). +### MapViewState + +- The `position` property changed from `number[]` to `[number, number, number]` (lat, lng, elevation). + ## Upgrading to v9.0 **Before you upgrade: known issues** From 33dde585154ca09247fa79fe7a109fa78c3dff3b Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Mon, 6 Jan 2025 16:14:03 -0500 Subject: [PATCH 3/7] fix: strictly include elevation in position type and docs --- docs/api-reference/core/globe-viewport.md | 2 +- .../core/src/shaderlib/project/project-functions.ts | 4 ++-- modules/core/src/viewports/first-person-viewport.ts | 2 +- modules/core/src/viewports/viewport.ts | 10 +++++----- modules/core/src/viewports/web-mercator-viewport.ts | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/api-reference/core/globe-viewport.md b/docs/api-reference/core/globe-viewport.md index 69f29e1f4ec..0678bffee3d 100644 --- a/docs/api-reference/core/globe-viewport.md +++ b/docs/api-reference/core/globe-viewport.md @@ -64,7 +64,7 @@ Projects world coordinates to pixel coordinates on screen. Parameters: -* `coordinates` (number[]) - `[longitude, latitude, altitude]`. `altitude` is in meters and default to `0` if not supplied. +* `coordinates` ([number, number, number]) - `[longitude, latitude, altitude]`. `altitude` is in meters and default to `0` if not supplied. * `opts` (object) + `topLeft` (boolean, optional) - Whether projected coords are top left. Default to `true`. diff --git a/modules/core/src/shaderlib/project/project-functions.ts b/modules/core/src/shaderlib/project/project-functions.ts index bc66d1498c1..86362dc8258 100644 --- a/modules/core/src/shaderlib/project/project-functions.ts +++ b/modules/core/src/shaderlib/project/project-functions.ts @@ -81,7 +81,7 @@ function normalizeParameters(opts: { /** Get the common space position from world coordinates in the given coordinate system */ export function getWorldPosition( - position: number[], + position: [number, number, number], { viewport, modelMatrix, @@ -134,7 +134,7 @@ export function getWorldPosition( * a reference coordinate system */ export function projectPosition( - position: number[], + position: [number, number, number], params: { /** The current viewport */ viewport: Viewport; diff --git a/modules/core/src/viewports/first-person-viewport.ts b/modules/core/src/viewports/first-person-viewport.ts index 41d709ac2d5..a60a71f76ef 100644 --- a/modules/core/src/viewports/first-person-viewport.ts +++ b/modules/core/src/viewports/first-person-viewport.ts @@ -21,7 +21,7 @@ export type FirstPersonViewportOptions = { longitude?: number; /** Latitude of the camera, in the geospatial case. */ latitude?: number; - /** Meter offsets of the camera from the lng-lat anchor point. Default `[0, 0, 0]`. */ + /** Meter offsets of the camera from the lng-lat-elevation anchor point. Default `[0, 0, 0]`. */ position?: [number, number, number]; /** Bearing (heading) of the camera in degrees. Default `0` (north). */ bearing?: number; diff --git a/modules/core/src/viewports/viewport.ts b/modules/core/src/viewports/viewport.ts index 05597627ce3..7d9777b9450 100644 --- a/modules/core/src/viewports/viewport.ts +++ b/modules/core/src/viewports/viewport.ts @@ -45,8 +45,8 @@ export type ViewportOptions = { longitude?: number; /** Latitude in degrees (geospatial only) */ latitude?: number; - /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat */ - position?: number[]; + /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat, elevation */ + position?: [number, number, number]; /** Zoom level */ zoom?: number; /** Padding around the viewport, in pixels. */ @@ -76,7 +76,7 @@ const DEGREES_TO_RADIANS = Math.PI / 180; const IDENTITY = createMat4(); -const ZERO_VECTOR = [0, 0, 0]; +const ZERO_VECTOR: [number, number, number] = [0, 0, 0]; const DEFAULT_DISTANCE_SCALES: DistanceScales = { unitsPerMeter: [1, 1, 1], @@ -138,7 +138,7 @@ export default class Viewport { isGeospatial: boolean; zoom: number; focalDistance: number; - position: number[]; + position: [number, number, number]; modelMatrix: number[] | null; /** Derived parameters */ @@ -149,7 +149,7 @@ export default class Viewport { distanceScales: DistanceScales; /** scale factors between world space and common space */ scale!: number; /** scale factor, equals 2^zoom */ center!: number[]; /** viewport center in common space */ - cameraPosition!: number[]; /** Camera position in common space */ + cameraPosition!: [number, number, number]; /** Camera position in common space */ projectionMatrix!: number[]; viewMatrix!: number[]; viewMatrixUncentered!: number[]; diff --git a/modules/core/src/viewports/web-mercator-viewport.ts b/modules/core/src/viewports/web-mercator-viewport.ts index 55dc1c360f7..453894859e2 100644 --- a/modules/core/src/viewports/web-mercator-viewport.ts +++ b/modules/core/src/viewports/web-mercator-viewport.ts @@ -44,8 +44,8 @@ export type WebMercatorViewportOptions = { altitude?: number; /** Camera fovy in degrees. If provided, overrides `altitude` */ fovy?: number; - /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat */ - position?: number[]; + /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat, elevation */ + position?: [number, number, number]; /** Zoom level */ zoom?: number; /** Padding around the viewport, in pixels. */ From 9d8647abefe8771d1ce733c428732333e5560b88 Mon Sep 17 00:00:00 2001 From: Charles Richardson Date: Mon, 6 Jan 2025 16:56:30 -0500 Subject: [PATCH 4/7] docs: ViewportOptions position type change --- docs/upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 28c4f8619a0..d57e5827214 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -32,7 +32,7 @@ Breaking changes: - `PointLight.attenuation` was previously ignored. To retain old behavior, use the default (`[1, 0, 0]`). -### MapViewState +### MapViewState / ViewportOptions - The `position` property changed from `number[]` to `[number, number, number]` (lat, lng, elevation). From 5c144634787f49d3f12793a4dc37fca008615107 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Wed, 8 Jan 2025 13:58:13 -0500 Subject: [PATCH 5/7] fix: enforce 3D coordinates across dependent modules --- .../src/contour-layer/contour-layer.ts | 9 ++- .../src/grid-layer/grid-layer.ts | 9 ++- .../src/heatmap-layer/heatmap-layer.ts | 17 +++--- .../src/hexagon-layer/hexagon-layer.ts | 9 ++- .../screen-grid-layer/screen-grid-layer.ts | 2 +- modules/core/src/lib/layer.ts | 8 +-- modules/core/src/lib/picking/pick-info.ts | 4 +- modules/core/src/lib/view-manager.ts | 12 ++-- .../shaderlib/project/project-functions.ts | 2 +- .../src/transitions/linear-interpolator.ts | 6 +- modules/core/src/viewports/globe-viewport.ts | 30 +++++----- modules/core/src/viewports/orbit-viewport.ts | 2 +- .../src/viewports/orthographic-viewport.ts | 12 ++-- modules/core/src/viewports/viewport.ts | 57 ++++++++++--------- .../src/viewports/web-mercator-viewport.ts | 10 ++-- .../extensions/src/brushing/shader-module.ts | 7 +-- .../extensions/src/terrain/terrain-cover.ts | 12 +++- .../extensions/src/utils/projection-utils.ts | 16 ++++-- .../src/h3-layers/h3-hexagon-layer.ts | 2 +- .../src/tile-3d-layer/tile-3d-layer.ts | 2 +- .../src/tileset-2d/tile-2d-traversal.ts | 12 ++-- modules/geo-layers/src/tileset-2d/utils.ts | 12 ++-- modules/google-maps/src/utils.ts | 2 +- .../solid-polygon-layer.ts | 7 ++- 24 files changed, 149 insertions(+), 112 deletions(-) diff --git a/modules/aggregation-layers/src/contour-layer/contour-layer.ts b/modules/aggregation-layers/src/contour-layer/contour-layer.ts index ded0d2acd82..9952ca64068 100644 --- a/modules/aggregation-layers/src/contour-layer/contour-layer.ts +++ b/modules/aggregation-layers/src/contour-layer/contour-layer.ts @@ -143,7 +143,7 @@ export default class GridLayer extends getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition(positions); + const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); const {cellSizeCommon, cellOriginCommon} = opts; return [ Math.floor((p[0] - cellOriginCommon[0]) / cellSizeCommon[0]), @@ -241,7 +241,10 @@ export default class GridLayer extends let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid = [(bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2]; + let centroid: [number, number] = [ + (bounds[0][0] + bounds[1][0]) / 2, + (bounds[0][1] + bounds[1][1]) / 2 + ]; const {cellSize, gridOrigin} = this.props; const {unitsPerMeter} = viewport.getDistanceScales(centroid); cellSizeCommon[0] = unitsPerMeter[0] * cellSize; @@ -272,7 +275,7 @@ export default class GridLayer extends binIdRange = getBinIdRange({ dataBounds: bounds, getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat(p); + const positionCommon = viewport.projectFlat([p[0], p[1]]); return [ Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) diff --git a/modules/aggregation-layers/src/grid-layer/grid-layer.ts b/modules/aggregation-layers/src/grid-layer/grid-layer.ts index f0b82e8028b..ed37da97358 100644 --- a/modules/aggregation-layers/src/grid-layer/grid-layer.ts +++ b/modules/aggregation-layers/src/grid-layer/grid-layer.ts @@ -313,7 +313,7 @@ export default class GridLayer extends } const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition(positions); + const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); const {cellSizeCommon, cellOriginCommon} = opts; return [ Math.floor((p[0] - cellOriginCommon[0]) / cellSizeCommon[0]), @@ -446,7 +446,10 @@ export default class GridLayer extends let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid = [(bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2]; + let centroid: [number, number] = [ + (bounds[0][0] + bounds[1][0]) / 2, + (bounds[0][1] + bounds[1][1]) / 2 + ]; const {cellSize} = this.props; const {unitsPerMeter} = viewport.getDistanceScales(centroid); cellSizeCommon[0] = unitsPerMeter[0] * cellSize; @@ -475,7 +478,7 @@ export default class GridLayer extends binIdRange = getBinIdRange({ dataBounds: bounds, getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat(p); + const positionCommon = viewport.projectFlat([p[0], p[1]]); return [ Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) diff --git a/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts b/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts index a1ad95b8d53..bbed01c8d9c 100644 --- a/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts +++ b/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts @@ -492,10 +492,10 @@ export default class HeatmapLayer< // Unproject all 4 corners of the current screen coordinates into world coordinates (lng/lat) // Takes care of viewport has non zero bearing/pitch (i.e axis not aligned with world coordiante system) const viewportCorners = [ - viewport.unproject([0, 0]), - viewport.unproject([viewport.width, 0]), - viewport.unproject([0, viewport.height]), - viewport.unproject([viewport.width, viewport.height]) + viewport.unproject([0, 0, 0]), + viewport.unproject([viewport.width, 0, 0]), + viewport.unproject([0, viewport.height, 0]), + viewport.unproject([viewport.width, viewport.height, 0]) ].map(p => p.map(Math.fround)); // #1: get world bounds for current viewport extends @@ -546,7 +546,10 @@ export default class HeatmapLayer< triPositionBuffer!.write(packVertices(viewportCorners, 3)); const textureBounds = viewportCorners.map(p => - getTextureCoordinates(viewport.projectPosition(p), normalizedCommonBounds!) + getTextureCoordinates( + viewport.projectPosition([p[0], p[1], p[2] || 0]), + normalizedCommonBounds! + ) ); triTexCoordBuffer!.write(packVertices(textureBounds, 2)); } @@ -691,8 +694,8 @@ export default class HeatmapLayer< _commonToWorldBounds(commonBounds) { const [xMin, yMin, xMax, yMax] = commonBounds; const {viewport} = this.context; - const bottomLeftWorld = viewport.unprojectPosition([xMin, yMin]); - const topRightWorld = viewport.unprojectPosition([xMax, yMax]); + const bottomLeftWorld = viewport.unprojectPosition([xMin, yMin, 0]); + const topRightWorld = viewport.unprojectPosition([xMax, yMax, 0]); return bottomLeftWorld.slice(0, 2).concat(topRightWorld.slice(0, 2)); } diff --git a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts index 0be249804d7..f4bcbee31db 100644 --- a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts +++ b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts @@ -317,7 +317,7 @@ export default class HexagonLayer< } const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition(positions); + const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); const {radiusCommon, hexOriginCommon} = opts; return pointToHexbin( [p[0] - hexOriginCommon[0], p[1] - hexOriginCommon[1]], @@ -451,7 +451,10 @@ export default class HexagonLayer< let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid = [(bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2]; + let centroid: [number, number] = [ + (bounds[0][0] + bounds[1][0]) / 2, + (bounds[0][1] + bounds[1][1]) / 2 + ]; const {radius} = this.props; const {unitsPerMeter} = viewport.getDistanceScales(centroid); radiusCommon = unitsPerMeter[0] * radius; @@ -474,7 +477,7 @@ export default class HexagonLayer< binIdRange = getBinIdRange({ dataBounds: bounds, getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat(p); + const positionCommon = viewport.projectFlat([p[0], p[1]]); positionCommon[0] -= hexOriginCommon[0]; positionCommon[1] -= hexOriginCommon[1]; return pointToHexbin(positionCommon, radiusCommon); diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts index 0be80371606..4f2c3edd55f 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts @@ -138,7 +138,7 @@ export default class ScreenGridLayer< sources: ['positions'], getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { const viewport = this.context.viewport; - const p = viewport.project(positions); + const p = viewport.project([positions[0], positions[1], positions[2] || 0]); const cellSizePixels: number = opts.cellSizePixels; if (p[0] < 0 || p[0] >= viewport.width || p[1] < 0 || p[1] >= viewport.height) { // Not on screen diff --git a/modules/core/src/lib/layer.ts b/modules/core/src/lib/layer.ts index 04e16aa177b..31730d50f48 100644 --- a/modules/core/src/lib/layer.ts +++ b/modules/core/src/lib/layer.ts @@ -219,7 +219,7 @@ export default abstract class Layer extends Component< // Public API for users /** Projects a point with current view state from the current layer's coordinate system to screen */ - project(xyz: number[]): number[] { + project(xyz: [number, number, number]): [number, number, number] { assert(this.internalState); const viewport = this.internalState.viewport || this.context.viewport; @@ -230,12 +230,12 @@ export default abstract class Layer extends Component< coordinateSystem: this.props.coordinateSystem }); const [x, y, z] = worldToPixels(worldPosition, viewport.pixelProjectionMatrix); - return xyz.length === 2 ? [x, y] : [x, y, z]; + return [x, y, z]; } /** Unprojects a screen pixel to the current view's default coordinate system Note: this does not reverse `project`. */ - unproject(xy: number[]): number[] { + unproject(xy: [number, number, number]): [number, number, number] { assert(this.internalState); const viewport = this.internalState.viewport || this.context.viewport; return viewport.unproject(xy); @@ -243,7 +243,7 @@ export default abstract class Layer extends Component< /** Projects a point with current view state from the current layer's coordinate system to the world space */ projectPosition( - xyz: number[], + xyz: [number, number, number], params?: { /** The viewport to use */ viewport?: Viewport; diff --git a/modules/core/src/lib/picking/pick-info.ts b/modules/core/src/lib/picking/pick-info.ts index 4a1d36bb379..ae13a02f2f1 100644 --- a/modules/core/src/lib/picking/pick-info.ts +++ b/modules/core/src/lib/picking/pick-info.ts @@ -53,9 +53,9 @@ export function getEmptyPickingInfo({ // Find the viewport that contain the picked pixel pickedViewport = getViewportFromCoordinates(pickInfo?.pickedViewports || viewports, {x, y}); } - let coordinate: number[] | undefined; + let coordinate: [number, number, number] | undefined; if (pickedViewport) { - const point = [x - pickedViewport.x, y - pickedViewport.y]; + const point: [number, number, number] = [x - pickedViewport.x, y - pickedViewport.y, 0]; if (z !== undefined) { point[2] = z; } diff --git a/modules/core/src/lib/view-manager.ts b/modules/core/src/lib/view-manager.ts index 8d68f61aebd..d53771a7f76 100644 --- a/modules/core/src/lib/view-manager.ts +++ b/modules/core/src/lib/view-manager.ts @@ -20,14 +20,14 @@ type ViewStateOf = ViewT extends View ? ViewStateT : ne type OneOfViews = ViewsT extends null ? MapView : ViewsT extends View[] - ? ViewsT[number] - : ViewsT; + ? ViewsT[number] + : ViewsT; export type AnyViewStateOf = ViewStateOf>; export type ViewStateMap = ViewsT extends null ? MapViewState : ViewsT extends View - ? ViewStateOf - : {[viewId: string]: AnyViewStateOf}; + ? ViewStateOf + : {[viewId: string]: AnyViewStateOf}; /** This is a very lose type of all "acceptable" viewState * It's not good for type hinting but matches what may exist internally @@ -195,13 +195,13 @@ export default class ViewManager { * @param {Object} opts.topLeft=true - Whether origin is top left * @return {Array|null} - [lng, lat, Z] or [X, Y, Z] */ - unproject(xyz: number[], opts?: {topLeft?: boolean}): number[] | null { + unproject(xyz: [number, number, number], opts?: {topLeft?: boolean}): number[] | null { const viewports = this.getViewports(); const pixel = {x: xyz[0], y: xyz[1]}; for (let i = viewports.length - 1; i >= 0; --i) { const viewport = viewports[i]; if (viewport.containsPixel(pixel)) { - const p = xyz.slice(); + const p = xyz.slice() as [number, number, number]; p[0] -= viewport.x; p[1] -= viewport.y; return viewport.unproject(p, opts); diff --git a/modules/core/src/shaderlib/project/project-functions.ts b/modules/core/src/shaderlib/project/project-functions.ts index 86362dc8258..8936ce04a4e 100644 --- a/modules/core/src/shaderlib/project/project-functions.ts +++ b/modules/core/src/shaderlib/project/project-functions.ts @@ -17,7 +17,7 @@ import type {CoordinateSystem} from '../../lib/constants'; import type Viewport from '../../viewports/viewport'; import type {NumericArray} from '../../types/types'; -const DEFAULT_COORDINATE_ORIGIN = [0, 0, 0]; +const DEFAULT_COORDINATE_ORIGIN: [number, number, number] = [0, 0, 0]; // In project.glsl, offset modes calculate z differently from LNG_LAT mode. // offset modes apply the y adjustment (unitsPerMeter2) when projecting z diff --git a/modules/core/src/transitions/linear-interpolator.ts b/modules/core/src/transitions/linear-interpolator.ts index b064b9a743b..84477c9e0b3 100644 --- a/modules/core/src/transitions/linear-interpolator.ts +++ b/modules/core/src/transitions/linear-interpolator.ts @@ -11,7 +11,7 @@ const DEFAULT_PROPS = ['longitude', 'latitude', 'zoom', 'bearing', 'pitch']; const DEFAULT_REQUIRED_PROPS = ['longitude', 'latitude', 'zoom']; type PropsWithAnchor = { - around?: number[]; + around?: [number, number, number]; aroundPosition?: number[]; [key: string]: any; }; @@ -21,7 +21,7 @@ type PropsWithAnchor = { */ export default class LinearInterpolator extends TransitionInterpolator { opts: { - around?: number[]; + around?: [number, number, number]; makeViewport?: (props: Record) => Viewport; }; @@ -42,7 +42,7 @@ export default class LinearInterpolator extends TransitionInterpolator { extract?: string[]; required?: string[]; }; - around?: number[]; + around?: [number, number, number]; makeViewport?: (props: Record) => Viewport; } = {} ) { diff --git a/modules/core/src/viewports/globe-viewport.ts b/modules/core/src/viewports/globe-viewport.ts index 1d7657dd7e5..1fa011d1502 100644 --- a/modules/core/src/viewports/globe-viewport.ts +++ b/modules/core/src/viewports/globe-viewport.ts @@ -47,7 +47,7 @@ export type GlobeViewportOptions = { /** Camera altitude relative to the viewport height, used to control the FOV. Default `1.5` */ altitude?: number; /* Meter offsets of the viewport center from lng, lat */ - position?: number[]; + position?: [number, number, number]; /** Zoom level */ zoom?: number; /** Use orthographic projection */ @@ -138,10 +138,10 @@ export default class GlobeViewport extends Viewport { getBounds(options: {z?: number} = {}): [number, number, number, number] { const unprojectOption = {targetZ: options.z || 0}; - const left = this.unproject([0, this.height / 2], unprojectOption); - const top = this.unproject([this.width / 2, 0], unprojectOption); - const right = this.unproject([this.width, this.height / 2], unprojectOption); - const bottom = this.unproject([this.width / 2, this.height], unprojectOption); + const left = this.unproject([0, this.height / 2, 0], unprojectOption); + const top = this.unproject([this.width / 2, 0, 0], unprojectOption); + const right = this.unproject([this.width, this.height / 2, 0], unprojectOption); + const bottom = this.unproject([this.width / 2, this.height, 0], unprojectOption); if (right[0] < this.longitude) right[0] += 360; if (left[0] > this.longitude) left[0] -= 360; @@ -155,9 +155,9 @@ export default class GlobeViewport extends Viewport { } unproject( - xyz: number[], + xyz: [number, number, number], {topLeft = true, targetZ}: {topLeft?: boolean; targetZ?: number} = {} - ): number[] { + ): [number, number, number] { const [x, y, z] = xyz; const y2 = topLeft ? y : this.height - y; @@ -190,10 +190,10 @@ export default class GlobeViewport extends Viewport { if (Number.isFinite(z)) { return [X, Y, Z]; } - return Number.isFinite(targetZ) ? [X, Y, targetZ as number] : [X, Y]; + return Number.isFinite(targetZ) ? [X, Y, targetZ as number] : [X, Y, 0]; } - projectPosition(xyz: number[]): [number, number, number] { + projectPosition(xyz: [number, number, number]): [number, number, number] { const [lng, lat, Z = 0] = xyz; const lambda = lng * DEGREES_TO_RADIANS; const phi = lat * DEGREES_TO_RADIANS; @@ -203,7 +203,7 @@ export default class GlobeViewport extends Viewport { return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D]; } - unprojectPosition(xyz: number[]): [number, number, number] { + unprojectPosition(xyz: [number, number, number]): [number, number, number] { const [x, y, z] = xyz; const D = vec3.len(xyz); const phi = Math.asin(z / D); @@ -215,15 +215,15 @@ export default class GlobeViewport extends Viewport { return [lng, lat, Z]; } - projectFlat(xyz: number[]): [number, number] { - return xyz as [number, number]; + projectFlat(xy: [number, number]): [number, number] { + return xy; } - unprojectFlat(xyz: number[]): [number, number] { - return xyz as [number, number]; + unprojectFlat(xy: [number, number]): [number, number] { + return xy; } - panByPosition(coords: number[], pixel: number[]): GlobeViewportOptions { + panByPosition(coords: number[], pixel: [number, number, number]): GlobeViewportOptions { const fromPosition = this.unproject(pixel); return { longitude: coords[0] - fromPosition[0] + this.longitude, diff --git a/modules/core/src/viewports/orbit-viewport.ts b/modules/core/src/viewports/orbit-viewport.ts index 333f6afaaf4..3c8c159b32d 100644 --- a/modules/core/src/viewports/orbit-viewport.ts +++ b/modules/core/src/viewports/orbit-viewport.ts @@ -136,7 +136,7 @@ export default class OrbitViewport extends Viewport { return [X, Y, Z]; } - panByPosition(coords: number[], pixel: number[]): OrbitViewportOptions { + panByPosition(coords: [number, number, number], pixel: number[]): OrbitViewportOptions { const p0 = this.project(coords); const nextCenter = [ this.width / 2 + p0[0] - pixel[0], diff --git a/modules/core/src/viewports/orthographic-viewport.ts b/modules/core/src/viewports/orthographic-viewport.ts index c97635c339a..96c008fe2fc 100644 --- a/modules/core/src/viewports/orthographic-viewport.ts +++ b/modules/core/src/viewports/orthographic-viewport.ts @@ -60,7 +60,7 @@ export type OrthographicViewportOptions = { /** 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]; + target?: [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]; @@ -121,24 +121,24 @@ export default class OrthographicViewport extends Viewport { }); } - projectFlat([X, Y]: number[]): [number, number] { + projectFlat([X, Y]: [number, number]): [number, number] { const {unitsPerMeter} = this.distanceScales; return [X * unitsPerMeter[0], Y * unitsPerMeter[1]]; } - unprojectFlat([x, y]: number[]): [number, number] { + unprojectFlat([x, y]: [number, number]): [number, number] { const {metersPerUnit} = this.distanceScales; return [x * metersPerUnit[0], y * metersPerUnit[1]]; } /* Needed by LinearInterpolator */ - panByPosition(coords: number[], pixel: number[]): OrthographicViewportOptions { + panByPosition(coords: [number, 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)}; + const target = this.unprojectFlat(newCenter); + return {target: [target[0], target[1], 0]}; } } diff --git a/modules/core/src/viewports/viewport.ts b/modules/core/src/viewports/viewport.ts index 7d9777b9450..f60725f6536 100644 --- a/modules/core/src/viewports/viewport.ts +++ b/modules/core/src/viewports/viewport.ts @@ -148,7 +148,7 @@ export default class Viewport { distanceScales: DistanceScales; /** scale factors between world space and common space */ scale!: number; /** scale factor, equals 2^zoom */ - center!: number[]; /** viewport center in common space */ + center!: [number, number, number]; /** viewport center in common space */ cameraPosition!: [number, number, number]; /** Camera position in common space */ projectionMatrix!: number[]; viewMatrix!: number[]; @@ -234,22 +234,21 @@ export default class Viewport { /** * Projects xyz (possibly latitude and longitude) to pixel coordinates in window * using viewport projection parameters - * - [longitude, latitude] to [x, y] * - [longitude, latitude, Z] => [x, y, z] * Note: By default, returns top-left coordinates for canvas/SVG type render * - * @param {Array} lngLatZ - [lng, lat] or [lng, lat, Z] + * @param {Array} xyz - [lng, lat, Z] * @param {Object} opts - options * @param {Object} opts.topLeft=true - Whether projected coords are top left * @return {Array} - [x, y] or [x, y, z] in top left coords */ - project(xyz: number[], {topLeft = true}: {topLeft?: boolean} = {}): number[] { + project(xyz: [number, number, number], {topLeft = true}: {topLeft?: boolean} = {}): number[] { const worldPosition = this.projectPosition(xyz); const coord = worldToPixels(worldPosition, this.pixelProjectionMatrix); const [x, y] = coord; const y2 = topLeft ? y : this.height - y; - return xyz.length === 2 ? [x, y2] : [x, y2, coord[2]]; + return [x, y2, coord[2]]; } /** @@ -263,33 +262,37 @@ export default class Viewport { * @return {Array|null} - [lng, lat, Z] or [X, Y, Z] */ unproject( - xyz: number[], + xyz: [number, number, number], {topLeft = true, targetZ}: {topLeft?: boolean; targetZ?: number} = {} - ): number[] { + ): [number, number, number] { const [x, y, z] = xyz; const y2 = topLeft ? y : this.height - y; const targetZWorld = targetZ && targetZ * this.distanceScales.unitsPerMeter[2]; - const coord = pixelsToWorld([x, y2, z], this.pixelUnprojectionMatrix, targetZWorld); + const coord = pixelsToWorld([x, y2, z], this.pixelUnprojectionMatrix, targetZWorld) as [ + number, + number, + number + ]; const [X, Y, Z] = this.unprojectPosition(coord); if (Number.isFinite(z)) { return [X, Y, Z]; } - return Number.isFinite(targetZ) ? [X, Y, targetZ as number] : [X, Y]; + return [X, Y, targetZ as number]; } // NON_LINEAR PROJECTION HOOKS // Used for web meractor projection - projectPosition(xyz: number[]): [number, number, number] { - const [X, Y] = this.projectFlat(xyz); + projectPosition(xyz: [number, number, number]): [number, number, number] { + const [X, Y] = this.projectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) * this.distanceScales.unitsPerMeter[2]; return [X, Y, Z]; } - unprojectPosition(xyz: number[]): [number, number, number] { - const [X, Y] = this.unprojectFlat(xyz); + unprojectPosition(xyz: [number, number, number]): [number, number, number] { + const [X, Y] = this.unprojectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) * this.distanceScales.metersPerUnit[2]; return [X, Y, Z]; } @@ -303,16 +306,16 @@ export default class Viewport { * Specifies a point on the sphere to project onto the map. * @return {Array} [x,y] coordinates. */ - projectFlat(xyz: number[]): [number, number] { + projectFlat(xy: [number, number]): [number, number] { if (this.isGeospatial) { // Shader clamps latitude to +-89.9, see /shaderlib/project/project.glsl.js // lngLatToWorld([0, -89.9])[1] = -317.9934163758329 // lngLatToWorld([0, 89.9])[1] = 829.9934163758271 - const result = lngLatToWorld(xyz); + const result = lngLatToWorld(xy); result[1] = clamp(result[1], -318, 830); - return result; + return [result[0], result[1]]; } - return xyz as [number, number]; + return xy; } /** @@ -323,11 +326,11 @@ export default class Viewport { * Has toArray method if you need a GeoJSON Array. * Per cartographic tradition, lat and lon are specified as degrees. */ - unprojectFlat(xyz: number[]): [number, number] { + unprojectFlat(xy: [number, number]): [number, number] { if (this.isGeospatial) { - return worldToLngLat(xyz); + return worldToLngLat(xy); } - return xyz as [number, number]; + return xy; } /** @@ -337,10 +340,10 @@ export default class Viewport { getBounds(options: {z?: number} = {}): [number, number, number, number] { const unprojectOption = {targetZ: options.z || 0}; - const topLeft = this.unproject([0, 0], unprojectOption); - const topRight = this.unproject([this.width, 0], unprojectOption); - const bottomLeft = this.unproject([0, this.height], unprojectOption); - const bottomRight = this.unproject([this.width, this.height], unprojectOption); + const topLeft = this.unproject([0, 0, 0], unprojectOption); + const topRight = this.unproject([this.width, 0, 0], unprojectOption); + const bottomLeft = this.unproject([0, this.height, 0], unprojectOption); + const bottomRight = this.unproject([this.width, this.height, 0], unprojectOption); return [ Math.min(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]), @@ -431,10 +434,10 @@ export default class Viewport { this.scale = scale; const {position, modelMatrix} = opts; - let meterOffset: number[] = ZERO_VECTOR; + let meterOffset: [number, number, number] = ZERO_VECTOR; if (position) { meterOffset = modelMatrix - ? (new Matrix4(modelMatrix).transformAsVector(position, []) as number[]) + ? (new Matrix4(modelMatrix).transformAsVector(position, []) as [number, number, number]) : position; } @@ -445,7 +448,7 @@ export default class Viewport { this.center = new Vector3(meterOffset) // Convert to pixels in current zoom .scale(this.distanceScales.unitsPerMeter) - .add(center); + .toArray() as [number, number, number]; } else { this.center = this.projectPosition(meterOffset); } diff --git a/modules/core/src/viewports/web-mercator-viewport.ts b/modules/core/src/viewports/web-mercator-viewport.ts index 453894859e2..45ec9b7f328 100644 --- a/modules/core/src/viewports/web-mercator-viewport.ts +++ b/modules/core/src/viewports/web-mercator-viewport.ts @@ -236,22 +236,22 @@ export default class WebMercatorViewport extends Viewport { return this._subViewports; } - projectPosition(xyz: number[]): [number, number, number] { + projectPosition(xyz: [number, number, number]): [number, number, number] { if (this._pseudoMeters) { // Backward compatibility return super.projectPosition(xyz); } - const [X, Y] = this.projectFlat(xyz); + const [X, Y] = this.projectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) * unitsPerMeter(xyz[1]); return [X, Y, Z]; } - unprojectPosition(xyz: number[]): [number, number, number] { + unprojectPosition(xyz: [number, number, number]): [number, number, number] { if (this._pseudoMeters) { // Backward compatibility return super.unprojectPosition(xyz); } - const [X, Y] = this.unprojectFlat(xyz); + const [X, Y] = this.unprojectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) / unitsPerMeter(Y); return [X, Y, Z]; } @@ -270,7 +270,7 @@ export default class WebMercatorViewport extends Viewport { return addMetersToLngLat(lngLatZ, xyz); } - panByPosition(coords: number[], pixel: number[]): WebMercatorViewportOptions { + panByPosition(coords: [number, number], pixel: number[]): WebMercatorViewportOptions { const fromLocation = pixelsToWorld(pixel, this.pixelUnprojectionMatrix); const toLocation = this.projectFlat(coords); diff --git a/modules/extensions/src/brushing/shader-module.ts b/modules/extensions/src/brushing/shader-module.ts index d2a487090e8..263d7143ceb 100644 --- a/modules/extensions/src/brushing/shader-module.ts +++ b/modules/extensions/src/brushing/shader-module.ts @@ -129,10 +129,9 @@ export default { radius: brushingRadius, target: TARGET[brushingTarget] || 0, mousePos: mousePosition - ? (viewport.unproject([mousePosition.x - viewport.x, mousePosition.y - viewport.y]) as [ - number, - number - ]) + ? (viewport + .unproject([mousePosition.x - viewport.x, mousePosition.y - viewport.y, 0]) + .slice(0, 2) as [number, number]) : [0, 0] }; }, diff --git a/modules/extensions/src/terrain/terrain-cover.ts b/modules/extensions/src/terrain/terrain-cover.ts index cb39fedfb28..ef27c620047 100644 --- a/modules/extensions/src/terrain/terrain-cover.ts +++ b/modules/extensions/src/terrain/terrain-cover.ts @@ -117,8 +117,16 @@ export class TerrainCover { shouldRedraw = true; this.targetBounds = this.tile.boundingBox; - const bottomLeftCommon = viewport.projectPosition(this.targetBounds[0]); - const topRightCommon = viewport.projectPosition(this.targetBounds[1]); + const bottomLeftCommon = viewport.projectPosition([ + this.targetBounds[0][0], + this.targetBounds[0][1], + this.targetBounds[0][2] || 0 + ]); + const topRightCommon = viewport.projectPosition([ + this.targetBounds[1][0], + this.targetBounds[1][1], + this.targetBounds[1][2] || 0 + ]); this.targetBoundsCommon = [ bottomLeftCommon[0], bottomLeftCommon[1], diff --git a/modules/extensions/src/utils/projection-utils.ts b/modules/extensions/src/utils/projection-utils.ts index 3d8b491ddb5..6ed485bf4dc 100644 --- a/modules/extensions/src/utils/projection-utils.ts +++ b/modules/extensions/src/utils/projection-utils.ts @@ -23,8 +23,14 @@ export function joinLayerBounds( for (const layer of layers) { const layerBounds = layer.getBounds(); if (layerBounds) { - const bottomLeftCommon = layer.projectPosition(layerBounds[0], {viewport, autoOffset: false}); - const topRightCommon = layer.projectPosition(layerBounds[1], {viewport, autoOffset: false}); + const bottomLeftCommon = layer.projectPosition([layerBounds[0][0], layerBounds[0][1], 0], { + viewport, + autoOffset: false + }); + const topRightCommon = layer.projectPosition([layerBounds[1][0], layerBounds[1][1], 0], { + viewport, + autoOffset: false + }); bounds[0] = Math.min(bounds[0], bottomLeftCommon[0]); bounds[1] = Math.min(bounds[1], bottomLeftCommon[1]); @@ -135,8 +141,10 @@ export function getViewportBounds(viewport: Viewport, zRange?: [number, number]) } // Viewport bounds in cartesian coordinates - const viewportBottomLeftCommon = viewport.projectPosition(viewportBoundsWorld.slice(0, 2)); - const viewportTopRightCommon = viewport.projectPosition(viewportBoundsWorld.slice(2, 4)); + const bottomLeft = viewportBoundsWorld.slice(0, 2); + const topRight = viewportBoundsWorld.slice(2, 4); + const viewportBottomLeftCommon = viewport.projectPosition([bottomLeft[0], bottomLeft[1], 0]); + const viewportTopRightCommon = viewport.projectPosition([topRight[0], topRight[1], 0]); return [ viewportBottomLeftCommon[0], viewportBottomLeftCommon[1], diff --git a/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts b/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts index 20220c174f1..d720c4d7031 100644 --- a/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts +++ b/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts @@ -211,7 +211,7 @@ export default class H3HexagonLayer< const [centerX, centerY] = viewport.projectFlat([centerLng, centerLat]); vertices = vertices.map(p => { - const worldPosition = viewport.projectFlat(p); + const worldPosition = viewport.projectFlat([p[0], p[1]]); return [ (worldPosition[0] - centerX) / unitsPerMeter[0], (worldPosition[1] - centerY) / unitsPerMeter[1] diff --git a/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts b/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts index 1710e20e09e..f1df582580c 100644 --- a/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts +++ b/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts @@ -241,7 +241,7 @@ export default class Tile3DLayer exten return; } - // eslint-disable-next-line @typescript-eslint/no-floating-promises + //@ts-expect-error Type 'Vector3' is not assignable to type '[number, number, number]' tileset3d.selectTiles(Object.values(viewports)).then(frameNumber => { const tilesetChanged = this.state.frameNumber !== frameNumber; if (tilesetChanged) { diff --git a/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts b/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts index ab3dba4ef55..2d1bde9185d 100644 --- a/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts +++ b/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts @@ -70,7 +70,7 @@ class OSMNode { // eslint-disable-next-line complexity update(params: { viewport: Viewport; - project: ((xyz: number[]) => number[]) | null; + project: ((xyz: [number, number, number]) => [number, number, number]) | null; cullingVolume: CullingVolume; elevationBounds: ZRange; minZ: number; @@ -144,7 +144,7 @@ class OSMNode { getBoundingVolume( zRange: ZRange, worldOffset: number, - project: ((xyz: number[]) => number[]) | null + project: ((xyz: [number, number, number]) => [number, number, number]) | null ) { if (project) { // Custom projection @@ -155,7 +155,11 @@ class OSMNode { // Convert from tile-relative coordinates to common space const refPointPositions: number[][] = []; for (const p of refPoints) { - const lngLat: number[] = osmTile2lngLat(this.x + p[0], this.y + p[1], this.z); + const lngLat: [number, number, number] = osmTile2lngLat( + this.x + p[0], + this.y + p[1], + this.z + ); lngLat[2] = zRange[0]; refPointPositions.push(project(lngLat)); @@ -190,7 +194,7 @@ export function getOSMTileIndices( zRange: ZRange | null, bounds?: Bounds ): TileIndex[] { - const project: ((xyz: number[]) => number[]) | null = + const project: ((xyz: [number, number, number]) => [number, number, number]) | null = viewport instanceof _GlobeViewport && viewport.resolution ? // eslint-disable-next-line @typescript-eslint/unbound-method viewport.projectPosition diff --git a/modules/geo-layers/src/tileset-2d/utils.ts b/modules/geo-layers/src/tileset-2d/utils.ts index 4de85da8edd..303e841b864 100644 --- a/modules/geo-layers/src/tileset-2d/utils.ts +++ b/modules/geo-layers/src/tileset-2d/utils.ts @@ -165,10 +165,10 @@ function getCullBoundsInViewport( const unprojectOption = {targetZ: z}; - const topLeft = viewport.unproject([x, y], unprojectOption); - const topRight = viewport.unproject([x + width, y], unprojectOption); - const bottomLeft = viewport.unproject([x, y + height], unprojectOption); - const bottomRight = viewport.unproject([x + width, y + height], unprojectOption); + const topLeft = viewport.unproject([x, y, 0], unprojectOption); + const topRight = viewport.unproject([x + width, y, 0], unprojectOption); + const bottomLeft = viewport.unproject([x, y + height, 0], unprojectOption); + const bottomRight = viewport.unproject([x + width, y + height, 0], unprojectOption); return [ Math.min(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]), @@ -204,12 +204,12 @@ function getScale(z: number, tileSize: number): number { } // https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_2 -export function osmTile2lngLat(x: number, y: number, z: number): [number, number] { +export function osmTile2lngLat(x: number, y: number, z: number): [number, number, number] { const scale = getScale(z, TILE_SIZE); const lng = (x / scale) * 360 - 180; const n = Math.PI - (2 * Math.PI * y) / scale; const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); - return [lng, lat]; + return [lng, lat, 0]; } function tile2XY(x: number, y: number, z: number, tileSize: number): [number, number] { diff --git a/modules/google-maps/src/utils.ts b/modules/google-maps/src/utils.ts index 34e4911719c..605c0e581d1 100644 --- a/modules/google-maps/src/utils.ts +++ b/modules/google-maps/src/utils.ts @@ -282,7 +282,7 @@ function getEventPixel(event, deck: Deck): {x: number; y: number} { } // event.pixel may not exist when clicking on a POI // https://developers.google.com/maps/documentation/javascript/reference/map#MouseEvent - const point = deck.getViewports()[0].project([event.latLng.lng(), event.latLng.lat()]); + const point = deck.getViewports()[0].project([event.latLng.lng(), event.latLng.lat(), 0]); return { x: point[0], y: point[1] diff --git a/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts b/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts index 675a90c6383..704741d48cd 100644 --- a/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts +++ b/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts @@ -160,13 +160,16 @@ export default class SolidPolygonLayer coordinateSystem = COORDINATE_SYSTEM.LNGLAT; } - let preproject: ((xy: number[]) => number[]) | undefined; + let preproject: ((xyz: [number, number, number]) => number[]) | undefined; if (coordinateSystem === COORDINATE_SYSTEM.LNGLAT) { if (_full3d) { preproject = viewport.projectPosition.bind(viewport); } else { - preproject = viewport.projectFlat.bind(viewport); + preproject = (xyz: [number, number, number]) => { + const [x, y] = viewport.projectFlat([xyz[0], xyz[1]]); + return [x, y, 0]; + }; } } From a8117e5a7ac5ac0214d913c339ff570e37af3f86 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Wed, 8 Jan 2025 14:01:34 -0500 Subject: [PATCH 6/7] fix: coordinate ordering --- docs/upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index d57e5827214..78d95fe8031 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -34,7 +34,7 @@ Breaking changes: ### MapViewState / ViewportOptions -- The `position` property changed from `number[]` to `[number, number, number]` (lat, lng, elevation). +- The `position` property changed from `number[]` to `[number, number, number]` (lng, lat, elevation). ## Upgrading to v9.0 From 1c7ac1c46d69d3b3907e442659d729992bb54025 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Thu, 23 Jan 2025 22:56:09 -0500 Subject: [PATCH 7/7] refactor: apply array helper types to coordinate vars --- .../src/common/utils/bounds-utils.ts | 8 +- .../src/contour-layer/contour-layer.ts | 12 +-- .../src/grid-layer/grid-layer.ts | 7 +- .../src/heatmap-layer/heatmap-layer.ts | 18 ++--- .../src/hexagon-layer/hexagon-layer.ts | 15 ++-- .../screen-grid-layer/screen-grid-layer.ts | 5 +- modules/core/src/lib/layer.ts | 25 +++--- modules/core/src/lib/picking/pick-info.ts | 8 +- modules/core/src/lib/view-manager.ts | 9 ++- .../shaderlib/project/project-functions.ts | 14 ++-- .../src/transitions/linear-interpolator.ts | 10 +-- .../src/viewports/first-person-viewport.ts | 10 ++- modules/core/src/viewports/globe-viewport.ts | 34 ++++---- modules/core/src/viewports/orbit-viewport.ts | 7 +- .../src/viewports/orthographic-viewport.ts | 7 +- modules/core/src/viewports/viewport.ts | 78 ++++++++++--------- .../src/viewports/web-mercator-viewport.ts | 15 ++-- modules/core/src/views/map-view.ts | 3 +- .../extensions/src/brushing/shader-module.ts | 4 +- .../extensions/src/terrain/terrain-cover.ts | 18 ++--- .../extensions/src/utils/projection-utils.ts | 21 ++--- .../src/h3-layers/h3-hexagon-layer.ts | 2 +- modules/geo-layers/src/h3-layers/h3-utils.ts | 4 +- .../src/tile-3d-layer/tile-3d-layer.ts | 1 - .../src/tileset-2d/tile-2d-traversal.ts | 13 ++-- modules/geo-layers/src/tileset-2d/utils.ts | 12 +-- modules/google-maps/src/utils.ts | 2 +- modules/layers/src/path-layer/path-layer.ts | 3 +- .../solid-polygon-layer.ts | 11 +-- .../simple-mesh-layer/simple-mesh-layer.ts | 7 +- .../tile-3d-layer/tile-3d-layer.spec.ts | 6 +- 31 files changed, 195 insertions(+), 194 deletions(-) diff --git a/modules/aggregation-layers/src/common/utils/bounds-utils.ts b/modules/aggregation-layers/src/common/utils/bounds-utils.ts index 17a117c0ac2..14c3b45b3bf 100644 --- a/modules/aggregation-layers/src/common/utils/bounds-utils.ts +++ b/modules/aggregation-layers/src/common/utils/bounds-utils.ts @@ -2,6 +2,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors +import {NumberArray2} from '@math.gl/core'; + /** Utility to estimate binIdRange as expected by AggregatorProps */ export function getBinIdRange({ dataBounds, @@ -9,9 +11,9 @@ export function getBinIdRange({ padding = 0 }: { /** Bounds of the input data */ - dataBounds: [min: number[], max: number[]]; + dataBounds: [min: NumberArray2, max: NumberArray2]; /** Given a data point, returns the bin id that it belongs to */ - getBinId: (p: number[]) => number[]; + getBinId: (p: NumberArray2) => NumberArray2; /** Add a border around the result to avoid clipping */ padding?: number; }): [number, number][] { @@ -20,7 +22,7 @@ export function getBinIdRange({ dataBounds[1], [dataBounds[0][0], dataBounds[1][1]], [dataBounds[1][0], dataBounds[0][1]] - ].map(p => getBinId(p)); + ].map(p => getBinId(p as NumberArray2)); const minX = Math.min(...corners.map(p => p[0])) - padding; const minY = Math.min(...corners.map(p => p[1])) - padding; diff --git a/modules/aggregation-layers/src/contour-layer/contour-layer.ts b/modules/aggregation-layers/src/contour-layer/contour-layer.ts index 9952ca64068..55aa1bb5572 100644 --- a/modules/aggregation-layers/src/contour-layer/contour-layer.ts +++ b/modules/aggregation-layers/src/contour-layer/contour-layer.ts @@ -15,6 +15,7 @@ import { UpdateParameters, DefaultProps } from '@deck.gl/core'; +import {Matrix4, type NumberArray2, type NumberArray3} from '@math.gl/core'; import {PathLayer, SolidPolygonLayer} from '@deck.gl/layers'; import {WebGLAggregator, CPUAggregator, AggregationOperation} from '../common/aggregator/index'; import AggregationLayer from '../common/aggregation-layer'; @@ -22,7 +23,6 @@ import {AggregationLayerProps} from '../common/aggregation-layer'; import {generateContours, Contour, ContourLine, ContourPolygon} from './contour-utils'; import {getAggregatorValueReader} from './value-reader'; import {getBinIdRange} from '../common/utils/bounds-utils'; -import {Matrix4} from '@math.gl/core'; import {BinOptions, binOptionsUniforms} from './bin-options-uniforms'; const DEFAULT_COLOR = [255, 255, 255, 255]; @@ -140,10 +140,10 @@ export default class GridLayer extends dimensions: 2, getBin: { sources: ['positions'], - getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { + getValue: ({positions}: {positions: NumberArray3}, index: number, opts: BinOptions) => { const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); + const p = viewport.projectPosition(positions); const {cellSizeCommon, cellOriginCommon} = opts; return [ Math.floor((p[0] - cellOriginCommon[0]) / cellSizeCommon[0]), @@ -241,7 +241,7 @@ export default class GridLayer extends let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid: [number, number] = [ + let centroid: NumberArray2 = [ (bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2 ]; @@ -274,8 +274,8 @@ export default class GridLayer extends binIdRange = getBinIdRange({ dataBounds: bounds, - getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat([p[0], p[1]]); + getBinId: p => { + const positionCommon = viewport.projectFlat(p as NumberArray2); return [ Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) diff --git a/modules/aggregation-layers/src/grid-layer/grid-layer.ts b/modules/aggregation-layers/src/grid-layer/grid-layer.ts index ed37da97358..bf08a9140d4 100644 --- a/modules/aggregation-layers/src/grid-layer/grid-layer.ts +++ b/modules/aggregation-layers/src/grid-layer/grid-layer.ts @@ -19,6 +19,7 @@ import { UpdateParameters, DefaultProps } from '@deck.gl/core'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import {WebGLAggregator, CPUAggregator, AggregationOperation} from '../common/aggregator/index'; import AggregationLayer from '../common/aggregation-layer'; import {AggregateAccessor} from '../common/types'; @@ -313,7 +314,7 @@ export default class GridLayer extends } const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); + const p = viewport.projectPosition(positions as NumberArray3); const {cellSizeCommon, cellOriginCommon} = opts; return [ Math.floor((p[0] - cellOriginCommon[0]) / cellSizeCommon[0]), @@ -446,7 +447,7 @@ export default class GridLayer extends let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid: [number, number] = [ + let centroid: NumberArray2 = [ (bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2 ]; @@ -478,7 +479,7 @@ export default class GridLayer extends binIdRange = getBinIdRange({ dataBounds: bounds, getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat([p[0], p[1]]); + const positionCommon = viewport.projectFlat(p as NumberArray2); return [ Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) diff --git a/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts b/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts index bbed01c8d9c..8109b5d1290 100644 --- a/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts +++ b/modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts @@ -28,6 +28,7 @@ import { DefaultProps, project32 } from '@deck.gl/core'; +import type {NumberArray2} from '@math.gl/core'; import TriangleLayer from './triangle-layer'; import AggregationLayer, {AggregationLayerProps} from './aggregation-layer'; import {defaultColorRange, colorRangeToFlatArray} from '../common/utils/color-utils'; @@ -492,10 +493,10 @@ export default class HeatmapLayer< // Unproject all 4 corners of the current screen coordinates into world coordinates (lng/lat) // Takes care of viewport has non zero bearing/pitch (i.e axis not aligned with world coordiante system) const viewportCorners = [ - viewport.unproject([0, 0, 0]), - viewport.unproject([viewport.width, 0, 0]), - viewport.unproject([0, viewport.height, 0]), - viewport.unproject([viewport.width, viewport.height, 0]) + viewport.unproject([0, 0]), + viewport.unproject([viewport.width, 0]), + viewport.unproject([0, viewport.height]), + viewport.unproject([viewport.width, viewport.height]) ].map(p => p.map(Math.fround)); // #1: get world bounds for current viewport extends @@ -546,10 +547,7 @@ export default class HeatmapLayer< triPositionBuffer!.write(packVertices(viewportCorners, 3)); const textureBounds = viewportCorners.map(p => - getTextureCoordinates( - viewport.projectPosition([p[0], p[1], p[2] || 0]), - normalizedCommonBounds! - ) + getTextureCoordinates(viewport.projectPosition(p as NumberArray2), normalizedCommonBounds!) ); triTexCoordBuffer!.write(packVertices(textureBounds, 2)); } @@ -694,8 +692,8 @@ export default class HeatmapLayer< _commonToWorldBounds(commonBounds) { const [xMin, yMin, xMax, yMax] = commonBounds; const {viewport} = this.context; - const bottomLeftWorld = viewport.unprojectPosition([xMin, yMin, 0]); - const topRightWorld = viewport.unprojectPosition([xMax, yMax, 0]); + const bottomLeftWorld = viewport.unprojectPosition([xMin, yMin]); + const topRightWorld = viewport.unprojectPosition([xMax, yMax]); return bottomLeftWorld.slice(0, 2).concat(topRightWorld.slice(0, 2)); } diff --git a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts index f4bcbee31db..5a3d438f1b1 100644 --- a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts +++ b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts @@ -19,6 +19,7 @@ import { UpdateParameters, DefaultProps } from '@deck.gl/core'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import {WebGLAggregator, CPUAggregator, AggregationOperation} from '../common/aggregator/index'; import AggregationLayer from '../common/aggregation-layer'; import {AggregateAccessor} from '../common/types'; @@ -311,13 +312,17 @@ export default class HexagonLayer< dimensions: 2, getBin: { sources: ['positions'], - getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { + getValue: ( + {positions}: {positions: NumberArray2 | NumberArray3}, + index: number, + opts: BinOptions + ) => { if (hexagonAggregator) { return hexagonAggregator(positions, radius); } const viewport = this.state.aggregatorViewport; // project to common space - const p = viewport.projectPosition([positions[0], positions[1], positions[2] || 0]); + const p = viewport.projectPosition(positions); const {radiusCommon, hexOriginCommon} = opts; return pointToHexbin( [p[0] - hexOriginCommon[0], p[1] - hexOriginCommon[1]], @@ -451,7 +456,7 @@ export default class HexagonLayer< let viewport = this.context.viewport; if (bounds && Number.isFinite(bounds[0][0])) { - let centroid: [number, number] = [ + let centroid: NumberArray2 = [ (bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2 ]; @@ -476,8 +481,8 @@ export default class HexagonLayer< binIdRange = getBinIdRange({ dataBounds: bounds, - getBinId: (p: number[]) => { - const positionCommon = viewport.projectFlat([p[0], p[1]]); + getBinId: (p: NumberArray2) => { + const positionCommon = viewport.projectFlat(p); positionCommon[0] -= hexOriginCommon[0]; positionCommon[1] -= hexOriginCommon[1]; return pointToHexbin(positionCommon, radiusCommon); diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts index 4f2c3edd55f..244e8662afd 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts @@ -15,6 +15,7 @@ import { UpdateParameters, DefaultProps } from '@deck.gl/core'; +import type {NumberArray3} from '@math.gl/core'; import {WebGLAggregator, CPUAggregator, AggregationOperation} from '../common/aggregator/index'; import AggregationLayer from '../common/aggregation-layer'; import ScreenGridCellLayer from './screen-grid-cell-layer'; @@ -136,9 +137,9 @@ export default class ScreenGridLayer< dimensions: 2, getBin: { sources: ['positions'], - getValue: ({positions}: {positions: number[]}, index: number, opts: BinOptions) => { + getValue: ({positions}: {positions: NumberArray3}, index: number, opts: BinOptions) => { const viewport = this.context.viewport; - const p = viewport.project([positions[0], positions[1], positions[2] || 0]); + const p = viewport.project(positions); const cellSizePixels: number = opts.cellSizePixels; if (p[0] < 0 || p[0] >= viewport.width || p[1] < 0 || p[1] >= viewport.height) { // Not on screen diff --git a/modules/core/src/lib/layer.ts b/modules/core/src/lib/layer.ts index 31730d50f48..92b16aa6e2d 100644 --- a/modules/core/src/lib/layer.ts +++ b/modules/core/src/lib/layer.ts @@ -3,8 +3,10 @@ // Copyright (c) vis.gl contributors /* eslint-disable react/no-direct-mutation-state */ -import {Buffer, Parameters as LumaParameters, TypedArray} from '@luma.gl/core'; +import {Buffer} from '@luma.gl/core'; import {WebGLDevice} from '@luma.gl/webgl'; +import {load} from '@loaders.gl/core'; +import {worldToPixels} from '@math.gl/web-mercator'; import {COORDINATE_SYSTEM} from './constants'; import AttributeManager from './attribute/attribute-manager'; import UniformTransitionManager from './uniform-transition-manager'; @@ -22,14 +24,13 @@ import typedArrayManager from '../utils/typed-array-manager'; import Component from '../lifecycle/component'; import LayerState, {ChangeFlags} from './layer-state'; -import {worldToPixels} from '@math.gl/web-mercator'; - -import {load} from '@loaders.gl/core'; - +import type {Parameters as LumaParameters, TypedArray, RenderPass} from '@luma.gl/core'; +import type {Model} from '@luma.gl/engine'; +import type {PickingProps} from '@luma.gl/shadertools'; import type {Loader} from '@loaders.gl/loader-utils'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import type {CoordinateSystem} from './constants'; import type Attribute from './attribute/attribute'; -import type {Model} from '@luma.gl/engine'; import type {PickingInfo, GetPickingInfoParams} from './picking/pick-info'; import type Viewport from '../viewports/viewport'; import type {NumericArray} from '../types/types'; @@ -37,8 +38,6 @@ import type {DefaultProps} from '../lifecycle/prop-types'; import type {LayerData, LayerProps} from '../types/layer-props'; import type {LayerContext} from './layer-manager'; import type {BinaryAttribute} from './attribute/attribute'; -import {RenderPass} from '@luma.gl/core'; -import {PickingProps} from '@luma.gl/shadertools'; const TRACE_CHANGE_FLAG = 'layer.changeFlag'; const TRACE_INITIALIZE = 'layer.initialize'; @@ -219,7 +218,7 @@ export default abstract class Layer extends Component< // Public API for users /** Projects a point with current view state from the current layer's coordinate system to screen */ - project(xyz: [number, number, number]): [number, number, number] { + project(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { assert(this.internalState); const viewport = this.internalState.viewport || this.context.viewport; @@ -235,15 +234,15 @@ export default abstract class Layer extends Component< /** Unprojects a screen pixel to the current view's default coordinate system Note: this does not reverse `project`. */ - unproject(xy: [number, number, number]): [number, number, number] { + unproject(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { assert(this.internalState); const viewport = this.internalState.viewport || this.context.viewport; - return viewport.unproject(xy); + return viewport.unproject(xyz); } /** Projects a point with current view state from the current layer's coordinate system to the world space */ projectPosition( - xyz: [number, number, number], + xyz: NumberArray2 | NumberArray3, params?: { /** The viewport to use */ viewport?: Viewport; @@ -442,7 +441,7 @@ export default abstract class Layer extends Component< } // Default implementation - getBounds(): [number[], number[]] | null { + getBounds(): [NumberArray2, NumberArray2] | null { return this.getAttributeManager()?.getBounds(['positions', 'instancePositions']); } diff --git a/modules/core/src/lib/picking/pick-info.ts b/modules/core/src/lib/picking/pick-info.ts index ae13a02f2f1..67be87e57af 100644 --- a/modules/core/src/lib/picking/pick-info.ts +++ b/modules/core/src/lib/picking/pick-info.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import type Layer from '../layer'; import type Viewport from '../../viewports/viewport'; import type {PickedPixel} from './query-object'; @@ -53,12 +54,9 @@ export function getEmptyPickingInfo({ // Find the viewport that contain the picked pixel pickedViewport = getViewportFromCoordinates(pickInfo?.pickedViewports || viewports, {x, y}); } - let coordinate: [number, number, number] | undefined; + let coordinate: NumberArray2 | NumberArray3 | undefined; if (pickedViewport) { - const point: [number, number, number] = [x - pickedViewport.x, y - pickedViewport.y, 0]; - if (z !== undefined) { - point[2] = z; - } + const point: NumberArray3 = [x - pickedViewport.x, y - pickedViewport.y, z ?? 0]; coordinate = pickedViewport.unproject(point); } diff --git a/modules/core/src/lib/view-manager.ts b/modules/core/src/lib/view-manager.ts index d53771a7f76..ca33dd99621 100644 --- a/modules/core/src/lib/view-manager.ts +++ b/modules/core/src/lib/view-manager.ts @@ -6,12 +6,13 @@ import {deepEqual} from '../utils/deep-equal'; import log from '../utils/log'; import {flatten} from '../utils/flatten'; +import type {Timeline} from '@luma.gl/engine'; +import type {NumberArray3} from '@math.gl/core'; +import type {EventManager} from 'mjolnir.js'; import type Controller from '../controllers/controller'; import type {ViewStateChangeParameters, InteractionState} from '../controllers/controller'; import type Viewport from '../viewports/viewport'; import type View from '../views/view'; -import type {Timeline} from '@luma.gl/engine'; -import type {EventManager} from 'mjolnir.js'; import type {ConstructorOf} from '../types/types'; import type {default as MapView, MapViewState} from '../views/map-view'; @@ -195,13 +196,13 @@ export default class ViewManager { * @param {Object} opts.topLeft=true - Whether origin is top left * @return {Array|null} - [lng, lat, Z] or [X, Y, Z] */ - unproject(xyz: [number, number, number], opts?: {topLeft?: boolean}): number[] | null { + unproject(xyz: NumberArray3, opts?: {topLeft?: boolean}): number[] | null { const viewports = this.getViewports(); const pixel = {x: xyz[0], y: xyz[1]}; for (let i = viewports.length - 1; i >= 0; --i) { const viewport = viewports[i]; if (viewport.containsPixel(pixel)) { - const p = xyz.slice() as [number, number, number]; + const p = xyz.slice() as NumberArray3; p[0] -= viewport.x; p[1] -= viewport.y; return viewport.unproject(p, opts); diff --git a/modules/core/src/shaderlib/project/project-functions.ts b/modules/core/src/shaderlib/project/project-functions.ts index 8936ce04a4e..ca722621dac 100644 --- a/modules/core/src/shaderlib/project/project-functions.ts +++ b/modules/core/src/shaderlib/project/project-functions.ts @@ -10,14 +10,14 @@ import {COORDINATE_SYSTEM} from '../../lib/constants'; import {getOffsetOrigin} from './viewport-uniforms'; import WebMercatorViewport from '../../viewports/web-mercator-viewport'; -import {vec3, vec4} from '@math.gl/core'; +import {type NumberArray2, type NumberArray3, vec3, vec4} from '@math.gl/core'; import {addMetersToLngLat} from '@math.gl/web-mercator'; import type {CoordinateSystem} from '../../lib/constants'; import type Viewport from '../../viewports/viewport'; import type {NumericArray} from '../../types/types'; -const DEFAULT_COORDINATE_ORIGIN: [number, number, number] = [0, 0, 0]; +const DEFAULT_COORDINATE_ORIGIN: NumberArray3 = [0, 0, 0]; // In project.glsl, offset modes calculate z differently from LNG_LAT mode. // offset modes apply the y adjustment (unitsPerMeter2) when projecting z @@ -27,7 +27,7 @@ function lngLatZToWorldPosition( viewport: Viewport, offsetMode: boolean = false ): [number, number, number] { - const p = viewport.projectPosition(lngLatZ); + const p = viewport.projectPosition(lngLatZ) as NumberArray3; // TODO - avoid using instanceof if (offsetMode && viewport instanceof WebMercatorViewport) { @@ -81,7 +81,7 @@ function normalizeParameters(opts: { /** Get the common space position from world coordinates in the given coordinate system */ export function getWorldPosition( - position: [number, number, number], + position: NumberArray2 | NumberArray3, { viewport, modelMatrix, @@ -124,7 +124,7 @@ export function getWorldPosition( default: return viewport.isGeospatial ? [x + coordinateOrigin[0], y + coordinateOrigin[1], z + coordinateOrigin[2]] - : viewport.projectPosition([x, y, z]); + : (viewport.projectPosition([x, y, z]) as NumberArray3); } } @@ -134,7 +134,7 @@ export function getWorldPosition( * a reference coordinate system */ export function projectPosition( - position: [number, number, number], + position: NumberArray2 | NumberArray3, params: { /** The current viewport */ viewport: Viewport; @@ -155,7 +155,7 @@ export function projectPosition( * Default `true` */ autoOffset?: boolean; } -): [number, number, number] { +): NumberArray3 { const { viewport, coordinateSystem, diff --git a/modules/core/src/transitions/linear-interpolator.ts b/modules/core/src/transitions/linear-interpolator.ts index 84477c9e0b3..579ce6f9273 100644 --- a/modules/core/src/transitions/linear-interpolator.ts +++ b/modules/core/src/transitions/linear-interpolator.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors +import {lerp, type NumberArray3} from '@math.gl/core'; import TransitionInterpolator from './transition-interpolator'; -import {lerp} from '@math.gl/core'; import type Viewport from '../viewports/viewport'; @@ -11,7 +11,7 @@ const DEFAULT_PROPS = ['longitude', 'latitude', 'zoom', 'bearing', 'pitch']; const DEFAULT_REQUIRED_PROPS = ['longitude', 'latitude', 'zoom']; type PropsWithAnchor = { - around?: [number, number, number]; + around?: NumberArray3; aroundPosition?: number[]; [key: string]: any; }; @@ -21,7 +21,7 @@ type PropsWithAnchor = { */ export default class LinearInterpolator extends TransitionInterpolator { opts: { - around?: [number, number, number]; + around?: NumberArray3; makeViewport?: (props: Record) => Viewport; }; @@ -42,7 +42,7 @@ export default class LinearInterpolator extends TransitionInterpolator { extract?: string[]; required?: string[]; }; - around?: [number, number, number]; + around?: NumberArray3; makeViewport?: (props: Record) => Viewport; } = {} ) { @@ -77,7 +77,7 @@ export default class LinearInterpolator extends TransitionInterpolator { if (makeViewport && around) { const startViewport = makeViewport(startProps); const endViewport = makeViewport(endProps); - const aroundPosition = startViewport.unproject(around); + const aroundPosition = startViewport.unproject(around) as NumberArray3; result.start.around = around; Object.assign(result.end, { around: endViewport.project(aroundPosition), diff --git a/modules/core/src/viewports/first-person-viewport.ts b/modules/core/src/viewports/first-person-viewport.ts index a60a71f76ef..f5931bc7ce5 100644 --- a/modules/core/src/viewports/first-person-viewport.ts +++ b/modules/core/src/viewports/first-person-viewport.ts @@ -2,9 +2,13 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import Viewport from '../viewports/viewport'; +import { + Matrix4, + type NumberArray3, + _SphericalCoordinates as SphericalCoordinates +} from '@math.gl/core'; import {getMeterZoom} from '@math.gl/web-mercator'; -import {Matrix4, _SphericalCoordinates as SphericalCoordinates} from '@math.gl/core'; +import Viewport from '../viewports/viewport'; export type FirstPersonViewportOptions = { /** Name of the viewport */ @@ -22,7 +26,7 @@ export type FirstPersonViewportOptions = { /** Latitude of the camera, in the geospatial case. */ latitude?: number; /** Meter offsets of the camera from the lng-lat-elevation anchor point. Default `[0, 0, 0]`. */ - position?: [number, number, number]; + position?: NumberArray3; /** Bearing (heading) of the camera in degrees. Default `0` (north). */ bearing?: number; /** Pitch (tilt) of the camera in degrees. Default `0` (horizontal). */ diff --git a/modules/core/src/viewports/globe-viewport.ts b/modules/core/src/viewports/globe-viewport.ts index 1fa011d1502..d615e175803 100644 --- a/modules/core/src/viewports/globe-viewport.ts +++ b/modules/core/src/viewports/globe-viewport.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import {Matrix4} from '@math.gl/core'; +import {Matrix4, type NumberArray2, type NumberArray3} from '@math.gl/core'; import Viewport from './viewport'; import {PROJECTION_MODE} from '../lib/constants'; import {altitudeToFovy, fovyToAltitude} from '@math.gl/web-mercator'; @@ -47,7 +47,7 @@ export type GlobeViewportOptions = { /** Camera altitude relative to the viewport height, used to control the FOV. Default `1.5` */ altitude?: number; /* Meter offsets of the viewport center from lng, lat */ - position?: [number, number, number]; + position?: NumberArray3; /** Zoom level */ zoom?: number; /** Use orthographic projection */ @@ -138,10 +138,10 @@ export default class GlobeViewport extends Viewport { getBounds(options: {z?: number} = {}): [number, number, number, number] { const unprojectOption = {targetZ: options.z || 0}; - const left = this.unproject([0, this.height / 2, 0], unprojectOption); - const top = this.unproject([this.width / 2, 0, 0], unprojectOption); - const right = this.unproject([this.width, this.height / 2, 0], unprojectOption); - const bottom = this.unproject([this.width / 2, this.height, 0], unprojectOption); + const left = this.unproject([0, this.height / 2], unprojectOption); + const top = this.unproject([this.width / 2, 0], unprojectOption); + const right = this.unproject([this.width, this.height / 2], unprojectOption); + const bottom = this.unproject([this.width / 2, this.height], unprojectOption); if (right[0] < this.longitude) right[0] += 360; if (left[0] > this.longitude) left[0] -= 360; @@ -155,10 +155,10 @@ export default class GlobeViewport extends Viewport { } unproject( - xyz: [number, number, number], + xyz: NumberArray2 | NumberArray3, {topLeft = true, targetZ}: {topLeft?: boolean; targetZ?: number} = {} - ): [number, number, number] { - const [x, y, z] = xyz; + ): NumberArray2 | NumberArray3 { + const [x, y, z = 0] = xyz; const y2 = topLeft ? y : this.height - y; const {pixelUnprojectionMatrix} = this; @@ -188,12 +188,12 @@ export default class GlobeViewport extends Viewport { const [X, Y, Z] = this.unprojectPosition(coord); if (Number.isFinite(z)) { - return [X, Y, Z]; + return Z ? [X, Y, Z] : [X, Y]; } - return Number.isFinite(targetZ) ? [X, Y, targetZ as number] : [X, Y, 0]; + return Number.isFinite(targetZ) ? [X, Y, targetZ as number] : [X, Y]; } - projectPosition(xyz: [number, number, number]): [number, number, number] { + projectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { const [lng, lat, Z = 0] = xyz; const lambda = lng * DEGREES_TO_RADIANS; const phi = lat * DEGREES_TO_RADIANS; @@ -203,8 +203,8 @@ export default class GlobeViewport extends Viewport { return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D]; } - unprojectPosition(xyz: [number, number, number]): [number, number, number] { - const [x, y, z] = xyz; + unprojectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { + const [x, y, z = 0] = xyz; const D = vec3.len(xyz); const phi = Math.asin(z / D); const lambda = Math.atan2(x, -y); @@ -215,15 +215,15 @@ export default class GlobeViewport extends Viewport { return [lng, lat, Z]; } - projectFlat(xy: [number, number]): [number, number] { + projectFlat(xy: NumberArray2): NumberArray2 { return xy; } - unprojectFlat(xy: [number, number]): [number, number] { + unprojectFlat(xy: NumberArray2): NumberArray2 { return xy; } - panByPosition(coords: number[], pixel: [number, number, number]): GlobeViewportOptions { + panByPosition(coords: number[], pixel: NumberArray2 | NumberArray3): GlobeViewportOptions { const fromPosition = this.unproject(pixel); return { longitude: coords[0] - fromPosition[0] + this.longitude, diff --git a/modules/core/src/viewports/orbit-viewport.ts b/modules/core/src/viewports/orbit-viewport.ts index 3c8c159b32d..a329c99668a 100644 --- a/modules/core/src/viewports/orbit-viewport.ts +++ b/modules/core/src/viewports/orbit-viewport.ts @@ -2,10 +2,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import Viewport from '../viewports/viewport'; - -import {Matrix4} from '@math.gl/core'; +import {Matrix4, type NumberArray3} from '@math.gl/core'; import {pixelsToWorld, fovyToAltitude} from '@math.gl/web-mercator'; +import Viewport from '../viewports/viewport'; const DEGREES_TO_RADIANS = Math.PI / 180; @@ -136,7 +135,7 @@ export default class OrbitViewport extends Viewport { return [X, Y, Z]; } - panByPosition(coords: [number, number, number], pixel: number[]): OrbitViewportOptions { + panByPosition(coords: NumberArray3, pixel: number[]): OrbitViewportOptions { const p0 = this.project(coords); const nextCenter = [ this.width / 2 + p0[0] - pixel[0], diff --git a/modules/core/src/viewports/orthographic-viewport.ts b/modules/core/src/viewports/orthographic-viewport.ts index 96c008fe2fc..7e4d32996c7 100644 --- a/modules/core/src/viewports/orthographic-viewport.ts +++ b/modules/core/src/viewports/orthographic-viewport.ts @@ -2,10 +2,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import Viewport from '../viewports/viewport'; - -import {Matrix4, clamp, vec2} from '@math.gl/core'; +import {Matrix4, type NumberArray3, clamp, vec2} from '@math.gl/core'; import {pixelsToWorld} from '@math.gl/web-mercator'; +import Viewport from '../viewports/viewport'; import type {Padding} from './viewport'; @@ -60,7 +59,7 @@ export type OrthographicViewportOptions = { /** Viewport height in pixels */ height?: number; /** The world position at the center of the viewport. Default `[0, 0, 0]`. */ - target?: [number, number, number]; + target?: NumberArray3; /** 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]; diff --git a/modules/core/src/viewports/viewport.ts b/modules/core/src/viewports/viewport.ts index f60725f6536..9d7a1fd514d 100644 --- a/modules/core/src/viewports/viewport.ts +++ b/modules/core/src/viewports/viewport.ts @@ -2,11 +2,15 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors -import log from '../utils/log'; -import {createMat4, getCameraPosition, getFrustumPlanes, FrustumPlane} from '../utils/math-utils'; - -import {Matrix4, Vector3, equals, clamp, mat4} from '@math.gl/core'; - +import { + Matrix4, + Vector3, + equals, + clamp, + mat4, + type NumberArray2, + type NumberArray3 +} from '@math.gl/core'; import { getDistanceScales, getMeterZoom, @@ -15,7 +19,8 @@ import { worldToPixels, pixelsToWorld } from '@math.gl/web-mercator'; - +import log from '../utils/log'; +import {createMat4, getCameraPosition, getFrustumPlanes, FrustumPlane} from '../utils/math-utils'; import {PROJECTION_MODE} from '../lib/constants'; export type DistanceScales = { @@ -46,7 +51,7 @@ export type ViewportOptions = { /** Latitude in degrees (geospatial only) */ latitude?: number; /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat, elevation */ - position?: [number, number, number]; + position?: NumberArray3; /** Zoom level */ zoom?: number; /** Padding around the viewport, in pixels. */ @@ -76,7 +81,7 @@ const DEGREES_TO_RADIANS = Math.PI / 180; const IDENTITY = createMat4(); -const ZERO_VECTOR: [number, number, number] = [0, 0, 0]; +const ZERO_VECTOR: NumberArray3 = [0, 0, 0]; const DEFAULT_DISTANCE_SCALES: DistanceScales = { unitsPerMeter: [1, 1, 1], @@ -138,7 +143,7 @@ export default class Viewport { isGeospatial: boolean; zoom: number; focalDistance: number; - position: [number, number, number]; + position: NumberArray3; modelMatrix: number[] | null; /** Derived parameters */ @@ -148,8 +153,8 @@ export default class Viewport { distanceScales: DistanceScales; /** scale factors between world space and common space */ scale!: number; /** scale factor, equals 2^zoom */ - center!: [number, number, number]; /** viewport center in common space */ - cameraPosition!: [number, number, number]; /** Camera position in common space */ + center!: NumberArray2 | NumberArray3; /** viewport center in common space */ + cameraPosition!: NumberArray2 | NumberArray3; /** Camera position in common space */ projectionMatrix!: number[]; viewMatrix!: number[]; viewMatrixUncentered!: number[]; @@ -242,13 +247,16 @@ export default class Viewport { * @param {Object} opts.topLeft=true - Whether projected coords are top left * @return {Array} - [x, y] or [x, y, z] in top left coords */ - project(xyz: [number, number, number], {topLeft = true}: {topLeft?: boolean} = {}): number[] { + project( + xyz: NumberArray2 | NumberArray3, + {topLeft = true}: {topLeft?: boolean} = {} + ): NumberArray2 | NumberArray3 { const worldPosition = this.projectPosition(xyz); const coord = worldToPixels(worldPosition, this.pixelProjectionMatrix); const [x, y] = coord; const y2 = topLeft ? y : this.height - y; - return [x, y2, coord[2]]; + return [x, y2, coord[2] ?? 0]; } /** @@ -262,22 +270,22 @@ export default class Viewport { * @return {Array|null} - [lng, lat, Z] or [X, Y, Z] */ unproject( - xyz: [number, number, number], + xyz: NumberArray2 | NumberArray3, {topLeft = true, targetZ}: {topLeft?: boolean; targetZ?: number} = {} - ): [number, number, number] { - const [x, y, z] = xyz; + ): NumberArray2 | NumberArray3 { + const [x, y, z = 0] = xyz; const y2 = topLeft ? y : this.height - y; const targetZWorld = targetZ && targetZ * this.distanceScales.unitsPerMeter[2]; - const coord = pixelsToWorld([x, y2, z], this.pixelUnprojectionMatrix, targetZWorld) as [ - number, - number, - number - ]; + const coord = pixelsToWorld( + [x, y2, z], + this.pixelUnprojectionMatrix, + targetZWorld + ) as NumberArray3; const [X, Y, Z] = this.unprojectPosition(coord); if (Number.isFinite(z)) { - return [X, Y, Z]; + return Z ? [X, Y, Z] : [X, Y]; } return [X, Y, targetZ as number]; } @@ -285,13 +293,13 @@ export default class Viewport { // NON_LINEAR PROJECTION HOOKS // Used for web meractor projection - projectPosition(xyz: [number, number, number]): [number, number, number] { + projectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { const [X, Y] = this.projectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) * this.distanceScales.unitsPerMeter[2]; return [X, Y, Z]; } - unprojectPosition(xyz: [number, number, number]): [number, number, number] { + unprojectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { const [X, Y] = this.unprojectFlat([xyz[0], xyz[1]]); const Z = (xyz[2] || 0) * this.distanceScales.metersPerUnit[2]; return [X, Y, Z]; @@ -306,14 +314,14 @@ export default class Viewport { * Specifies a point on the sphere to project onto the map. * @return {Array} [x,y] coordinates. */ - projectFlat(xy: [number, number]): [number, number] { + projectFlat(xy: NumberArray2): NumberArray2 { if (this.isGeospatial) { // Shader clamps latitude to +-89.9, see /shaderlib/project/project.glsl.js // lngLatToWorld([0, -89.9])[1] = -317.9934163758329 // lngLatToWorld([0, 89.9])[1] = 829.9934163758271 const result = lngLatToWorld(xy); result[1] = clamp(result[1], -318, 830); - return [result[0], result[1]]; + return result; } return xy; } @@ -326,9 +334,9 @@ export default class Viewport { * Has toArray method if you need a GeoJSON Array. * Per cartographic tradition, lat and lon are specified as degrees. */ - unprojectFlat(xy: [number, number]): [number, number] { + unprojectFlat(xy: NumberArray2): NumberArray2 { if (this.isGeospatial) { - return worldToLngLat(xy); + return worldToLngLat(xy as [number, number]); } return xy; } @@ -340,10 +348,10 @@ export default class Viewport { getBounds(options: {z?: number} = {}): [number, number, number, number] { const unprojectOption = {targetZ: options.z || 0}; - const topLeft = this.unproject([0, 0, 0], unprojectOption); - const topRight = this.unproject([this.width, 0, 0], unprojectOption); - const bottomLeft = this.unproject([0, this.height, 0], unprojectOption); - const bottomRight = this.unproject([this.width, this.height, 0], unprojectOption); + const topLeft = this.unproject([0, 0], unprojectOption); + const topRight = this.unproject([this.width, 0], unprojectOption); + const bottomLeft = this.unproject([0, this.height], unprojectOption); + const bottomRight = this.unproject([this.width, this.height], unprojectOption); return [ Math.min(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]), @@ -434,10 +442,10 @@ export default class Viewport { this.scale = scale; const {position, modelMatrix} = opts; - let meterOffset: [number, number, number] = ZERO_VECTOR; + let meterOffset: NumberArray3 = ZERO_VECTOR; if (position) { meterOffset = modelMatrix - ? (new Matrix4(modelMatrix).transformAsVector(position, []) as [number, number, number]) + ? (new Matrix4(modelMatrix).transformAsVector(position, []) as NumberArray3) : position; } @@ -448,7 +456,7 @@ export default class Viewport { this.center = new Vector3(meterOffset) // Convert to pixels in current zoom .scale(this.distanceScales.unitsPerMeter) - .toArray() as [number, number, number]; + .toArray() as NumberArray3; } else { this.center = this.projectPosition(meterOffset); } diff --git a/modules/core/src/viewports/web-mercator-viewport.ts b/modules/core/src/viewports/web-mercator-viewport.ts index 45ec9b7f328..2cc8359c0b8 100644 --- a/modules/core/src/viewports/web-mercator-viewport.ts +++ b/modules/core/src/viewports/web-mercator-viewport.ts @@ -4,8 +4,7 @@ // View and Projection Matrix calculations for mapbox-js style // map view properties -import Viewport from './viewport'; - +import {Matrix4, type NumberArray2, type NumberArray3, clamp, vec2} from '@math.gl/core'; import { pixelsToWorld, getViewMatrix, @@ -17,9 +16,7 @@ import { fitBounds, getBounds } from '@math.gl/web-mercator'; -import {Padding} from './viewport'; - -import {Matrix4, clamp, vec2} from '@math.gl/core'; +import Viewport, {Padding} from './viewport'; export type WebMercatorViewportOptions = { /** Name of the viewport */ @@ -45,7 +42,7 @@ export type WebMercatorViewportOptions = { /** Camera fovy in degrees. If provided, overrides `altitude` */ fovy?: number; /** Viewport center in world space. If geospatial, refers to meter offsets from lng, lat, elevation */ - position?: [number, number, number]; + position?: NumberArray3; /** Zoom level */ zoom?: number; /** Padding around the viewport, in pixels. */ @@ -236,7 +233,7 @@ export default class WebMercatorViewport extends Viewport { return this._subViewports; } - projectPosition(xyz: [number, number, number]): [number, number, number] { + projectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { if (this._pseudoMeters) { // Backward compatibility return super.projectPosition(xyz); @@ -246,7 +243,7 @@ export default class WebMercatorViewport extends Viewport { return [X, Y, Z]; } - unprojectPosition(xyz: [number, number, number]): [number, number, number] { + unprojectPosition(xyz: NumberArray2 | NumberArray3): NumberArray2 | NumberArray3 { if (this._pseudoMeters) { // Backward compatibility return super.unprojectPosition(xyz); @@ -270,7 +267,7 @@ export default class WebMercatorViewport extends Viewport { return addMetersToLngLat(lngLatZ, xyz); } - panByPosition(coords: [number, number], pixel: number[]): WebMercatorViewportOptions { + panByPosition(coords: NumberArray2, pixel: number[]): WebMercatorViewportOptions { const fromLocation = pixelsToWorld(pixel, this.pixelUnprojectionMatrix); const toLocation = this.projectFlat(coords); diff --git a/modules/core/src/views/map-view.ts b/modules/core/src/views/map-view.ts index 201b14cc877..4cc5c672bb3 100644 --- a/modules/core/src/views/map-view.ts +++ b/modules/core/src/views/map-view.ts @@ -6,6 +6,7 @@ import View, {CommonViewState, CommonViewProps} from './view'; import WebMercatorViewport from '../viewports/web-mercator-viewport'; import MapController from '../controllers/map-controller'; +import type {NumberArray3} from '@math.gl/core'; import type {NumericArray} from '../types/types'; export type MapViewState = { @@ -28,7 +29,7 @@ export type MapViewState = { /** Max pitch, default `60` */ maxPitch?: number; /** Viewport center offsets from lng, lat, and elevation in meters */ - position?: [number, number, number]; + position?: NumberArray3; } & CommonViewState; export type MapViewProps = { diff --git a/modules/extensions/src/brushing/shader-module.ts b/modules/extensions/src/brushing/shader-module.ts index 263d7143ceb..e74a0a8ff9c 100644 --- a/modules/extensions/src/brushing/shader-module.ts +++ b/modules/extensions/src/brushing/shader-module.ts @@ -6,7 +6,7 @@ import type {ShaderModule} from '@luma.gl/shadertools'; import {project} from '@deck.gl/core'; import type {Viewport} from '@deck.gl/core'; - +import type {NumberArray2} from '@math.gl/core'; import type {BrushingExtensionProps} from './brushing-extension'; export type BrushingModuleProps = { @@ -131,7 +131,7 @@ export default { mousePos: mousePosition ? (viewport .unproject([mousePosition.x - viewport.x, mousePosition.y - viewport.y, 0]) - .slice(0, 2) as [number, number]) + .slice(0, 2) as NumberArray2) : [0, 0] }; }, diff --git a/modules/extensions/src/terrain/terrain-cover.ts b/modules/extensions/src/terrain/terrain-cover.ts index ef27c620047..9f5feb69747 100644 --- a/modules/extensions/src/terrain/terrain-cover.ts +++ b/modules/extensions/src/terrain/terrain-cover.ts @@ -3,14 +3,14 @@ // Copyright (c) vis.gl contributors import {Framebuffer} from '@luma.gl/core'; - import type {Layer, Viewport} from '@deck.gl/core'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import {createRenderTarget} from './utils'; import {joinLayerBounds, makeViewport, getRenderBounds, Bounds} from '../utils/projection-utils'; type TileHeader = { - boundingBox: [min: number[], max: number[]]; + boundingBox: [min: NumberArray2 | NumberArray3, max: NumberArray2 | NumberArray3]; }; /** @@ -34,7 +34,7 @@ export class TerrainCover { private layers: string[] = []; private tile: TileHeader | null; /** Cached version of targetLayer.getBounds() */ - private targetBounds: [number[], number[]] | null = null; + private targetBounds: [NumberArray2 | NumberArray3, NumberArray2 | NumberArray3] | null = null; /** targetBounds in cartesian space */ private targetBoundsCommon: Bounds | null = null; @@ -117,16 +117,8 @@ export class TerrainCover { shouldRedraw = true; this.targetBounds = this.tile.boundingBox; - const bottomLeftCommon = viewport.projectPosition([ - this.targetBounds[0][0], - this.targetBounds[0][1], - this.targetBounds[0][2] || 0 - ]); - const topRightCommon = viewport.projectPosition([ - this.targetBounds[1][0], - this.targetBounds[1][1], - this.targetBounds[1][2] || 0 - ]); + const bottomLeftCommon = viewport.projectPosition(this.targetBounds[0]); + const topRightCommon = viewport.projectPosition(this.targetBounds[1]); this.targetBoundsCommon = [ bottomLeftCommon[0], bottomLeftCommon[1], diff --git a/modules/extensions/src/utils/projection-utils.ts b/modules/extensions/src/utils/projection-utils.ts index 6ed485bf4dc..6092107eab4 100644 --- a/modules/extensions/src/utils/projection-utils.ts +++ b/modules/extensions/src/utils/projection-utils.ts @@ -4,6 +4,7 @@ import {WebMercatorViewport, OrthographicViewport} from '@deck.gl/core'; import type {Layer, Viewport} from '@deck.gl/core'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; /** Bounds in CARTESIAN coordinates */ export type Bounds = [minX: number, minY: number, maxX: number, maxY: number]; @@ -23,14 +24,8 @@ export function joinLayerBounds( for (const layer of layers) { const layerBounds = layer.getBounds(); if (layerBounds) { - const bottomLeftCommon = layer.projectPosition([layerBounds[0][0], layerBounds[0][1], 0], { - viewport, - autoOffset: false - }); - const topRightCommon = layer.projectPosition([layerBounds[1][0], layerBounds[1][1], 0], { - viewport, - autoOffset: false - }); + const bottomLeftCommon = layer.projectPosition(layerBounds[0], {viewport, autoOffset: false}); + const topRightCommon = layer.projectPosition(layerBounds[1], {viewport, autoOffset: false}); bounds[0] = Math.min(bounds[0], bottomLeftCommon[0]); bounds[1] = Math.min(bounds[1], bottomLeftCommon[1]); @@ -73,7 +68,7 @@ export function makeViewport(opts: { (bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, 0 - ]); + ]) as NumberArray3; let {width, height, zoom} = opts; if (zoom === undefined) { @@ -141,10 +136,10 @@ export function getViewportBounds(viewport: Viewport, zRange?: [number, number]) } // Viewport bounds in cartesian coordinates - const bottomLeft = viewportBoundsWorld.slice(0, 2); - const topRight = viewportBoundsWorld.slice(2, 4); - const viewportBottomLeftCommon = viewport.projectPosition([bottomLeft[0], bottomLeft[1], 0]); - const viewportTopRightCommon = viewport.projectPosition([topRight[0], topRight[1], 0]); + const bottomLeft = viewportBoundsWorld.slice(0, 2) as NumberArray2; + const topRight = viewportBoundsWorld.slice(2, 4) as NumberArray2; + const viewportBottomLeftCommon = viewport.projectPosition(bottomLeft); + const viewportTopRightCommon = viewport.projectPosition(topRight); return [ viewportBottomLeftCommon[0], viewportBottomLeftCommon[1], diff --git a/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts b/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts index d720c4d7031..20220c174f1 100644 --- a/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts +++ b/modules/geo-layers/src/h3-layers/h3-hexagon-layer.ts @@ -211,7 +211,7 @@ export default class H3HexagonLayer< const [centerX, centerY] = viewport.projectFlat([centerLng, centerLat]); vertices = vertices.map(p => { - const worldPosition = viewport.projectFlat([p[0], p[1]]); + const worldPosition = viewport.projectFlat(p); return [ (worldPosition[0] - centerX) / unitsPerMeter[0], (worldPosition[1] - centerY) / unitsPerMeter[1] diff --git a/modules/geo-layers/src/h3-layers/h3-utils.ts b/modules/geo-layers/src/h3-layers/h3-utils.ts index 0ffa9fa524d..9188204840f 100644 --- a/modules/geo-layers/src/h3-layers/h3-utils.ts +++ b/modules/geo-layers/src/h3-layers/h3-utils.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors import {CoordPair, H3IndexInput, cellToBoundary, cellToLatLng} from 'h3-js'; -import {lerp} from '@math.gl/core'; +import {lerp, NumberArray2} from '@math.gl/core'; // normalize longitudes w.r.t center (refLng), when not provided first vertex export function normalizeLongitudes(vertices: CoordPair[], refLng?: number): void { @@ -42,7 +42,7 @@ export function getHexagonCentroid(getHexagon, object, objectInfo) { return [lng, lat]; } -export function h3ToPolygon(hexId: H3IndexInput, coverage: number = 1): number[][] { +export function h3ToPolygon(hexId: H3IndexInput, coverage: number = 1): NumberArray2[] { const vertices = cellToBoundary(hexId, true); if (coverage !== 1) { diff --git a/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts b/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts index f1df582580c..6c3c5d37050 100644 --- a/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts +++ b/modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts @@ -241,7 +241,6 @@ export default class Tile3DLayer exten return; } - //@ts-expect-error Type 'Vector3' is not assignable to type '[number, number, number]' tileset3d.selectTiles(Object.values(viewports)).then(frameNumber => { const tilesetChanged = this.state.frameNumber !== frameNumber; if (tilesetChanged) { diff --git a/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts b/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts index 2d1bde9185d..609ca0e6979 100644 --- a/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts +++ b/modules/geo-layers/src/tileset-2d/tile-2d-traversal.ts @@ -13,6 +13,7 @@ import { import {lngLatToWorld} from '@math.gl/web-mercator'; import {Bounds, TileIndex, ZRange} from './types'; import {osmTile2lngLat} from './utils'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; const TILE_SIZE = 512; // number of world copies to check @@ -70,7 +71,7 @@ class OSMNode { // eslint-disable-next-line complexity update(params: { viewport: Viewport; - project: ((xyz: [number, number, number]) => [number, number, number]) | null; + project: ((xyz: NumberArray2 | NumberArray3) => NumberArray2 | NumberArray3) | null; cullingVolume: CullingVolume; elevationBounds: ZRange; minZ: number; @@ -144,7 +145,7 @@ class OSMNode { getBoundingVolume( zRange: ZRange, worldOffset: number, - project: ((xyz: [number, number, number]) => [number, number, number]) | null + project: ((xyz: NumberArray2 | NumberArray3) => NumberArray2 | NumberArray3) | null ) { if (project) { // Custom projection @@ -155,11 +156,7 @@ class OSMNode { // Convert from tile-relative coordinates to common space const refPointPositions: number[][] = []; for (const p of refPoints) { - const lngLat: [number, number, number] = osmTile2lngLat( - this.x + p[0], - this.y + p[1], - this.z - ); + const lngLat: NumberArray3 = osmTile2lngLat(this.x + p[0], this.y + p[1], this.z); lngLat[2] = zRange[0]; refPointPositions.push(project(lngLat)); @@ -194,7 +191,7 @@ export function getOSMTileIndices( zRange: ZRange | null, bounds?: Bounds ): TileIndex[] { - const project: ((xyz: [number, number, number]) => [number, number, number]) | null = + const project: ((xyz: NumberArray2 | NumberArray3) => NumberArray2 | NumberArray3) | null = viewport instanceof _GlobeViewport && viewport.resolution ? // eslint-disable-next-line @typescript-eslint/unbound-method viewport.projectPosition diff --git a/modules/geo-layers/src/tileset-2d/utils.ts b/modules/geo-layers/src/tileset-2d/utils.ts index 303e841b864..56ffd2aedb2 100644 --- a/modules/geo-layers/src/tileset-2d/utils.ts +++ b/modules/geo-layers/src/tileset-2d/utils.ts @@ -3,7 +3,7 @@ // Copyright (c) vis.gl contributors import {Viewport} from '@deck.gl/core'; -import {Matrix4} from '@math.gl/core'; +import {Matrix4, type NumberArray3} from '@math.gl/core'; import {getOSMTileIndices} from './tile-2d-traversal'; import {Bounds, GeoBoundingBox, TileBoundingBox, TileIndex, ZRange} from './types'; @@ -165,10 +165,10 @@ function getCullBoundsInViewport( const unprojectOption = {targetZ: z}; - const topLeft = viewport.unproject([x, y, 0], unprojectOption); - const topRight = viewport.unproject([x + width, y, 0], unprojectOption); - const bottomLeft = viewport.unproject([x, y + height, 0], unprojectOption); - const bottomRight = viewport.unproject([x + width, y + height, 0], unprojectOption); + const topLeft = viewport.unproject([x, y], unprojectOption); + const topRight = viewport.unproject([x + width, y], unprojectOption); + const bottomLeft = viewport.unproject([x, y + height], unprojectOption); + const bottomRight = viewport.unproject([x + width, y + height], unprojectOption); return [ Math.min(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]), @@ -204,7 +204,7 @@ function getScale(z: number, tileSize: number): number { } // https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_2 -export function osmTile2lngLat(x: number, y: number, z: number): [number, number, number] { +export function osmTile2lngLat(x: number, y: number, z: number): NumberArray3 { const scale = getScale(z, TILE_SIZE); const lng = (x / scale) * 360 - 180; const n = Math.PI - (2 * Math.PI * y) / scale; diff --git a/modules/google-maps/src/utils.ts b/modules/google-maps/src/utils.ts index 605c0e581d1..34e4911719c 100644 --- a/modules/google-maps/src/utils.ts +++ b/modules/google-maps/src/utils.ts @@ -282,7 +282,7 @@ function getEventPixel(event, deck: Deck): {x: number; y: number} { } // event.pixel may not exist when clicking on a POI // https://developers.google.com/maps/documentation/javascript/reference/map#MouseEvent - const point = deck.getViewports()[0].project([event.latLng.lng(), event.latLng.lat(), 0]); + const point = deck.getViewports()[0].project([event.latLng.lng(), event.latLng.lat()]); return { x: point[0], y: point[1] diff --git a/modules/layers/src/path-layer/path-layer.ts b/modules/layers/src/path-layer/path-layer.ts index fe8d61c7677..0894e902541 100644 --- a/modules/layers/src/path-layer/path-layer.ts +++ b/modules/layers/src/path-layer/path-layer.ts @@ -24,6 +24,7 @@ import type { DefaultProps } from '@deck.gl/core'; import type {PathGeometry} from './path'; +import {NumberArray2} from '@math.gl/core'; type _PathLayerProps = { data: LayerDataSource; @@ -142,7 +143,7 @@ export default class PathLayer extends return false; } - getBounds(): [number[], number[]] | null { + getBounds(): [NumberArray2, NumberArray2] | null { return this.getAttributeManager()?.getBounds(['vertexPositions']); } diff --git a/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts b/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts index 704741d48cd..85d5f9caf2b 100644 --- a/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts +++ b/modules/layers/src/solid-polygon-layer/solid-polygon-layer.ts @@ -26,6 +26,7 @@ import type { PickingInfo, DefaultProps } from '@deck.gl/core'; +import type {NumberArray2, NumberArray3} from '@math.gl/core'; import type {PolygonGeometry} from './polygon'; type _SolidPolygonLayerProps = { @@ -148,7 +149,7 @@ export default class SolidPolygonLayer return false; } - getBounds(): [number[], number[]] | null { + getBounds(): [NumberArray2, NumberArray2] | null { return this.getAttributeManager()?.getBounds(['vertexPositions']); } @@ -160,15 +161,15 @@ export default class SolidPolygonLayer coordinateSystem = COORDINATE_SYSTEM.LNGLAT; } - let preproject: ((xyz: [number, number, number]) => number[]) | undefined; + let preproject: ((xyz: NumberArray3) => NumberArray2 | NumberArray3) | undefined; if (coordinateSystem === COORDINATE_SYSTEM.LNGLAT) { if (_full3d) { preproject = viewport.projectPosition.bind(viewport); } else { - preproject = (xyz: [number, number, number]) => { - const [x, y] = viewport.projectFlat([xyz[0], xyz[1]]); - return [x, y, 0]; + preproject = (xyz: NumberArray3) => { + const [x, y, z = 0] = viewport.projectFlat([xyz[0], xyz[1]]); + return [x, y, z]; }; } } diff --git a/modules/mesh-layers/src/simple-mesh-layer/simple-mesh-layer.ts b/modules/mesh-layers/src/simple-mesh-layer/simple-mesh-layer.ts index 9fc1b15ca13..ccf23a92aa9 100644 --- a/modules/mesh-layers/src/simple-mesh-layer/simple-mesh-layer.ts +++ b/modules/mesh-layers/src/simple-mesh-layer/simple-mesh-layer.ts @@ -30,6 +30,7 @@ import type { import type {MeshAttribute, MeshAttributes} from '@loaders.gl/schema'; import type {Geometry as GeometryType} from '@luma.gl/engine'; import {getMeshBoundingBox} from '@loaders.gl/schema'; +import {NumberArray2} from '@math.gl/core'; function normalizeGeometryAttributes(attributes: MeshAttributes): MeshAttributes { const positionAttribute = attributes.positions || attributes.POSITION; @@ -198,7 +199,7 @@ export default class SimpleMeshLayer e model?: Model; emptyTexture: Texture; hasNormals?: boolean; - positionBounds?: [number[], number[]] | null; + positionBounds?: [NumberArray2, NumberArray2] | null; }; getShaders() { @@ -209,7 +210,7 @@ export default class SimpleMeshLayer e }); } - getBounds(): [number[], number[]] | null { + getBounds(): [NumberArray2, NumberArray2] | null { if (this.props._instanced) { return super.getBounds(); } @@ -234,7 +235,7 @@ export default class SimpleMeshLayer e } this.state.positionBounds = result; - return result; + return result ?? null; } initializeState() { diff --git a/test/modules/geo-layers/tile-3d-layer/tile-3d-layer.spec.ts b/test/modules/geo-layers/tile-3d-layer/tile-3d-layer.spec.ts index 2d1f1c100b7..ad595c53ae3 100644 --- a/test/modules/geo-layers/tile-3d-layer/tile-3d-layer.spec.ts +++ b/test/modules/geo-layers/tile-3d-layer/tile-3d-layer.spec.ts @@ -4,13 +4,14 @@ import test from 'tape-promise/tape'; -import {testLayerAsync} from '@deck.gl/test-utils'; +import {LayerTestCase, testLayerAsync} from '@deck.gl/test-utils'; import {Tile3DLayer} from '@deck.gl/geo-layers'; import {WebMercatorViewport} from '@deck.gl/core'; test('Tile3DLayer', async t => { - const testCases = [ + const testCases: LayerTestCase[] = [ { + title: 'Tile3DLayer initial load', props: { data: './test/data/3d-tiles/tileset.json', getPointColor: [0, 0, 0] @@ -23,6 +24,7 @@ test('Tile3DLayer', async t => { } }, { + title: 'Tile3DLayer update opacity', updateProps: { opacity: 0.5 },