React native maps MapView Region not updating on MarkerDrag - react-native

I am trying to implement to search and pinpoint a location in react native. I am using react-native-maps and react-native-google-places-autocomplete packages for their obvious usages.
First I have initiated region in the state as:
constructor(){
this.state={
mapRegion: {
latitude: this.props.latitude ? this.props.latitude : 27.7172,
longitude: this.props.longitude ? this.props.longitude : 85.3240,
latitudeDelta: 0.005,
longitudeDelta: 0.005,
},
}
}
I have tried to update the region on pressing the autocompleted search results as well as upon marker drag. But I get an error as:
Error: You attempted to set the key latitude with the value '27' on an object that is meant to be immutable and has been frozen.
I have implemented the code as:
<GooglePlacesAutocomplete
placeholder='Search'
fetchDetails={true}
onPress={(data, details = null) => {
let tempMapRegion = this.state.mapRegion;
tempMapRegion.latitude = details.geometry.location.lat;
tempMapRegion.longitude = details.geometry.location.lng;
this.setState({ mapRegion: tempMapRegion })
}}
query={{
key: AppSettings.googleMapApiKey,
language: 'en',
}}
/>
<MapView
initialRegion={{
latitude: this.props.latitude ? this.props.latitude : 27.7172,
longitude: this.props.longitude ? this.props.longitude : 85.3240,
latitudeDelta: 0.005,
longitudeDelta: 0.005,
}}
region={this.state.mapRegion}
onRegionChange={(e) => { this.onRegionChange(e) }}
style={{ height: 300, width: 300 }}
>
<Marker draggable
coordinate={this.state.mapRegion}
onDragEnd={(e) => {
let tempMapRegion = this.state.mapRegion;
tempMapRegion.latitude = e.nativeEvent.coordinate.latitude
tempMapRegion.longitude = e.nativeEvent.coordinate.longitude
this.setState({ mapRegion: tempMapRegion })
}}
/>
</MapView>
The onRegionChange in the MapView works smoothly and marker is dragged automatically to the centre, but the reverse process brings up the above error.
What is causing this error and how do I get past this?

<View style={{ padding: 2, }}>
<GooglePlacesAutocomplete
placeholder='Search'
fetchDetails={true}
onPress={(data, details = null) => {
let tempMapRegion = this.state.mapRegion;
tempMapRegion.latitude = details.geometry.location.lat;
tempMapRegion.longitude = details.geometry.location.lng;
this.map.animateToRegion(this.newRegion(tempMapRegion));
this.setState({ address: data.description })
}}
query={{
key: AppSettings.googleMapApiKey,
language: 'en',
}}
/>
</View>
<MapView
provider={this.props.provider}
ref={ref => { this.map = ref; }}
mapType={MAP_TYPES.TERRAIN}
initialRegion={this.state.mapRegion}
onRegionChangeComplete={(e) => { this.onRegionChange(e) }}
style={{ height: width, width: width, marginTop: -5 }}
>
<Marker draggable
coordinate={this.state.mapRegion}
onDragEnd={(e) => {
let tempMapRegion = this.state.mapRegion;
tempMapRegion.latitude = e.nativeEvent.coordinate.latitude
tempMapRegion.longitude = e.nativeEvent.coordinate.longitude
this.map.animateToRegion(this.newRegion(tempMapRegion));
// this.setState({ mapRegion: tempMapRegion })
}}
/>
</MapView>
So, what basically did the thing, was using the animateToRegion property of the mapview. It basically animates the view to the mentioned region and then calls the onRegionChange. I had stumbled upon this answer a number if times but it hadnt worked. Weirdly this only works in the build version and not while debugging, not on the emulator at least.
Thanks to https://stackoverflow.com/a/53836679/5379191 this answer for showing the way though.
newRegion(tempMapRegion) {
return {
...this.state.mapRegion,
...this.regionCoordinate(tempMapRegion),
};
}
regionCoordinate(tempMapRegion) {
return {
latitude: tempMapRegion.latitude,
longitude: tempMapRegion.longitude,
};
}

Related

Cant able to update the polyline in the map using react hooks

Please help me here I am trying to update polyline based on the user tracking but the polyline is not getting updated and i am using the functional components i had tried different ways but its not working. Please help me Below is code i had wrote
Map component
<MapView
provider={PROVIDER_GOOGLE}
style={{flex: 1, borderRadius: 20}}
showsUserLocation={true}
userLocationPriority={'high'}
userLocationFastestInterval={2000}
initialRegion={{
latitude: originLat,
longitude: originLng,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}>
<Marker.Animated
coordinate={
new AnimatedRegion({
latitude: originLat,
longitude: originLng,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
})
}
pinColor={'#FF8C00'}>
<Image
source={Images.marker}
style={{height: 100, width: 100, marginTop: 36}}
/>
</Marker.Animated>
<Polyline
coordinates={userLocation}
strokeColor={colors.blue}
strokeWidth={3}
/>
</MapView>
Watch poistion
const watchPosition = () => {
Geolocation.watchPosition(
position => {
const userUpdatedLocation = new Location();
userUpdatedLocation.latitude = position.coords.latitude;
userUpdatedLocation.longitude = position.coords.longitude;
setUserLocationLL(userUpdatedLocation);
},
error => {
Alert.alert(error.message.toString());
},
{
enableHighAccuracy: true,
timeout: 100,
maximumAge: 0,
distanceFilter: 0,
useSignificantChanges: true
},
);
}
Below are variables i am assigning
const [userLocation, setUserLocation] = useState<Location[]>([]);
const [userLocationLL, setUserLocationLL] = useState<Location>(initialLocation);
Polyline :
<Polyline
coordinates={userLocation}
strokeColor={colors.blue}
strokeWidth={3}
/>
Updating the set state
useEffect(() => {
setUserLocation(locations => [...locations, userLocationLL]);
}, [userLocationLL]);
Looks like you are missing updating the userLocation state holding the historical location data in an array to plot out.
<Polyline
coordinates={userLocation} // <-- array of locations
strokeColor={colors.blue}
strokeWidth={3}
/>
You can either update the userLocation array in the watchPosition handler
const watchPosition = () => {
Geolocation.watchPosition(
position => {
const userUpdatedLocation = new Location();
userUpdatedLocation.latitude = position.coords.latitude;
userUpdatedLocation.longitude = position.coords.longitude;
setUserLocationLL(userUpdatedLocation);
// Append new position to `userLocation` array
setUserLocation(locations => [...locations, userUpdatedLocation]);
},
....
);
}
or you can use an useEffect hook to update the userLocation state when the userLocationLL state updates.
useEffect(() => {
setUserLocation(locations => [...locations, userLocationLL]);
}, [userLocationLL]);

React native call useEffect when variable has changed

I have two components Place and Map. Every time when user in component Place clicks button "Show on Map" I open Map and get route params latitude/longitude from Place and I want to render area near marker. I try to set current latitude/longitude to the state to rerender component near marker but it doesn't work and my map opens at the same place where I closed it. The only option I found is to make component unmount when close Map but I don't think it is the best solution.
Place:
const onNavigationTap = () => {
navigation.navigate('Map', {
destination: data["data"].coordinates
});
}
return(
<TouchableOpacity onPress={() => onNavigationTap()}>
<View style={{flexDirection: "row", alignItems: "center"}}>
<Ionicons size={hp('5%')} name={'navigate-circle-outline'} color='white'/>
</View>
</TouchableOpacity>
)
Map:
const [currentDestination, setCurrentDestination] = useState(undefined);
if (route.params) {
var {destination} = route.params;
}
useEffect(() => {
setCurrentDestination(destination);
}, [destination]);
return (
<MapView
mapType={satellite ? "hybrid" : "standard"}
style={{flex: 1}}
showsUserLocation={true}
followsUserLocation={true}
provider={PROVIDER_GOOGLE}
initialRegion={
currentDestination ?
{
longitude: parseFloat(currentDestination.longitude),
latitude: parseFloat(currentDestination.latitude),
longitudeDelta: 0.0043,
latitudeDelta: 0.0034,
} : {
latitude: 53.227200,
longitude: 50.243698,
latitudeDelta: 3,
longitudeDelta: 3,
}
}
>
</MapView>
)
why not use directly routes.params like this :
<MapView
mapType={satellite ? 'hybrid' : 'standard'}
style={{ flex: 1 }}
showsUserLocation={true}
followsUserLocation={true}
provider={PROVIDER_GOOGLE}
initialRegion={
route.params.destination
? {
longitude: parseFloat(route.params.destination.longitude),
latitude: parseFloat(route.params.destination.latitude),
longitudeDelta: 0.0043,
latitudeDelta: 0.0034,
}
: {
latitude: 53.2272,
longitude: 50.243698,
latitudeDelta: 3,
longitudeDelta: 3,
}
}></MapView>
like this whenever you call place , routes.params will be new value

How to fit all markers to given coordinates provided dynamically? (react-native-maps)

I have implemented this example where I can zoom in to the given coordinates by clicking on the button.
Below you can read what I aiming to implement and I couldn't:
First, I want to be able to read coordinates out of a dynamic array, I tried by putting the array in the state but it fails.
const ASPECT_RATIO = width / height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const MARKERS = [
{
latitude: 42.637368,
longitude: 21.148682,
},
{
latitude: 42.604021,
longitude: 21.261292,
},
{
latitude: 42.500833,
longitude: 21.181641,
}
];
const DEFAULT_PADDING = { top: 60, right: 60, bottom: 60, left: 60 };
export default class map_of_patients extends React.Component {
constructor(){
this.state={}
}
fitAllMarkers() {
this.map.fitToCoordinates(MARKERS, {
edgePadding: DEFAULT_PADDING,
animated: true,
});
}
render() {
return (
<View style={styles.container}>
<MapView
ref={ref => {
this.map = ref;
}}
style={styles.map}
initialRegion={{
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
>
{MARKERS.map((marker, i) => (
<Marker key={i} identifier={`id${i}`} coordinate={marker} />
))}
</MapView>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => this.fitAllMarkers()}
style={[styles.bubble, styles.button]}
>
<Text>Fit All Markers</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Second I would like to call the function fitAllMarkers into coordinates on start, so I don't have to click somewhere to do it. I tried by calling in inside componentDidMount() but didn't work either.
Third, I would like to zoom in to the region by giving the coordinates from the dynamic array.
I managed to fix all the issues mentioned in the question by doing the following:
-To fit all markers on initialization of the map I did as #Marek Lisik suggested using onMapReady={this.fitAllMarkers.bind(this)}.
-And to read from a dynamic array I managed to pass the array beforehand to the state and by the time the map is initialized it already had some data.
Here is the entire code again with the changes:
constructor(props) {
super(props);
this.state = {
region: {
latitude: 42.65847,
longitude: 21.16070,
latitudeDelta: 0.500,
longitudeDelta: 0.500 * width / height,
},
MARKERS: [
{
latitude: 42.637368,
longitude: 21.148682,
description: "dfsdf",
title: "title"
},
{
latitude: 42.604021,
longitude: 21.261292,
description: "sdfsdf",
title: "title"
},
{
latitude: 42.500833,
longitude: 21.181641,
description: "sdfsdfds",
title: "title"
}
]
};
}
fitAllMarkers() {
this.map.fitToCoordinates(this.state.MARKERS, {
edgePadding: DEFAULT_PADDING,
animated: true,
});
}
render() {
return (
<View style={styles.container}>
<MapView
ref={ref => {
this.map = ref;
}}
style={styles.map}
initialRegion={this.state.region}
onMapReady={this.fitAllMarkers.bind(this)}
>
{this.state.MARKERS.map((marker, i) => (
<Marker key={i} identifier={`id${i}`} coordinate={marker}
description={marker.description}>
</Marker>
))}
</MapView>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => this.fitAllMarkers()}
style={[styles.bubble, styles.button]}
>
<Text>Fit All Markers</Text>
</TouchableOpacity>
</View>
</View>
);
}

React native initial region not updating with state

I have below code base
componentDidMount() {
//firebase.firestore().collection('locations').doc('test').set({ test: 'test' })
Geolocation.getCurrentPosition(
(position) => {
if (position) {
this.setState({
region: {
latitude: Number(position.coords.latitude),
longitude: Number(position.coords.longitude),
latitudeDelta: 0.003,
longitudeDelta: 0.003,
},
});
}
alert(JSON.stringify(this.state.region))
},
(error) => alert(JSON.stringify(error)),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000, distanceFilter: 100 }
);
this.unsubscribe = this.locationRef.onSnapshot(this.getCollectionData);
// firebase.firestore().collection("locations").get().then(QuerySnapshot => { });
}
And Map
render() {
return (<View style={{
flex: 1,
flexDirection: 'column',
}}>
<HeaderNavigationBar {...this.props} />
<MapView showsUserLocation={true}
// ref={map => this.map = map}
initialRegion={this.state.region}
style={styles.container}
>
{this.state.AllLocations.map((marker, index) => (
<MapView.Marker key={index}
coordinate={marker.locations.coords}
title={marker.Title}
/>
))}
</MapView>
</View>);
}
componentDidMount is correcty updating the state, but map is not showing correct position,it is taking as it was set in construction
constructor(props) {
super(props);
this.locationRef = firebase.firestore().collection('locations');
this.unsubscribe = null;
this.state = {
isLoading: true,
AllLocations: [],
region: {
latitude: 0,
longitude: 0,
latitudeDelta: 0.003,
longitudeDelta: 0.003,
}
};
}
Please help
thanks
initialRegion is used for the initial render of map.
In your case, You are getting the user location after your map render, Thats the reason its not getting updated.
To counter this there are two ways.
1: use a loading state till you get your use location, and prevent the map render before you get the location.
2: Use region, for example region = {this.state.region}.
<MapView showsUserLocation={true}
// ref={map => this.map = map}
initialRegion={this.state.region}
region={this.state.region}
style={styles.container}
>
{this.state.AllLocations.map((marker, index) => (
<MapView.Marker key={index}
coordinate={marker.locations.coords}
title={marker.Title}
/>
))}
</MapView>
Both will work, it depends on your usecase.
Try this
{this.state.region ? (<MapView
style={[styles.map]}
initialRegion={this.state.region}
region={this.state.region}
provider={PROVIDER_GOOGLE}
>
{this.state.AllLocations.map((marker, index) => (
<MapView.Marker key={index}
coordinate={marker.locations.coords}
title={marker.Title}
/>
))}
</MapView>) : (
<MapView
loadingEnabled={true}
style={styles.map}
showsMyLocationButton={true}
provider={PROVIDER_GOOGLE}
style={[styles.map]}
>
</MapView>
)
}
Set region: null in the constructor it will work

Is it possible to change MapView Market pinColor programatically by clicking any button?

I have implemented a MapView with react-native-maps. I'm trying to change Marker's pinColor by clicking on it.
Note: I have large amounts of markers. So I don't think refreshing all view can be a good solution. I need directly change the selected marker's color.
I didn't find how to do it. I tried below code:
class TestMap extends React.Component {
constructor(props) {
this.state = {
testColor: "#FFFFFF",
userLatitude:0,
userLongitude:0,
data:[]
}
}
render() {
return (
<MapView
provider={PROVIDER_GOOGLE}
showsTraffic={true}
showsBuildings={true}
toolbarEnabled={true}
loadingEnabled={true}
style={styles.map}
initialRegion={{
latitude: this.state.userLatitude,
longitude: this.state.userLongitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA
}}
onPoiClick={this.onPoiClick}
showsUserLocation={true}
followsUserLocation={true}
showsMyLocationButton={true}
loadingBackgroundColor="#FEA405"
loadingIndicatorColor="white"
onLongPress={e => this.onMapPress(e)}
enableZoomControl
>
{this.ListMarkers()}
</MapView>
)};
ListMarkers() {
return this.state.data.map((data, i) => {
return (
<Marker
key={i}
onPress={e => this.onPressMarker(e, i, data)}
coordinate={{
longitude: data.LONGITUDE,
latitude: data.LATITUDE
}}
pinColor={this.state.testColor}
/>
)}
)};
onPressMarker(e, index, data) {
this.setState({testColor:"#000000"});
}
}
I expect the color of marker should change after clicking on it but it is not working.
Thanks for your help.
You can set the selected pin in the state and use a different style in that case, if you have some id in your data you can use that value instead of the index:
constructor(props) {
this.state = {
selectedPin: -1,
}
}
ListMarkers = () => {
return this.state.data.map((data, i) => {
return (
<Marker
key={i}
onPress={e => this.onPressMarker(e, i, data)}
coordinate={{
longitude: data.LONGITUDE,
latitude: data.LATITUDE
}}
pinColor={ i === this.state.selectedPin ? '#FF0000' : '#FFFFFF'}
/>
)}
)};
onPressMarker = (e, index, data)=> {
this.setState({selectedPin:index});
}