Skip to content

[Bug]: "setCamera" with bounds option doesn't fit these bounds (Android) #4050

@Crysp

Description

@Crysp

Mapbox Implementation

Mapbox

Mapbox Version

11.15.3

React Native Version

0.82.0

React Native Architecture

New Architecture (Fabric/TurboModules)

Platform

Android

@rnmapbox/maps version

10.2.4

Standalone component to reproduce

import React, { useCallback, useRef, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { MapView, Camera, MarkerView } from '@rnmapbox/maps';

const point1 = [-122.1, 37];
const point2 = [-121.2, 37];

const BugReportExample = () => {
  const camera = useRef(null);
  const [mode, setMode] = useState(0);
  const setCamera = useCallback(() => {
    switch (mode) {
      case 0:
        camera.current?.setCamera({
          bounds: {
            ne: point1,
            sw: point2,
          },
        });
        setMode(1);
        break;
      case 1:
        camera.current?.setCamera({
          centerCoordinate: point1,
          zoomLevel: 14,
        });
        setMode(2);
        break;
      case 2:
        camera.current?.setCamera({
          centerCoordinate: point2,
          zoomLevel: 14,
        });
        setMode(0);
        break;
    }
  }, [mode]);

  return (
    <View style={styles.container}>
      <MapView style={styles.map}>
        <Camera
          ref={camera}
          defaultSettings={{ centerCoordinate: point1, zoomLevel: 14 }}
        />
        <MarkerView coordinate={point1} allowOverlap>
          <View style={[styles.marker, styles.red]} />
        </MarkerView>
        <MarkerView coordinate={point2} allowOverlap>
          <View style={[styles.marker, styles.orange]} />
        </MarkerView>
      </MapView>
      <Pressable style={styles.button} onPress={setCamera}>
        <Text style={styles.buttonText}>Switch Camera</Text>
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    width: '100%',
    height: '100%',
  },
  map: {
    flex: 1,
  },
  marker: {
    width: 20,
    height: 20,
    borderRadius: 5,
  },
  red: {
    backgroundColor: 'red',
  },
  orange: {
    backgroundColor: 'orange',
  },
  button: {
    position: 'absolute',
    top: 40,
    right: 20,
    paddingVertical: 4,
    paddingHorizontal: 12,
    borderRadius: 8,
    backgroundColor: '#FFF',
  },
  buttonText: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#000',
  },
});

Observed behavior and steps to reproduce

Switch camera by tapping "Switch Camera"

Screen_recording_20251017_142049.mp4

Expected behavior

Red and orange points should fits screen bounds

Notes / preliminary analysis

If you change coordinates to these than camera would be in correct position

const point1 = [-121.1, 37];
const point2 = [-121.1, 37.1];
Screen_recording_20251017_143940.mp4

There are another issues with "setCamera" with bounds option and paddingBottom have been set to half of the screen or more. But I think the core issue may be the same

Also I have researched native code and it seems that Mapbox SDK 11.15.3 is broken.

Passed correct data to camera options calculations

Image

The center was calculated correctly, but zoom is 0

Image

Issues with padding

The camera is not centered in the acceptable area.

Screen_recording_20251020_012645.mp4

Standalone component to reproduce

import React, { useCallback, useRef, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { MapView, Camera, MarkerView } from '@rnmapbox/maps';

const center = [-121.1, 37];
const northPoint = [-121.1, 37.1];
const northWestPoint = [-121.2, 37.1];
const westPoint = [-121.2, 37];

const paddingTop = 40;
const paddingBottom = 300;
const paddingLeft = 40;
const paddingRight = 40;

const BugReportExample = () => {
  const camera = useRef(null);
  const [mode, setMode] = useState(0);
  const setCamera = useCallback(() => {
    switch (mode) {
      case 0:
        camera.current?.setCamera({
          bounds: {
            ne: center,
            sw: northPoint,
          },
          animationDuration: 300,
          animationMode: 'linearTo',
        });
        setMode(1);
        break;
      case 1:
        camera.current?.setCamera({
          bounds: {
            ne: center,
            sw: northWestPoint,
          },
          animationDuration: 300,
          animationMode: 'linearTo',
        });
        setMode(2);
        break;
      case 2:
        camera.current?.setCamera({
          bounds: {
            ne: center,
            sw: westPoint,
          },
          animationDuration: 300,
          animationMode: 'linearTo',
        });
        setMode(3);
        break;
      case 3:
        camera.current?.setCamera({
          centerCoordinate: center,
          zoomLevel: 10,
          animationDuration: 300,
          animationMode: 'linearTo',
        });
        setMode(0);
        break;
    }
  }, [mode]);

  return (
    <View style={styles.container}>
      <MapView style={styles.map}>
        <Camera
          ref={camera}
          defaultSettings={{
            centerCoordinate: center,
            zoomLevel: 10,
          }}
          padding={{
            paddingTop,
            paddingBottom,
            paddingLeft,
            paddingRight,
          }}
        />
        <MarkerView coordinate={center} allowOverlap>
          <View style={[styles.marker, styles.red]} />
        </MarkerView>
        <MarkerView coordinate={northPoint} allowOverlap>
          <View style={[styles.marker, styles.orange]} />
        </MarkerView>
        <MarkerView coordinate={northWestPoint} allowOverlap>
          <View style={[styles.marker, styles.orange]} />
        </MarkerView>
        <MarkerView coordinate={westPoint} allowOverlap>
          <View style={[styles.marker, styles.orange]} />
        </MarkerView>
      </MapView>
      <View
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          right: 0,
          height: paddingTop,
          backgroundColor: 'rgba(255, 0, 0, 0.1)',
        }}
        pointerEvents='none'
      />
      <View
        style={{
          position: 'absolute',
          top: paddingTop,
          left: 0,
          bottom: paddingBottom,
          width: paddingLeft,
          backgroundColor: 'rgba(255, 0, 0, 0.1)',
        }}
        pointerEvents='none'
      />
      <View
        style={{
          position: 'absolute',
          top: paddingTop,
          right: 0,
          bottom: paddingBottom,
          width: paddingRight,
          backgroundColor: 'rgba(255, 0, 0, 0.1)',
        }}
        pointerEvents='none'
      />
      <View
        style={{
          position: 'absolute',
          bottom: 0,
          left: 0,
          right: 0,
          height: paddingBottom,
          backgroundColor: 'rgba(255, 0, 0, 0.1)',
        }}
        pointerEvents='none'
      />
      <View
        style={{
          position: 'absolute',
          left: 0,
          right: 0,
          bottom: 60,
          alignItems: 'center',
        }}
      >
        <Pressable style={styles.button} onPress={setCamera}>
          <Text style={styles.buttonText}>Switch Camera</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    width: '100%',
    height: '100%',
  },
  map: {
    flex: 1,
  },
  marker: {
    width: 20,
    height: 20,
    borderRadius: 5,
  },
  red: {
    backgroundColor: 'red',
  },
  orange: {
    backgroundColor: 'orange',
  },
  button: {
    paddingVertical: 4,
    paddingHorizontal: 12,
    borderRadius: 8,
    backgroundColor: '#FFF',
  },
  buttonText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#000',
  },
});

Additional links and references

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🪲Something isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions