Joey Yi Zhao
Joey Yi Zhao

Reputation: 42474

How to zoom in/out in react-native-map?

I am using react-native to build a map application. The api I am using is from this link: https://github.com/lelandrichardson/react-native-maps.

Below is the code I bring the map on my app. I wonder how I can give a zoom value on that map. And how I can change the zoom value when the user clicks a button on the map.

What is the zoom API I should use to achieve this?

import React, { Component, StyleSheet, View, TextInput } from "react-native";
import MapView from "react-native-maps";

class MapPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      region: {
        latitude: 4.21048,
        longitude: 101.97577,
        latitudeDelta: 10,
        longitudeDelta: 5,
      },
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <TextInput style={styles.inputText}>Map</TextInput>
        <MapView
          style={styles.map}
          mapType={"standard"}
          region={this.state.region}
          zoomEnabled={true}
          scrollEnabled={true}
          showsScale={true}
        />
      </View>
    );
  }
}

module.exports = MapPage;

const styles = StyleSheet.create({
  map: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    padding: 30,
    flex: 1,
    alignItems: "center",
  },
  inputText: {
    height: 36,
    padding: 4,
    marginRight: 5,
    flex: 4,
    fontSize: 18,
    borderWidth: 1,
    borderColor: "#48BBEC",
    borderRadius: 8,
    color: "#48BBEC",
  },
});

Upvotes: 46

Views: 83457

Answers (7)

krlozadan
krlozadan

Reputation: 317

This is what I did and it worked out for me very well:

function getRegion(origin, destination, zoom) {
  const oLat = Math.abs(origin.latitude);
  const oLng = Math.abs(origin.longitude);
  const dLat = Math.abs(destination.latitude);
  const dLng = Math.abs(destination.longitude);

  return {
      latitude: (origin.latitude + destination.latitude) / 2,
      longitude: (origin.longitude + destination.longitude) / 2,
      latitudeDelta: Math.abs(oLat - dLat) + zoom,
      longitudeDelta: Math.abs(oLng - dLng) + zoom,
  };
}

Upvotes: 1

Matthew N.
Matthew N.

Reputation: 29

const handleZoomIn = () => {
  map.current?.getCamera().then((cam: Camera) => {
    if (Platform.OS === 'android') {
      cam.zoom += 1;
    } else {
      cam.altitude /= 2;
    }
    map.current?.animateCamera(cam);
  });
};

const handleZoomOut = () => {
  map.current?.getCamera().then((cam: Camera) => {
    if (Platform.OS === 'android') {
      cam.zoom -= 1;
    } else {
      cam.altitude *= 2;
    }
    map.current?.animateCamera(cam);
  });
};

Upvotes: 2

houcin olmostaf
houcin olmostaf

Reputation: 201

Example of Pinch to Zoom Image in React Native

// Import React in our code
import React from 'react';

// Import all the components we are going to use
import { SafeAreaView, StyleSheet, View } from 'react-native';

// Import ImageViewer which will help us to zoom Image
import ImageViewer from 'react-native-image-zoom-viewer';

const App = () => {
  const images = [
    {
      url:
        'https://raw.githubusercontent.com/AboutReact/sampleresource/master/sample_img.png',
    },
    {
      url:
        'https://raw.githubusercontent.com/AboutReact/sampleresource/master/old_logo.png',
    },
  ];

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={styles.container}>
        <ImageViewer imageUrls={images} renderIndicator={() => null} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#F5FCFF',
    flex: 1,
  },
});

export default App;

Upvotes: -1

Cristian Canales
Cristian Canales

Reputation: 47

I was able to make this work using Dimensions.get('window');

    const window = Dimensions.get('window');
    const { width, height }  = window
    LONGITUDE_DELTA = LATITUD_DELTA + (width / height)

and by default set LATITUD_DELTA = 0.0922. Then just update this values with the prop onRegionChangeComplete in the <MapView>

Upvotes: 3

Tomislav Brabec
Tomislav Brabec

Reputation: 559

New React Native Maps API gives you option to call animateCamera method with zoom parameter.

    const MapComponent= (props: any) => {
    
    const map: LegacyRef<MapView> = useRef(null);
    
    const onZoomInPress = () => {
        map?.current?.getCamera().then((cam: Camera) => {
            cam.zoom += 1;
            map?.current?.animateCamera(cam);
        });
    };
    
    return (
            <View>
                <MapView
                    ref={map}
                    provider={PROVIDER_GOOGLE}
                    region={region}>
                </MapView>
                <ButtonComponent
                    style={{position: 'absolute', bottom: 400, left: 0}}
                    onPress={onZoomInPress}>
                    Zoom In
                </MainButtonBlue>
            </View>
        );
    }

Upvotes: 4

findhumane
findhumane

Reputation: 11

I created the following based on the mercator math in https://github.com/tuupola/php_google_maps

The key function is mercatorDegreeDeltas(latitude, longitude, width, height, zoom) which returns { latitudeDelta, longitudeDelta } for the specified latitude/longitude center point, map dimensions, and zoom level (1-20).

import React from 'react';
import { useWindowDimensions } from 'react-native';
import MapView from 'react-native-maps';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useHeaderHeight } from '@react-navigation/elements';

const MERCATOR_OFFSET = Math.pow(2, 28);
const MERCATOR_RADIUS = MERCATOR_OFFSET / Math.PI;

function mercatorLatitudeToY(latitude) {
  return Math.round(
    MERCATOR_OFFSET - 
    (
      (
        MERCATOR_RADIUS *
          Math.log(
            (1 + Math.sin(latitude * (Math.PI / 180))) /
            (1 - Math.sin(latitude * (Math.PI / 180)))
          )
      ) / 2
    )
  );
}

function mercatorLongitudeToX(longitude) {
  return Math.round(
    MERCATOR_OFFSET +
      (
        (
          (MERCATOR_RADIUS * longitude) * Math.PI
        ) / 180
      )
  );
}

function mercatorXToLongitude(x) {
  return (
    (
      (x - MERCATOR_OFFSET) / MERCATOR_RADIUS
    ) * 180
  ) / Math.PI;
}

function mercatorYToLatitude(y) {
  return (
    (
      (
        Math.PI / 2
      ) -
        (2 * Math.atan(
              Math.exp(
                (
                  y - MERCATOR_OFFSET
                ) / MERCATOR_RADIUS
              )
            )
        )
    ) * 180
  ) / Math.PI;
}

function mercatorAdjustLatitudeByOffsetAndZoom(latitude, offset, zoom) {
  return mercatorYToLatitude(mercatorLatitudeToY(latitude) + (offset << (21 - zoom)));
}

function mercatorAdjustLongitudeByOffsetAndZoom(longitude, offset, zoom) {
  return mercatorXToLongitude(mercatorLongitudeToX(longitude) + (offset << (21 - zoom)));
}

function mercatorDegreeDeltas(latitude, longitude, width, height, zoom) {

  if (!zoom) {
    zoom = 20;
  }

  const deltaX = width / 2;
  const deltaY = height / 2;

  const northLatitude = mercatorAdjustLatitudeByOffsetAndZoom(latitude, deltaY * -1, zoom);
  const westLongitude = mercatorAdjustLongitudeByOffsetAndZoom(longitude, deltaX * -1, zoom);
  const southLatitude = mercatorAdjustLatitudeByOffsetAndZoom(latitude, deltaY, zoom);
  const eastLongitude = mercatorAdjustLongitudeByOffsetAndZoom(longitude, deltaY, zoom);

  const latitudeDelta = Math.abs(northLatitude - southLatitude);
  const longitudeDelta = Math.abs(eastLongitude - westLongitude);

  return { latitudeDelta, longitudeDelta };
}

// Somewhat arbitrarily, Riverside Park, Independence, KS 67301
const CENTER_UNITED_STATES = {
  latitude: 37.24435373025407,
  longitude: -95.70234410503208,
};

export default function MapViewWrapper() {
  const { width, height } = useWindowDimensions();
  const tabBarHeight = useBottomTabBarHeight();
  const headerHeight = useHeaderHeight();
  const initialRegion = React.useRef(null);
  const availableHeight = height - tabBarHeight - headerHeight;

  // Only calculate initial region once
  if (!initialRegion.current) {
    const { latitudeDelta, longitudeDelta } = mercatorDegreeDeltas(
      CENTER_UNITED_STATES.latitude,
      CENTER_UNITED_STATES.longitude,
      width,
      availableHeight,
      4,
    );
    
    initialRegion.current = {
      latitude: CENTER_UNITED_STATES.latitude,
      longitude: CENTER_UNITED_STATES.longitude,
      latitudeDelta: latitudeDelta,
      longitudeDelta: longitudeDelta,
    };
  }

  return (
    <MapView
      initialRegion={initialRegion.current}
      style={{ width: width, height: availableHeight }}
    />
  );
}

There is at least one issue: if you change the zoom from 4 to 3, it isn't centered properly, but larger zoom values work. I don't need the lower zoom values right now, so I haven't investigated the math any further (maybe some sort of overflow?).

Upvotes: 0

David
David

Reputation: 2821

You should use the animateToRegion method (see here)

It takes a region object which has latitudeDelta and longitudeDelta. Use these to set the zoom level.

Updated:

in a Region object the latitude and longitude specify the center location and latitudeDelta and longitudeDelta specify the span of the viewable map area.

This image from this blog post illustrates it well (LatΔ and LngΔ). enter image description here

Upvotes: 128

Related Questions