1
- import type Point from '@mapbox/point-geometry' ;
1
+ import Point from '@mapbox/point-geometry' ;
2
2
import { type IReadonlyTransform , type ITransform } from '../transform_interface' ;
3
3
import { type LngLat , type LngLatLike } from '../lng_lat' ;
4
4
import { type CameraForBoundsOptions , type PointLike } from '../../ui/camera' ;
5
5
import { type PaddingOptions } from '../edge_insets' ;
6
6
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' ;
8
8
import { quat } from 'gl-matrix' ;
9
9
import { interpolates } from '@maplibre/maplibre-gl-style-spec' ;
10
+ import { projectToWorldCoordinates , unprojectFromWorldCoordinates } from './mercator_utils' ;
10
11
11
12
export type MapControlsDeltas = {
12
13
panDelta : Point ;
@@ -151,3 +152,70 @@ export function updateRotation(args: UpdateRotationArgs) {
151
152
args . tr . setBearing ( interpolates . number ( args . startEulerAngles . bearing , args . endEulerAngles . bearing , args . k ) ) ;
152
153
}
153
154
}
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