Skip to content

Commit 6275dd2

Browse files
authored
Refactor camera helper to split the logic for vertical-perspective and mercator (#5162)
* Refactor camera helper * Use the helper in the factory. * Fix build test * Fix according to code review
1 parent dda90d8 commit 6275dd2

6 files changed

+549
-454
lines changed

src/geo/projection/camera_helper.ts

+70-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import type Point from '@mapbox/point-geometry';
1+
import Point from '@mapbox/point-geometry';
22
import {type IReadonlyTransform, type ITransform} from '../transform_interface';
33
import {type LngLat, type LngLatLike} from '../lng_lat';
44
import {type CameraForBoundsOptions, type PointLike} from '../../ui/camera';
55
import {type PaddingOptions} from '../edge_insets';
66
import {type LngLatBounds} from '../lng_lat_bounds';
7-
import {getRollPitchBearing, type RollPitchBearing, rollPitchBearingToQuat, warnOnce} from '../../util/util';
7+
import {degreesToRadians, getRollPitchBearing, type RollPitchBearing, rollPitchBearingToQuat, scaleZoom, warnOnce, zoomScale} from '../../util/util';
88
import {quat} from 'gl-matrix';
99
import {interpolates} from '@maplibre/maplibre-gl-style-spec';
10+
import {projectToWorldCoordinates, unprojectFromWorldCoordinates} from './mercator_utils';
1011

1112
export type MapControlsDeltas = {
1213
panDelta: Point;
@@ -151,3 +152,70 @@ export function updateRotation(args: UpdateRotationArgs) {
151152
args.tr.setBearing(interpolates.number(args.startEulerAngles.bearing, args.endEulerAngles.bearing, args.k));
152153
}
153154
}
155+
156+
export function cameraForBoxAndBearing(options: CameraForBoundsOptions, padding: PaddingOptions, bounds: LngLatBounds, bearing: number, tr: IReadonlyTransform): CameraForBoxAndBearingHandlerResult {
157+
const edgePadding = tr.padding;
158+
159+
// Consider all corners of the rotated bounding box derived from the given points
160+
// when find the camera position that fits the given points.
161+
162+
const nwWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthWest());
163+
const neWorld = projectToWorldCoordinates(tr.worldSize, bounds.getNorthEast());
164+
const seWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthEast());
165+
const swWorld = projectToWorldCoordinates(tr.worldSize, bounds.getSouthWest());
166+
167+
const bearingRadians = degreesToRadians(-bearing);
168+
169+
const nwRotatedWorld = nwWorld.rotate(bearingRadians);
170+
const neRotatedWorld = neWorld.rotate(bearingRadians);
171+
const seRotatedWorld = seWorld.rotate(bearingRadians);
172+
const swRotatedWorld = swWorld.rotate(bearingRadians);
173+
174+
const upperRight = new Point(
175+
Math.max(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x),
176+
Math.max(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y)
177+
);
178+
179+
const lowerLeft = new Point(
180+
Math.min(nwRotatedWorld.x, neRotatedWorld.x, swRotatedWorld.x, seRotatedWorld.x),
181+
Math.min(nwRotatedWorld.y, neRotatedWorld.y, swRotatedWorld.y, seRotatedWorld.y)
182+
);
183+
184+
// Calculate zoom: consider the original bbox and padding.
185+
const size = upperRight.sub(lowerLeft);
186+
187+
const availableWidth = (tr.width - (edgePadding.left + edgePadding.right + padding.left + padding.right));
188+
const availableHeight = (tr.height - (edgePadding.top + edgePadding.bottom + padding.top + padding.bottom));
189+
const scaleX = availableWidth / size.x;
190+
const scaleY = availableHeight / size.y;
191+
192+
if (scaleY < 0 || scaleX < 0) {
193+
cameraBoundsWarning();
194+
return undefined;
195+
}
196+
197+
const zoom = Math.min(scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom);
198+
199+
// Calculate center: apply the zoom, the configured offset, as well as offset that exists as a result of padding.
200+
const offset = Point.convert(options.offset);
201+
const paddingOffsetX = (padding.left - padding.right) / 2;
202+
const paddingOffsetY = (padding.top - padding.bottom) / 2;
203+
const paddingOffset = new Point(paddingOffsetX, paddingOffsetY);
204+
const rotatedPaddingOffset = paddingOffset.rotate(degreesToRadians(bearing));
205+
const offsetAtInitialZoom = offset.add(rotatedPaddingOffset);
206+
const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / zoomScale(zoom));
207+
208+
const center = unprojectFromWorldCoordinates(
209+
tr.worldSize,
210+
// either world diagonal can be used (NW-SE or NE-SW)
211+
nwWorld.add(seWorld).div(2).sub(offsetAtFinalZoom)
212+
);
213+
214+
const result = {
215+
center,
216+
zoom,
217+
bearing
218+
};
219+
220+
return result;
221+
}

0 commit comments

Comments
 (0)