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

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]);

Related

How to optimize react native maps performance when using animated marker

I'm trying to optimize react-native-maps perfomance.
This is my customized Marker
export function usePrevious(value) {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function VehicleMarker({CarSetting, car, onOpen}) {
const [marker, setMarker] = useState(null);
const [CarIcon, setCarIcon] = useState(Car);
const [coordinate] = useState(
new AnimatedRegion({
latitude: car.Lt,
longitude: car.Ln,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}),
);
const [CarType, setCarType] = useState(0);
const [tracksViewChanges, setTracksViewChanges] = useState(false);
const prevCoordinate = usePrevious(coordinate);
useEffect(() => {
if (prevCoordinate !== coordinate) {
setTracksViewChanges(true);
} else {
setTracksViewChanges(false);
}
}, [coordinate]);
useEffect(() => {
animateMarker();
setCarIcon(getCarImage(CarSetting, car));
setCarType(
CarSetting?.find(setting => setting.CarID == car.CarID)?.CarTypeID,
);
}, [car]);
const animateMarker = () => {
const newCoordinate = {
latitude: car.Lt,
longitude: car.Ln,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
};
if (Platform.OS === 'android') {
if (marker) {
marker.animateMarkerToCoordinate(newCoordinate, 5000);
}
} else {
coordinate.timing(newCoordinate).start();
}
};
return (
<Marker.Animated
key={car.CarID}
ref={marker => {
setMarker(marker);
}}
coordinate={coordinate}
onPress={onOpen}
anchor={{x: 0.5, y: 0.5}}
rotation={CarType == 0 ? 360 - car.Angle : 0}
tracksViewChanges={tracksViewChanges}>
<Animated.View style={styles.markerWrap}>
<Animated.Image source={CarIcon} style={styles.marker} />
</Animated.View>
</Marker.Animated>
);
}
I have about 80 vehicles, and half of them are always on the go, I update their coordinate every 15s . And each vehicle have a marker is plates number ( because I don't want it rotates so I clone it into an other component ).
It run smooth with under 30vehicle. But more than 30 it becomes laggy. I can't cluster them because our customer wants to see all of them.
Have any solutions for it?
my solution is reducing the resolution of the image ( to the size I want, in my case is 30x30 px ) and use icon istead of image.
<Marker.Animated
ref={marker => {
setVehicle(marker);
}}
coordinate={coordinate}
onPress={onOpen}
anchor={{x: 0.5, y: 0.5}}
rotation={CarType == 0 ? 360 - car.Angle : 0}
tracksViewChanges={tracksViewChanges}
icon={CarIcon}>
{/* <Animated.View style={styles.markerWrap}>
<Animated.Image source={CarIcon} style={styles.marker} />
</Animated.View> */}
</Marker.Animated>

"NSCFBoolean objectForKeyedSubscript:]: unrecognized selector sent to instance" in React Native

I am using React Native with Expo and this was originally a class based component but I converted it to a functional component because I want to use hooks. Now it is throwing an error shown in the screenshot. I am not sure what to do about the error?I have seen one other posting about this, but was a little lost (I am practicing with react). The component is basically a GPS with a marker! Thank you
import { View, Text, Animated, StyleSheet } from "react-native";
import MapView, { Marker, PROVIDER_GOOGLE } from "react-native-maps";
import React, { Component, useState } from "react";
import { MaterialCommunityIcons } from "react-native-vector-icons";
const LATITUDE = 18.7934829;
const LONGITUDE = 98.9867401;
const LATITUDE_DELTA = 0.009;
const LONGITUDE_DELTA = 0.009;
export default function MapLocation() {
const [location, setLocation] = useState({
isLoading: true,
latitude: LATITUDE,
longitude: LONGITUDE,
error: null,
});
var getMapRegion = () => ({
latitude: location.latitude,
longitude: location.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position);
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => setLocation({ error: error.message }),
{ enableHighAccuracy: false, timeout: 200000, maximumAge: 1000 }
);
navigator.geolocation.watchPosition((position) => {
const { latitude, longitude } = position.coords;
setLocation({ latitude, longitude });
});
const { isLoading } = location;
return (
<View style={{ flex: 1 }}>
<MapView
style={{ flex: 1 }}
provider={PROVIDER_GOOGLE}
region={getMapRegion}
showsUserLocation={true}
showsMyLocationButton={true}
>
<Marker coordinate={getMapRegion}>
<MaterialCommunityIcons name="egg" color={"white"} size={35} style={styles.shadow} />
</Marker>
<Marker
coordinate={{ latitude: 34.0198536, longitude: -80.923467 }}
pinColor="maroon"
title={"title"}
description={"description"}
>
<MaterialCommunityIcons name="school" color={"maroon"} size={40} style={styles.shadow} />
</Marker>
</MapView>
</View>
);
}
const styles = StyleSheet.create({
shadow: {
// transform: [{ rotateZ: "10deg" }],
shadowColor: "black",
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.5,
shadowRadius: 2,
elevation: 3,
},
});
There were several problems with my conversion to hooks using useState and useEffect. Both of which I solved with this guide on converting classes to functions. I was not using the "useEffect hook" in place of component did mount, I also did not have an empty array at the end of the useEffect hook which made it re-render constantly from what I understand.
Link to functional component conversion steps

React native maps MapView Region not updating on MarkerDrag

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,
};
}

React Native Maps animate to bearing after initial render

I am trying to call a method on the map right after the component first renders. In this case this.map is undefined, but shouldn't it be set by the ref? How do I get a reference to the MapView in the componentDidMount method?
import React from 'react';
import { MapView } from 'expo';
export default class Map extends React.Component {
componentDidMount() {
this.map.animateToBearing(25)
}
render() {
return (
<MapView
ref={ref => { this.map = ref }}
style={{ flex: 1 }}
mapType="satellite"
initialRegion={{
latitude: 39.2741004,
longitude: -76.6502307,
latitudeDelta: 0.002,
longitudeDelta: 0.001,
}}
/>
);
}
}
Looking at this Github issue, you probably have to use onLayout instead of componentDidMount.
For example:
<MapView
ref={ref => { this.map = ref }}
onLayout={() => this.map.animateToBearing(25)}
....
/>
const [heading, setHeading] = useState(0);
const cameraView = {
center: {
latitude: lat,
longitude: lng,
},
pitch: 10,
heading: heading,
altitude: 1,
zoom: 15
};
let getHeading = () => {
Location.watchHeadingAsync(value => {
setHeading(value.magHeading)
});
};
useEffect(()=>{
initialLocation();
getHeading();
}, [])
By using watchHeadingAsync you can update the heading constantly.

React-Native-Maps don't seem to rerender once the geo location is acquired

I'm trying to create a simple app that loads a google map (using airbnb's react-native-maps library) and shows the user's current location. What I'm seeing is that the map always shows the default initial position rather than re-rendering once the user's location is acquired.
I'm using React 0.42 and testing only on iOS. Here is some code to clarify:
1.) I set an initial state
state = {
region: {
latitude: 52,
longitude: 5,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}
}
2.) I get the user's location within componentDidMount
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
region: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.0011
}
});
},
(error) => alert(JSON.stringify(error)),
{enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}
);
}
3.) With render, I display the map with the initial region, and expect that region to change once the user's location is acquired
render() {
return (
<View style={{ flex: 1 }}>
<View style={{backgroundColor: 'coral', height: 70, justifyContent: 'center', alignItems: 'center'}}>
<Text>
<Text>longitude: {this.state.region.longitude}</Text>
<Text>latitude: {this.state.region.latitude}</Text>
</Text>
</View>
<View style={styles.container}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={this.state.region}
region={this.state.region}
onRegionChange={this.onRegionChange}
onRegionChangeComplete={this.reloadEntities}
/>
</View>
</View>
);
}
Here is the onRegionChange, just updates the state with the new region, which I believe will cause a re-render
onRegionChange = (region) => {
this.setState({ region });
}
NOTE: The longitude and latitude text values do update as the region on the map changes, and they have they are updated once the user's location is acquired.
So I'm a bit confused as to why the map does not change what it's showing once the user's location is acquired. Any help would be much appreciated!
Update: I've taken a look at this thread: https://github.com/airbnb/react-native-maps/issues/43 and it seems to revolve mainly around Android, but I did try to remove the enableHighAccuracy option with no luck.
Set the region of the MapView with the value of your region state this.state.region.
You need to get the current position and setting it to the region and then to use the watchPosition function to get the coordinates everytime the device detects there's a change in the location, this time set the new values to your region state.
This would work this way
import React, { Component } from 'react';
import MapView from 'react-native-maps';
import { AppRegistry, View } from 'react-native';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0122;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const SPACE = 0.01;
class Test extends Component {
constructor() {
super();
this.state = {
region: {
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA
}
}
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
region: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
accuracy: position.coords.accuracy
}
});
},
(error) => alert(error.message),
{timeout: 10000}
);
this.watchID = navigator.geolocation.watchPosition((position) => {
const newRegion = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
accuracy: position.coords.accuracy
}
this.setState({newRegion});
});
}
componentWillUnmount() {
navigator.geolocation.clearWatch(this.watchID);
}
render() {
return (
<View style={styles.container}>
<MapView
style={styles.map}
region={this.state.region}
showsUserLocation={true}
followUserLocation={true}>
</MapView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
});
AppRegistry.registerComponent('Russia terrorist state', () => Test);
Be sure to enable showsUserLocation and followUserLocation in the MapView, this way the app will ask the user current location.
followUserLocation depends on showUserLocation.
Constant longitude and latitude are only as example.
You should not use initialRegion prop with region prop together:
Use this prop instead of region only if you don't want to control the
viewport of the map besides the initial region.
Source
It should work after you remove the initialRegion prop.
It's also important to get rid of onRegionChangeComplete={this.reloadEntities}.
Every time the region changes, react-native will reload and default back to the initial state you declared in your constructor.