Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get visible map bounding box #2571

Merged
merged 11 commits into from
Nov 22, 2018
Merged

Get visible map bounding box #2571

merged 11 commits into from
Nov 22, 2018

Conversation

radeno
Copy link
Contributor

@radeno radeno commented Nov 3, 2018

It is still in progress. Do not merge yet. I can't build code on Android platform.

Does any other open PR do the same thing?

No

What issue is this PR fixing?

#356

How did you test this PR?

Examples:

iOS: Apple Maps iOS: Google Maps Android: Google Maps
simulator screen shot - iphone 6 - 2018-11-03 at 18 30 13 simulator screen shot - iphone 6 - 2018-11-03 at 18 30 49 screenshot_1541585289

@radeno radeno changed the title [WIP] Add getting visible map bounding box [WIP] Get visible map bounding box Nov 3, 2018
@radeno radeno changed the title [WIP] Get visible map bounding box WIP: Get visible map bounding box Nov 3, 2018
@radeno
Copy link
Contributor Author

radeno commented Nov 5, 2018

Guys, if you someone can guide me how to make Android version functional i will be helpful.
There is problem that all commands are called through wrapper function. But that function is defined as void function. So there should not be any return value.
Any idea how to make it work without high code pollution?

@rborn
Copy link
Collaborator

rborn commented Nov 5, 2018

@alvelig any idea? 🤗

@radeno
Copy link
Contributor Author

radeno commented Nov 7, 2018

@rborn @alvelig got it. It works as you can see in first comment.
Would be nice to get some code review response.

@Peretz30
Copy link
Contributor

@radeno man, awesome job! I had tested your fork, works brilliant!

@rborn
Copy link
Collaborator

rborn commented Nov 14, 2018

@Peretz30 tested both ios and android? ❤️

@radeno radeno changed the title WIP: Get visible map bounding box Get visible map bounding box Nov 14, 2018
@Peretz30
Copy link
Contributor

@rborn tested on Android device. iOS not tested.

@radeno
Copy link
Contributor Author

radeno commented Nov 20, 2018

Merged with upstream. Hope will be merged. Want to add getZoom (need to investigate how to do that properly) or something like that. Than it will be easier to get visible all markers and so on.

@rborn
Copy link
Collaborator

rborn commented Nov 20, 2018

@alvelig 🐽

Copy link
Collaborator

@rborn rborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM , one question though: why does it have to be a promise? (I really don't know so I want to understand 😊)

@radeno
Copy link
Contributor Author

radeno commented Nov 20, 2018

@rborn if i understand it correct, then there are two solutions how to return a value:

  1. callbacks https://facebook.github.io/react-native/docs/native-modules-ios#callbacks or
  2. promises https://facebook.github.io/react-native/docs/native-modules-ios#promises
    Edit: for Android: https://facebook.github.io/react-native/docs/native-modules-android#promises and https://facebook.github.io/react-native/docs/native-modules-android#promises

Callbacks are "hell", anyways is tagged as experimental.

So returned promise is awaited in javascript async function. It is transparent on JS side.

@miracle2k
Copy link
Contributor

On Google Maps, more values are available: https://developers.google.com/android/reference/com/google/android/gms/maps/model/VisibleRegion - the farLeft, farRight etc. properties. Are these different values than are contained in the latLngBoundsobject? I can't quite tell if there is different information in those, or not.

If so, is it worth it to return them as well?

I am a little bit obsessive about exposing as much of the native map helpers as possible, and wouldn't like to miss out on available data which we later cannot include for backwards-compatibility reasons, thus requiring us to add yet another helper.

@radeno
Copy link
Contributor Author

radeno commented Nov 21, 2018

@miracle2k if have MapKit same functionality then no problem to return it. But if it's only for Google Maps then it is not multiplatform.

@miracle2k
Copy link
Contributor

@radeno MapKit does not expose those fields directly, but it might be possible to calculate them ourselves. It might also be ok to only return those fields on Android.

Still, I am not even sure if those fields contain any new information; the whole thing might be nothing. Can anyone currently running this PR check?

@radeno
Copy link
Contributor Author

radeno commented Nov 21, 2018

@miracle2k ok, let me check what google maps returns. But as naming says i except it should be for camera view, when map is skewed (not 90° angle).

@Peretz30
Copy link
Contributor

@radeno can you say, please, what is the difference between your solution and this?

const getBoundingBox = (region) => ([
  region.longitude - region.longitudeDelta/2, // westLng - min lng
  region.latitude - region.latitudeDelta/2, // southLat - min lat
  region.longitude + region.longitudeDelta/2, // eastLng - max lng
  region.latitude + region.latitudeDelta/2 // northLat - max lat
])

@radeno
Copy link
Contributor Author

radeno commented Nov 22, 2018

@Peretz30
looks like not much different
Top is native calculated, bottom by region. So depends on Zoom, and just latitude coordinate is different. Third decimal place is 110m and Fifth just 1.1m. I don't make any deep investigation how internal SDK calculates it neither performance difference.

1 2
simulator screen shot - iphone 6 - 2018-11-22 at 09 42 29 simulator screen shot - iphone 6 - 2018-11-22 at 09 44 40

@radeno
Copy link
Contributor Author

radeno commented Nov 22, 2018

@Peretz30 @rborn
It's look like computing bounding box from region is much cheaper and performant than native. Because everytime is regionChanged https://github.com/react-community/react-native-maps/blob/c7a55f5e2b3b7854e248d35baf8dbd716a95350c/lib/ios/AirMaps/AIRMapManager.m#L837 then callback Region contains precalculated delta and then sum-up 3 numbers on JS side with simple operators is ultra-cheap. Almost 0 miliseconds.

On the other hand native calculations through my PR should takes in average 250ms on MapKit and average 150ms on Google Maps. But should be more precise.

I think we should also implement helper method which takes region as argument and returns bounding box. Something like coordinateForPoint method. But without promise. Because coordinateForPoint calls native method.
Eg:

  /**
   * Get bounding box from region
   *
   * @param region Region
   *
   * @return Object Object bounding box ({ northEast: <LatLng>, southWest: <LatLng> })
   */
  mapBoundariesForRegion(region) {
    return {
      northEast: {
        latitude: region.latitude + region.latitudeDelta / 2,
        longitude: region.longitude + region.longitudeDelta / 2,
      },
      southWest: {
        latitude: region.latitude - region.latitudeDelta / 2,
        longitude: region.longitude - region.longitudeDelta / 2,
      },
    };
  }

What do you think about it?

@Peretz30
Copy link
Contributor

@radeno I agree, but I think name of the function should be getMapBoundaries, because MapView has setMapBoundaries

@miracle2k
Copy link
Contributor

Note there is currently no way to get the region from the map, except via the event. I do think an imperative way to access the region, getRegion() or getMapBoundaries() as in this PR is useful.

@rborn
Copy link
Collaborator

rborn commented Nov 22, 2018

@miracle2k you DO think or DON'T ?

@miracle2k
Copy link
Contributor

Right now, if I ever want to use the region in calculations, I have to setup a handler for onRegionChangeComplete or onRegionChange, then copy the region to local state (component state, or an instance member).

I think in many cases, where I only need to know the region in rare cases, for example when a user presses a "find button", I would think I would opt for calling this.refs.map.getMapBoundaries() at the time when I need that data, instead.

@rborn
Copy link
Collaborator

rborn commented Nov 22, 2018

@miracle2k ok
@radeno I think @Peretz30 's suggestion for the name is valid, maybe you agree?

@radeno
Copy link
Contributor Author

radeno commented Nov 22, 2018

@miracle2k you are right about getting bounding box without region change.
@rborn @Peretz30 hard to say what name should be better. Based on documentation and implementation https://github.com/react-community/react-native-maps/blob/e0c61eddebb2bfde5058338ce15e04148d812809/lib/components/MapView.js#L603 setMapBoudaries accepts two arguments and passing to native side https://github.com/react-community/react-native-maps/blob/1aee143302129f3811f1753155600bd041b5214d/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m#L394
On the other hand you want to pass region to method getMapBoundaries, which i think is very confusion. Why region? Or why pass any argument? Region is something "low level" how native maps works. As @miracle2k says, we don't have region in every event. So when we are forcing to pass region everytime, then it will have very limited usage.

I think adding helper method like mapBoundariesForRegion is very self-descriptive. When i pass region i get map boundaries. Same idea as pointForCoordinate or coordinateForPoint But without promise. No need to be async function.

@Peretz30
Copy link
Contributor

@radeno , thanks for explanation. Now I see, your logic seems to be right and your function name suits better.

@rborn rborn merged commit df9f735 into react-native-maps:master Nov 22, 2018
@radeno radeno deleted the master branch December 6, 2018 10:56
ryanbourneuk pushed a commit to 3sidedcube/react-native-maps that referenced this pull request Dec 10, 2018
* upstream/master: (28 commits)
  Calculate bounding box from region (react-native-maps#2615)
  [iOS GoogleMap] Fix animateCamera (react-native-maps#2608)
  Fix type definition error (react-native-maps#2607)
  [Android] Fix app crash in Android if building found but cannot getActiveLevelIndex (react-native-maps#2598)
  Provide a camera system (react-native-maps#2563)
  Get visible map bounding box (react-native-maps#2571)
  [0.22.1] Release (react-native-maps#2574)
  Move dev only deps to devDependencies. (react-native-maps#2548)
  Specify how to use Google Maps (react-native-maps#2550)
  r2507: remove marker: Attempt to invoke virtual method 'void com.google.android.gms.maps.model.setIcon(com.google.android.gms.maps.model.BitmapDescription)' on a null object reference #: remove marker: Attempt to invoke virtual method 'void com.google.android.gms.maps.model.setIcon(com.google.android.gms.maps.model.BitmapDescription)' on a null object reference (react-native-maps#2555)
  update to clarify cacheEnabled is apple maps only
  [0.22.0] Release (react-native-maps#2535)
  Fix for “The specified child already has a parent”
  Improve installation docs (react-native-maps#2541)
  fix fitToSuppliedMarkers function (react-native-maps#2524)
  Performance improvements for tracksViewChanges (react-native-maps#2487)
  fix spelling mistakes
  Added flag to make sure that there has an Observer of view.
  hotfix PR react-native-maps#2478
  Fix a peer dependencies warning
  ...
@sebackend
Copy link

Hi, thanks for the solution...Everything was working fine (i put some markers to test both coordinates, northEast & southWest), but when i rotated the map, the markers are showed in another position...How can this be fixed? there is no support for rotation?

@JonathanSainsburys
Copy link

Hello, im new to react-native, javascript as a whole infact. Can someone explain how to call this method. This is what i have:

    var obj = new MapView()
    obj.getMapBoundaries()
    .then((northEast, southWest) => {
      console.log(northEast)
    })
    .catch((error) => {
      console.log(error)
    })

I get this error back: [TypeError: undefined is not an object (evaluating 'this.props.provider')]

Please help @sebackend @radeno @rborn . Thanks

@rborn
Copy link
Collaborator

rborn commented May 1, 2020

What are you trying to do with var obj = new MapView() ?

@JonathanSainsburys
Copy link

JonathanSainsburys commented May 2, 2020

@rborn

Use Case
I want the user to scroll the map to a desired location and then tap the searchHere button. Which would return the map boundaries of where the user has scrolled to.

Approach
im trying to create an instance of MapView() and the call the method getMapBoundries(). I think my code is wrong, so my
question is how do call the getMapBoundries() method? Have a look at the full class below

Code

import React, { useState } from 'react';
import { StylSheet, Text, View, Platform, Button } from 'react-native';
import MapView, { PROVIDER_GOOGLE, Marker } from 'react-native-maps'

const Map = () => {
    const [initialRegion, setInitialRegion] = useState({ latitude: 51.509865, longitude: -0.118092, latitudeDelta: 0.0043, longitudeDelta: 0.0034 })
    const [places, setPlaces] = useState([])

    const searchHere = () => {
        var obj = new MapView()
        obj.getMapBoundaries()
            .then((northEast, southWest) => {
                console.log(northEast)
            })
            .catch((error) => {
                console.log(error)
            })
    }

    return (
        <View style={styles.container}>
            <MapView provider={PROVIDER_GOOGLE}
                style={styles.map}
                region={initialRegion}>
            </MapView>
            <View style={styles.button}>
                <Button
                    title='Search this area'
                    onPress={searchHere}
                />
            </View>
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        width: '100%',
        height: '100%'
    },
    map: {
        flex: 1,
    },
    button: {
        width: '60%',
        height: 100,
        position: 'absolute',
        alignSelf: 'center',
        top: 100,
    }
});


export default Map

@JonathanSainsburys
Copy link

@rborn i believe iv found the solution. Turns out i need to use the refs prop as follows:

 <MapView 
      refs={ (mapView) => {
             this.mapView = mapView
       }}
      provider={PROVIDER_GOOGLE}
      style={styles.map}
      region={initialRegion}>
</MapView>

then i can call this.mapView.getMapBoundries() anywhere i need to

@rborn
Copy link
Collaborator

rborn commented May 3, 2020

Exactly, JSX is declarative, you don't need to recreate it with new

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants