Skip to content

Commit

Permalink
fix: enforce 3D coordinates across dependent modules
Browse files Browse the repository at this point in the history
  • Loading branch information
charlieforward9 committed Jan 8, 2025
1 parent e306dfe commit eb96c86
Show file tree
Hide file tree
Showing 26 changed files with 179 additions and 129 deletions.
19 changes: 12 additions & 7 deletions modules/aggregation-layers/src/contour-layer/contour-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> 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]),
Expand Down Expand Up @@ -232,16 +232,20 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends

private _updateBinOptions() {
const bounds = this.getBounds();
const cellSizeCommon: [number, number] = [1, 1];
let cellOriginCommon: [number, number] = [0, 0];
const cellSizeCommon: [number, number, number] = [1, 1, 0];
let cellOriginCommon: [number, number, number] = [0, 0, 0];
let binIdRange: [number, number][] = [
[0, 1],
[0, 1]
];
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, number] = [
(bounds[0][0] + bounds[1][0]) / 2,
(bounds[0][1] + bounds[1][1]) / 2,
0
];
const {cellSize, gridOrigin} = this.props;
const {unitsPerMeter} = viewport.getDistanceScales(centroid);
cellSizeCommon[0] = unitsPerMeter[0] * cellSize;
Expand All @@ -254,7 +258,8 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends
Math.floor((centroidCommon[0] - gridOrigin[0]) / cellSizeCommon[0]) * cellSizeCommon[0] +
gridOrigin[0],
Math.floor((centroidCommon[1] - gridOrigin[1]) / cellSizeCommon[1]) * cellSizeCommon[1] +
gridOrigin[1]
gridOrigin[1],
0
];
centroid = viewport.unprojectFlat(cellOriginCommon);

Expand All @@ -267,12 +272,12 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends
: new Viewport({position: [centroid[0], centroid[1], 0], zoom: 12});

// Round to the nearest 32-bit float to match CPU and GPU results
cellOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1])];
cellOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1]), 0];

binIdRange = getBinIdRange({
dataBounds: bounds,
getBinId: (p: number[]) => {
const positionCommon = viewport.projectFlat(p);
const positionCommon = viewport.projectFlat([p[0], p[1], p[2] || 0]);
return [
Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]),
Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1])
Expand Down
23 changes: 16 additions & 7 deletions modules/aggregation-layers/src/grid-layer/grid-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> 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]),
Expand Down Expand Up @@ -437,16 +437,20 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends

private _updateBinOptions() {
const bounds = this.getBounds();
const cellSizeCommon: [number, number] = [1, 1];
let cellOriginCommon: [number, number] = [0, 0];
const cellSizeCommon: [number, number, number] = [1, 1, 0];
let cellOriginCommon: [number, number, number] = [0, 0, 0];
let binIdRange: [number, number][] = [
[0, 1],
[0, 1]
];
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, number] = [
(bounds[0][0] + bounds[1][0]) / 2,
(bounds[0][1] + bounds[1][1]) / 2,
0
];
const {cellSize} = this.props;
const {unitsPerMeter} = viewport.getDistanceScales(centroid);
cellSizeCommon[0] = unitsPerMeter[0] * cellSize;
Expand All @@ -457,7 +461,8 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends
const centroidCommon = viewport.projectFlat(centroid);
cellOriginCommon = [
Math.floor(centroidCommon[0] / cellSizeCommon[0]) * cellSizeCommon[0],
Math.floor(centroidCommon[1] / cellSizeCommon[1]) * cellSizeCommon[1]
Math.floor(centroidCommon[1] / cellSizeCommon[1]) * cellSizeCommon[1],
0
];
centroid = viewport.unprojectFlat(cellOriginCommon);

Expand All @@ -470,12 +475,16 @@ export default class GridLayer<DataT = any, ExtraPropsT extends {} = {}> extends
: new Viewport({position: [centroid[0], centroid[1], 0], zoom: 12});

// Round to the nearest 32-bit float to match CPU and GPU results
cellOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1])];
cellOriginCommon = [
Math.fround(viewport.center[0]),
Math.fround(viewport.center[1]),
Math.fround(viewport.center[2] || 0)
];

binIdRange = getBinIdRange({
dataBounds: bounds,
getBinId: (p: number[]) => {
const positionCommon = viewport.projectFlat(p);
const positionCommon = viewport.projectFlat([p[0], p[1], p[2] || 0]);
return [
Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]),
Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1])
Expand Down
17 changes: 10 additions & 7 deletions modules/aggregation-layers/src/heatmap-layer/heatmap-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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));
}
Expand Down
26 changes: 18 additions & 8 deletions modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]],
Expand Down Expand Up @@ -451,15 +451,21 @@ 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, number] = [
(bounds[0][0] + bounds[1][0]) / 2,
(bounds[0][1] + bounds[1][1]) / 2,
0
];
const {radius} = this.props;
const {unitsPerMeter} = viewport.getDistanceScales(centroid);
radiusCommon = unitsPerMeter[0] * radius;

// Use the centroid of the hex at the center of the data
// This offsets the common space without changing the bins
const centerHex = pointToHexbin(viewport.projectFlat(centroid), radiusCommon);
centroid = viewport.unprojectFlat(getHexbinCentroid(centerHex, radiusCommon));
const projection = viewport.projectFlat(centroid);
const centerHex = pointToHexbin([projection[0], projection[1]], radiusCommon);
const centerHexCommon = getHexbinCentroid(centerHex, radiusCommon);
centroid = viewport.unprojectFlat([centerHexCommon[0], centerHexCommon[1], 0]);

const ViewportType = viewport.constructor as any;
// We construct a viewport for the GPU aggregator's project module
Expand All @@ -474,10 +480,10 @@ export default class HexagonLayer<
binIdRange = getBinIdRange({
dataBounds: bounds,
getBinId: (p: number[]) => {
const positionCommon = viewport.projectFlat(p);
const positionCommon = viewport.projectFlat([p[0], p[1], p[2] || 0]);
positionCommon[0] -= hexOriginCommon[0];
positionCommon[1] -= hexOriginCommon[1];
return pointToHexbin(positionCommon, radiusCommon);
return pointToHexbin([positionCommon[0], positionCommon[1]], radiusCommon);
},
padding: 1
});
Expand Down Expand Up @@ -636,12 +642,16 @@ export default class HexagonLayer<
bin.id as [number, number],
this.state.radiusCommon
);
const centroid = this.context.viewport.unprojectFlat(centroidCommon);
const centroid = this.context.viewport.unprojectFlat([
centroidCommon[0],
centroidCommon[1],
0
]);

object = {
col: bin.id[0],
row: bin.id[1],
position: centroid,
position: [centroid[0], centroid[1]],
colorValue: bin.value[0],
elevationValue: bin.value[1],
count: bin.count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions modules/core/src/lib/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export default abstract class Layer<PropsT extends {} = {}> 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;

Expand All @@ -230,20 +230,20 @@ export default abstract class Layer<PropsT extends {} = {}> 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);
}

/** 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;
Expand Down
4 changes: 2 additions & 2 deletions modules/core/src/lib/picking/pick-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 6 additions & 6 deletions modules/core/src/lib/view-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ type ViewStateOf<ViewT> = ViewT extends View<infer ViewStateT> ? ViewStateT : ne
type OneOfViews<ViewsT extends ViewOrViews> = ViewsT extends null
? MapView
: ViewsT extends View[]
? ViewsT[number]
: ViewsT;
? ViewsT[number]
: ViewsT;
export type AnyViewStateOf<ViewsT extends ViewOrViews> = ViewStateOf<OneOfViews<ViewsT>>;
export type ViewStateMap<ViewsT extends ViewOrViews> = ViewsT extends null
? MapViewState
: ViewsT extends View
? ViewStateOf<ViewsT>
: {[viewId: string]: AnyViewStateOf<ViewsT>};
? ViewStateOf<ViewsT>
: {[viewId: string]: AnyViewStateOf<ViewsT>};

/** This is a very lose type of all "acceptable" viewState
* It's not good for type hinting but matches what may exist internally
Expand Down Expand Up @@ -195,13 +195,13 @@ export default class ViewManager<ViewsT extends View[]> {
* @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);
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/shaderlib/project/project-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions modules/core/src/transitions/linear-interpolator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -21,7 +21,7 @@ type PropsWithAnchor = {
*/
export default class LinearInterpolator extends TransitionInterpolator {
opts: {
around?: number[];
around?: [number, number, number];
makeViewport?: (props: Record<string, any>) => Viewport;
};

Expand All @@ -42,7 +42,7 @@ export default class LinearInterpolator extends TransitionInterpolator {
extract?: string[];
required?: string[];
};
around?: number[];
around?: [number, number, number];
makeViewport?: (props: Record<string, any>) => Viewport;
} = {}
) {
Expand Down
Loading

0 comments on commit eb96c86

Please sign in to comment.