maps
maps copied to clipboard
Zoom level not applied correctly when switching from setting camera with `centerCoordinate` to `bounds`
Mapbox Implementation
Mapbox
Mapbox Version
11.3.0
React Native Version
0.72.4
Platform
iOS, Android
@rnmapbox/maps version
10.1.19
Standalone component to reproduce
import React, { useRef } from "react";
import { Button, StyleSheet, TouchableOpacity, View, Text } from "react-native";
import Mapbox from "@rnmapbox/maps";
import { ShapeSource, CircleLayer, Camera } from "@rnmapbox/maps";
const App = () => {
const realMapRef = useRef<Mapbox.MapView>(null);
const realCameraRef = useRef<Mapbox.Camera>(null);
const currentZoom = 16;
const sheetHeight = 374;
const padding = {
paddingTop: 60,
paddingLeft: 24,
paddingRight: 24,
paddingBottom: sheetHeight ? sheetHeight - 30 : 30,
};
// this function is called in the parent component. Left for the reference
const zoomToBounds = (bounds) => {
realCameraRef.current.camera?.setCamera({
bounds: {
ne: [ 10.6701055, 59.9111846 ],
sw: [ 10.6270699, 59.9008117 ],
},
padding: padding,
heading: 0.0, // reset to north
animationMode: 'easeTo',
});
};
// this function is called in the parent component. Left for the reference
const zoomToPosition = (pos) => {
realCameraRef.current.setCamera({
centerCoordinate: [ 10.6701055, 59.9008117 ],
padding: padding,
zoomLevel: currentZoom < 17 ? 17 : undefined,
heading: 0.0,
animationMode: 'easeTo',
});
};
return (
<Mapbox.MapView
ref={realMapRef}
styleURL="mapbox://styles/123"
projection="globe"
rotateEnabled={true}
scaleBarEnabled={false}
logoEnabled={false}
>
<Camera ref={realCameraRef} animationDuration={0} />
{/*Rest of the code*/}
</Mapbox.MapView>
);
}
export default App;
Observed behavior and steps to reproduce
I see warning in Xcode when the bug happens: [Warning, maps-core]: {}[General]: Unable to calculate camera for given bounds/geometry, padding is greater than map's width or height.
We set the camera position and zoom level by using two methods, depends on what the user click.
If user click a marker in the map, we call zoomToPosition function where we set centerCoordinate with the marker position and the padding.
If user click on the fence we call zoomToBounds function where we use bounds param to set the camera in the center with the correct zoom.
The problem is that when we call zoomToPosition and the camera zoom in to the marker and later we call zoomToBounds the camera centers correctly but the zoom level is not applied to show all the markers within the bounds.
https://github.com/rnmapbox/maps/assets/13038459/ebb1df5a-201a-4501-b248-9e596c6d1563
const padding = {
paddingTop: topSafeArea + 10,
paddingLeft: 24,
paddingRight: 24,
paddingBottom: sheetHeight ? sheetHeight - 30 : 30,
};
const zoomToPosition = (pos) => {
realCameraRef.current.setCamera({
centerCoordinate: [ 10.6701055, 59.9008117 ],
padding: padding,
zoomLevel: currentZoom < 17 ? 17 : undefined,
heading: 0.0,
animationMode: 'easeTo',
});
};
const zoomToBounds = (bounds) => {
realCameraRef.current.camera?.setCamera({
bounds: {
ne: [ 10.6701055, 59.9111846 ],
sw: [ 10.6270699, 59.9008117 ],
},
padding: padding,
heading: 0.0, // reset to north
animationMode: 'easeTo',
});
};
Screen recording. Here we can see that clicking on the marker zoom in the camera, when we navigate back the zoomToBounds is called and it should zoom out the camera to show all the markers, but instead the zoom level don't change.
https://github.com/rnmapbox/maps/assets/13038459/bc2d5116-047d-49f7-b33b-d01ca54c2ae0
Expected behavior
Zoom level should be applied correctly to show all the markers within the bounds when calling first
camera?.setCamera({
centerCoordinate: pos,
padding: padding,
zoomLevel: currentZoom < 17 ? 17 : undefined,
heading: 0.0,
animationMode: 'easeTo',
});
and calling after that setCamera with bounds
realCameraRef.current.camera?.setCamera({
bounds: {
ne: [ 10.6701055, 59.9111846 ],
sw: [ 10.6270699, 59.9008117 ],
},
padding: padding,
heading: 0.0, // reset to north
animationMode: 'easeTo',
});
};
Zoom level is not applied correctly when we use
Notes / preliminary analysis
We render the bottom sheet on the map. We use the bottom sheet height to calculate bottom padding, to make sure we center the camera in the visible part of the map.
It appears that this zoom issue is gone when we set the padding to the static value example: 30, but this will not center the camera in the visible part of the map.
Additional links and references
No response
Related issue #3354
In RNMBXCamera.siwft, If I reset the camera state before applying new bounds the padding error goes away and the zoom is applied as expected.
Screen recording showing expected behavior: https://github.com/rnmapbox/maps/assets/13038459/47d7ce59-bc9c-430e-80d5-8e81f6e9ce22
Example code that fix the bug. I am not sure if this is the right way of doing it.
func resetCameraState(map: MapView) {
// Reset camera options to default values
let defaultCamera = CameraOptions(
center: nil,
padding: .zero,
anchor: nil,
zoom: nil,
bearing: nil,
pitch: nil
)
// Apply the default camera options to reset the camera state
map.mapboxMap.setCamera(to: defaultCamera)
}
private func toUpdateItem(stop: [String: Any]) -> CameraUpdateItem? {
if stop.isEmpty {
return nil
}
var zoom: CGFloat?
if let z = stop["zoom"] as? Double {
zoom = CGFloat(z)
}
var pitch: CGFloat?
if let p = stop["pitch"] as? Double {
pitch = CGFloat(p)
}
var heading: CLLocationDirection?
if let h = stop["heading"] as? Double {
heading = CLLocationDirection(h)
}
let padding: UIEdgeInsets = UIEdgeInsets(
top: stop["paddingTop"] as? Double ?? 0,
left: stop["paddingLeft"] as? Double ?? 0,
bottom: stop["paddingBottom"] as? Double ?? 0,
right: stop["paddingRight"] as? Double ?? 0
)
var camera: CameraOptions?
if let feature = stop["centerCoordinate"] as? String {
let centerFeature: Turf.Feature? = logged("RNMBXCamera.toUpdateItem.decode.cc") {
try JSONDecoder().decode(Turf.Feature.self, from: feature.data(using: .utf8)!)
}
var center: LocationCoordinate2D?
switch centerFeature?.geometry {
case .point(let centerPoint):
center = centerPoint.coordinates
default:
Logger.log(level: .error, message: "RNMBXCamera.toUpdateItem: Unexpected geometry: \(String(describing: centerFeature?.geometry))")
return nil
}
camera = CameraOptions(
center: center,
padding: padding,
anchor: nil,
zoom: zoom,
bearing: heading,
pitch: pitch
)
} else if let feature = stop["bounds"] as? String {
let collection: Turf.FeatureCollection? = logged("RNMBXCamera.toUpdateItem.decode.bound") {
try JSONDecoder().decode(Turf.FeatureCollection.self, from: feature.data(using: .utf8)!)
}
let features = collection?.features
let ne: CLLocationCoordinate2D
switch features?.first?.geometry {
case .point(let point):
ne = point.coordinates
default:
Logger.log(level: .error, message: "RNMBXCamera.toUpdateItem: Unexpected geometry: \(String(describing: features?.first?.geometry))")
return nil
}
let sw: CLLocationCoordinate2D
switch features?.last?.geometry {
case .point(let point):
sw = point.coordinates
default:
Logger.log(level: .error, message: "RNMBXCamera.toUpdateItem: Unexpected geometry: \(String(describing: features?.last?.geometry))")
return nil
}
withMapView { map in
// Reset the camera state before applying new bounds
self.resetCameraState(map: map)
#if RNMBX_11
let bounds = [sw, ne]
#else
let bounds = CoordinateBounds(southwest: sw, northeast: ne)
#endif
camera = map.mapboxMap.camera(
for: bounds,
padding: padding,
bearing: heading ?? map.mapboxMap.cameraState.bearing,
pitch: pitch ?? map.mapboxMap.cameraState.pitch
)
}
} else {
withMapView { map in
// Reset the camera state before applying new center coordinate
self.resetCameraState(map: map)
camera = CameraOptions(
center: nil,
padding: padding,
anchor: nil,
zoom: zoom,
bearing: heading,
pitch: pitch
)
}
}
guard let camera = camera else {
return nil
}
var duration: TimeInterval?
if let d = stop["duration"] as? Double {
duration = toSeconds(d)
}
var mode: CameraMode = .flight
if let m = stop["mode"] as? String, let m = CameraMode(rawValue: m) {
mode = m
}
return CameraUpdateItem(
camera: camera,
mode: mode,
duration: duration
)
}
@mfazekas is it relates to https://github.com/mapbox/mapbox-maps-ios/issues/2170 ? Can you suggest a hot fix? Thanks @RyszardRzepa for a solution, but camera jumps unpleasantly
@RyszardRzepa fyi after some research I found that replacing deprecated method to the new API is fixed this bug
diff --git a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
index 261245c..d70b480 100644
--- a/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
+++ b/node_modules/@rnmapbox/maps/ios/RNMBX/RNMBXCamera.swift
@@ -452,12 +452,21 @@ open class RNMBXCamera : RNMBXMapComponentBase {
let bounds = CoordinateBounds(southwest: sw, northeast: ne)
#endif
- camera = map.mapboxMap.camera(
- for: bounds,
- padding: padding,
- bearing: heading ?? map.mapboxMap.cameraState.bearing,
- pitch: pitch ?? map.mapboxMap.cameraState.pitch
- )
+ do {
+ camera = try map.mapboxMap.camera(
+ for: bounds,
+ camera: CameraOptions(
+ padding: padding,
+ bearing: heading ?? map.mapboxMap.cameraState.bearing,
+ pitch: pitch ?? map.mapboxMap.cameraState.pitch
+ ),
+ coordinatesPadding: nil,
+ maxZoom: nil,
+ offset: nil
+ )
+ } catch let error {
+ Logger.log(level: .error, message: "RNMBXCamera.toUpdateItem: MapError: \(error.localizedDescription)")
+ }
}
} else {
camera = CameraOptions(
Any news here?
Thank you!
Same issue