(React Native) Polyline Maps + AsyncStorage | Attempted to assign to readonly property - properties

so I'm trying to: when a button is called, it does a setInterval that every minute saves the latitude and longitude, so I can build a polyline that conennects them.
When the button title is "Iniciar" (start) it does what I said and then the title changes to "Concluir", when the title is "Concluir" (finish) it saves the coords with AsyncStorage.
Here is some of the code with a few comments (I removed the part when it gets and updates the coords becouse it's working just fine)
export default class geolocation extends Component {
constructor(props) {
super(props);
this.state = {
initialPosition: {
latitude: 0,
longitude: 0,
latitudeDelta: 0,
longitudeDelta: 0,
kind: '',
},
markerPosition: { //marker where I am right now
latitude: 0,
longitude: 0,
kind: '',
},
refreshIntervalId: null, //so I can cancel the setInterval
markers: [], //here it's all the markers that I'll get from the setInterval
titleButtonStartFinish: 'Iniciar', //so I can change the title to see if i'm starting or finishing
};
AsyncStorage.getItem('markers').then((value) => { //so I can get the markers I saved in Async
if (value) {
const s = JSON.parse(value);
this.setState({ markers: s });
}
});
this.ButtonStartFinishOnPress = this.ButtonStartFinishOnPress.bind(this);
}
async ButtonStartFinishOnPress() {
let s = this.state;
if (s.titleButtonStartFinish === 'Iniciar') {
s.titleButtonStartFinish = 'Concluir';
s.markers = [];
s.refreshIntervalId = setInterval(() => {
s.markers.push(this.state.markerPosition);
}, 60000);
this.setState(s);
}
else if (s.titleButtonStartFinish === 'Concluir') {
s.titleButtonStartFinish = 'Iniciar';
clearInterval(s.refreshIntervalId);
console.log(JSON.stringify(s.markers));
if (s.markers) {
const x = JSON.stringify(s.markers);
await AsyncStorage.setItem('markers', x);
}
this.setState(s);
}
}
render() {
return (
<View style={styles.container}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
region={this.state.initialPosition}
>
<Polyline // the polyline connecting all the markers
coordinates={this.state.markers}
strokeColor="#B24112"
strokeWidth={4}
/>
<Marker coordinate={this.state.markerPosition}> //showing the marker where i am right now
<View style={styles.radius}>
<View style={styles.marker} />
</View>
</Marker>
{this.state.markers.map((marker) => ( //showing where all the markers are
<Marker coordinate={marker} />
))}
</MapView>
<ButtonStartFinish
title={this.state.titleButtonStartFinish}
onPress={() => this.ButtonStartFinishOnPress()}
/>
</View>
);
}
}
Everything seems to be working just fine, until I add the Polyline thing.
It get the coords, it builds the polyline, then I can close the app and open again and the coords and the polyline comes back (becouse I saved them in the AsyncStorage), BUT when I do that and then i hit the start button again, it was supposed to delete the coords in "this.state.markers" and start saving new coords in the setInterval.. but it doesn't, that's when this error happens. click here to see the error

Got it!
This bug can happen when you are trying to make an array.push in a string instead of an array object, when you make your pushing in the same array into the the object more then once, or when you are not using the setState correctly.
In my case, I just needed to save the markers array in another const;
then JSON.parse(the backup const);
then the markers array = [];
then i use the .forEach function in the backup const, like
backup.forEach((marker) => { this.state.markers.push(markers)});
That's the way I found so my code won't try to push an array to a string
I tried to JSON.parse(markers array) and JSON.parse(the marker I want to push) but that wasn't enought

Related

React Native Maps not centering the map on user location if it gets updated

This is the first time I'm working with maps in React Native. I've some initial values setup like:
const [latitude, setLatitude] = useState(38.889815);
const [longitude, setLongitude] = useState(-77.005900);
const [heading, setHeading] = useState(0);
const GEOLOCATION_OPTIONS = {
pitch: 65,
altitude: 100,
zoom: 17,
};
const [camera, setCamera] = useState({
center: {
latitude: latitude,
longitude: longitude
},
heading: heading,
pitch: GEOLOCATION_OPTIONS.pitch,
zoom: GEOLOCATION_OPTIONS.zoom,
altitude: GEOLOCATION_OPTIONS.altitude
});
Then I'm asking for user location
function getCurrentLocation() {
Geolocation.watchPosition(
position => {
const myLastPosition = myPosition;
const myPosition = position.coords;
if (myPosition != myLastPosition) {
setLatitude(myPosition.latitude)
setLongitude(myPosition.longitude)
setHeading(myPosition.heading)
setCamera(
{
center: {
latitude: myPosition.latitude,
longitude: myPosition.longitude
},
heading: heading,
pitch: GEOLOCATION_OPTIONS.pitch,
zoom: GEOLOCATION_OPTIONS.zoom,
altitude: GEOLOCATION_OPTIONS.altitude
}
)
}
},
null,
{
enableHighAccuracy: true,
}
);
};
useEffect(() => {
getCurrentLocation()
}, [camera])
And finally, I'm displaying the map as
<View style={{ flex: 1 }}>
<MapView
ref={(mapView) => { _mapView = mapView }}
provider={PROVIDER_GOOGLE}
scrollEnabled={false}
pitchEnabled={false}
rotateEnabled={false}
loadingEnabled={true}
moveOnMarkerPress={true}
showsUserLocation={true}
showsBuildings={false}
showsCompass={true}
showsMyLocationButton={false}
followsUserLocation={true}
style={styles.mapMain}
userLocationPriority={'high'}
minZoomLevel={GEOLOCATION_OPTIONS.zoom}
maxZoomLevel={GEOLOCATION_OPTIONS.zoom}
initialCamera={camera}>
</MapView>
</View >
The options which are true and false are basically there to stop the user from moving the app. The idea is that the map will update as per user location and will also rotate if the user heading is updated.
Now when I manually change the user location for the iOS simulator I'm running I do Features > Location > Custom Location and doing so I can see that the user location marker is moved to that position but the camera is not getting updated through the values of the state are also updated.
Also, the map doesn't rotate if the heading is updated.
What I'm trying to do is the same as navigation, it moves as the user moves and rotates when the user takes a turn.

current location works perfect with long and lat null values, however not with IOS?

I want to get my current location on a map on IOS, however long: null and lat: null, both seem to get the current location on a map for android but do not work for IOS, what do I do to get the current location on IOS ? below is the code, for example my initial region is lat:null, and long: null, however on android i get the current location, however on ios i do not
class MapScreen extends Component {
//Set the HeaderTitle screen
static navigationOptions = props => {
const placeName = props.navigation.getParam("placeName");
return { headerTitle: placeName.toUpperCase() };
};
constructor(props) {
super(props);
//Initial State
this.state = {
lat: null,
long: null,
places: [],
isLoading: false,
placeType: "restaurant"
};
}
componentDidMount() {
console.log(this.props);
const { navigation } = this.props;
const placeType = navigation.getParam("placeType");
this.setState({ placeType: placeType });
this.getCurrentLocation();
}
/**
* Get current user's position
*/
getCurrentLocation() {
navigator.geolocation.getCurrentPosition(position => {
const lat = position.coords.latitude;
const long = position.coords.longitude;
this.setState({ lat: lat, long: long });
this.getPlaces();
});
}
/**
* Get the Place URL
*/
getPlacesUrl(lat, long, radius, type, apiKey) {
const baseUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?`;
const location = `location=${lat},${long}&radius=${radius}`;
const typeData = `&types=${type}`;
const api = `&key=${apiKey}`;
return `${baseUrl}${location}${typeData}${api}`;
}
getPlaces() {
const { lat, long, placeType } = this.state;
const markers = [];
const url = this.getPlacesUrl(lat, long, 1500, placeType, GOOGLE_API_KEY);
fetch(url)
.then(res => res.json())
.then(res => {
res.results.map((element, index) => {
const marketObj = {};
marketObj.id = element.id;
marketObj.name = element.name;
marketObj.photos = element.photos;
marketObj.rating = element.rating;
marketObj.vicinity = element.vicinity;
marketObj.marker = {
latitude: element.geometry.location.lat,
longitude: element.geometry.location.lng
};
markers.push(marketObj);
});
//update our places array
this.setState({ places: markers });
});
}
render() {
const { lat, long, places } = this.state;
return (
<View style={styles.container}>
<View style={styles.mapView}>
<MapView
style={{
flex: 1
}}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: lat,
longitude: long,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}}
>
{places.map((marker, i) => (
<MapView.Marker
key={i}
coordinate={{
latitude: marker.marker.latitude,
longitude: marker.marker.longitude
}}
title={marker.name}
/>
))}
</MapView>
did you tried react-native-geolocation-service
React native geolocation service for iOS and android.
Why ?
This library is created in an attempt to fix the location timeout issue on android with the react-native's current implementation of Geolocation API. This library tries to solve the issue by using Google Play Service's new FusedLocationProviderClient API, which Google strongly recommends over android's default framework location API. It automatically decides which provider to use based on your request configuration and also prompts you to change the location mode if it doesn't satisfy your current request configuration.
NOTE: Location request can still timeout since many android devices have GPS issue in the hardware level or in the system software level. Check the FAQ for more details.

Update map markers dynamically on slider change - React Native

I have a little problem with rendering markers.
Everything gets slow and laggy.
You can see it on this video: https://streamable.com/fjqe7
Is there a way to render my Markers smoother?
this is what I tried
<View style={styles.slider}>
<Slider
maximumValue={1000}
minimumValue={100}
step={50}
value={this.state.newRadius}
onValueChange={newRadius => {
this.setState({newRadius})
this.getLocation()
}}
/>
<View>
<Text>Radius: {this.state.newRadius} meter</Text>
</View>
</View>
onValueChange I'm loading the getLocation()
my getLocation is for showing and filtering my Markers inside my RadiusValue
getLocation(){
let { region } = this.state;
let { latitude, longitude } = region;
let markers = spielanlagen.map(marker => {
let name = marker.name
let image = marker.image
let street = marker.street
console.log(name);
console.log(marker.coordinate);
let coords = marker.coordinate
return {
coordinate: {
latitude: coords[0],
longitude: coords[1],
},
name: marker.name,
street: marker.street,
image: marker.image
}
}).filter(marker => {
let distance = this.calculateDistance(latitude, longitude, marker.coordinate.latitude, marker.coordinate.longitude);
return distance <= this.state.newRadius;
});
this.setState({
markers: markers,
loaded: true,
});
};
calculateDistance(origLat, origLon, markerLat, markerLon) {
return geolib.getDistance(
{latitude: origLat, longitude: origLon},
{latitude: markerLat, longitude: markerLon}
);
}
I hope there is a better solution with re-rendering the markers.

Getting error when changing the coordinates of region on react-native-maps

I am getting an error "You attempted to set the key latitude with the value xx.xxxxx on an object that is meant to be immutable and has been frozen when my app starts or when i try to change the coordinates of the region via callback funciton.
My goal is to be able to change the region location from input field like on Google Maps. But whenever i change the coordinates i get this error. I tried with rerendering the map with the new coordinates and that works, but that will do lots of requests.
Is there a way of giving the region new coordinates and showing it without rerendering the whole map ?
Here is my code:
export default class Map extends Component{
constructor(){
super();
this.state = {
region: {
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
},
radius: 500
}
}
componentWillMount(){
}
regionChange = (region) =>{
let reg = this.state.region;
reg.latitude = region.latitude;
reg.longitude = region.longitude;
this.setState({region:reg});
}
changeRadius = (radius) => {
this.setState({radius});
console.log("RADIUS", this.state.radius);
}
render(){
return (
<View style={ styles.map }>
<MapView
provider={PROVIDER_GOOGLE}
style={ styles.map }
region={this.state.region}
onRegionChangeComplete={(region) => {this.regionChange(region)}}
onPress={(event) => this.regionChange(event.nativeEvent.coordinate)}
>
<MapView.Circle
center = { this.state.region }
radius = { this.state.radius }
strokeWidth = { 1 }
strokeColor = { '#1a66ff' }
fillColor = { 'rgba(230, 238, 255, 0.5)' }
onRegionChangeComplete = {(region) => {this.regionChange({region});console.log(region)}}
/>
<Marker
coordinate = { this.state.region }
title = {"Marker"}
description = {"Marker description"}
/>
</MapView>
<Slider
step={50}
maximumValue={2000}
onValueChange={(value) => this.changeRadius(value)}
value={this.state.radius}
style={{marginTop:10}}
/>
<Autocomplete regionChange={(reg) => this.regionChange(reg)} />
</View>
)
}
}
Objects are not primitive data types. So, they are passed by value by reference. In your code when you did let reg = this.state.region you have made a reference to this.state.region. So, when you mutate reg, you mutate state directly which is a big no in react.
I suggest you to use spread operator to make a new copy of state.
let reg = { ...state, {} }

react native mapbox dynamically added PointAnnotations are misplaced

I currently developing a react native app ( version 0.55.2) and mapbox/react-native (version 6.1.2-beta2)
I have a situation where some annotations are shown initially on map render, then further annotations are loaded when the user's zooms.
The first annotations are displayed at the right place.
However, when new annotations are added, there are all stuck at the top left corner.
Following their documentation, https://github.com/mapbox/react-native-mapbox-gl/blob/master/docs/MapView.md, I tried to call the function when the map is loaded or rendered. I even tried a setTimeout. The annotations always appears at the topleft map.
Any ideas how should I approach this?
THanks!
class map extends React.Component {
constructor(props) {
super(props);
this.getMapVisibleBounds = getMapVisibleBounds.bind(this);
this.state = {
...INIT_MAP_STATE
};
}
//compo lifecyle
componentDidUpdate(prevProps, prevState) {
if (this.state.userPosition.longitude !== prevState.userPosition.longitude) {
this.setBounds();//first annotations. works fine
}
if (this.state.zoomLevel !== prevState.zoomLevel) {
this.setBounds(); //update annotations. doesn't work
}
}
render()=>{
const { quest, checkpoint } = this.props;
const { selectedIndex } = this.state;
return (
<View style={styles.container}>
<Mapbox.MapView
styleURL={MAP_STYLE}
zoomLevel={this.state.zoomLevel}
centerCoordinate={[this.state.userPosition.longitude,
this.state.userPosition.latitude]}
style={styles.mapWrap}
>
{this.renderMap(checkpoint, "checkpoint")}
</Mapbox.MapView>
</View>
);
}
setBounds = () => {
this.getMapVisibleBounds(this.map)
.catch(err => {
console.error(err);
})
.then(bounds => {
this._setMapBounds(bounds);// set state bounds
return this.props.onLoadQuest(bounds); //api call
});
}
}
// annotations rendering
class checkPoint extends Component {
constructor(props) {
super(props);
}
renderAnnotations = (data, id) => {
const uniqKey = `checkpoint_${id}`;
return (
<Mapbox.PointAnnotation key={uniqKey} id={uniqKey} coordinate={[data[0], data[1]]}>
<TouchableWithoutFeedback onPress={idx => this.onSelect(id)}>
<Image source={checkPointImg} style={styles.selfAvatar} resizeMode="contain" />
</TouchableWithoutFeedback>
</Mapbox.PointAnnotation>
);
};
render() {
if (!this.props.checkpoint || isEmpty(this.props.checkpoint)) {
return null;
}
const { hits } = this.props.checkpoint;
if (!Array.isArray(hits)) {
return [];
}
return hits.map((c, idx) =>
this.renderAnnotations(c._source.location.coordinates, c._source.id)
);
}
}
"PointAnnotation" is legacy, try passing your points to as an object. You're map render will be so much faster once you make the swap. Something like this.
<MapboxGL.MapView
centerCoordinate={[ userLocation.longitude, userLocation.latitude ]}
pitchEnabled={false}
rotateEnabled={false}
style={{ flex: 1 }}
showUserLocation={true}
styleURL={'your_style_url'}
userTrackingMode={MapboxGL.UserTrackingModes.MGLUserTrackingModeFollow}
zoomLevel={10}
>
<MapboxGL.ShapeSource
key='icon'
id='icon'
onPress={this._onMarkerPress}
shape={{type: "FeatureCollection", features: featuresObject }}
type='geojson'
images={images}
>
<MapboxGL.SymbolLayer
id='icon'
style={layerStyles.icon}
/>
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
Where "featuresObject" looks something like this...
let featuresObject = []
annotation.forEach((annot, index) => {
let lat = annot.latitude
let lng = annot.longitude
featuresObject[index] = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [lng, lat]
},
properties: {
exampleProperty: propertyValue,
}
}
})
Example for polygon layer
Example with custom icon
You can add markers dynamically by using this code:
Create marker component:
const Marker = ({ coordinate, image, id }) => {
return (
<MapboxGL.MarkerView coordinate={coordinate} id={id}>
// Add any image or icon or view for marker
<Image
source={{ uri: image }}
style={{width: '100%', height: '100%'}}
resizeMode="contain"
/>
</MapboxGL.MarkerView>
);
};
Consume it inside MapBoxGL:
<MapboxGL.MapView
style={{
// it will help you keep markers inside mapview
overflow: 'hidden'
}}>
{markers &&
markers?.length > 0 &&
markers.map((marker, index) => (
<Marker
coordinate={[marker.longitude, marker.latitude]}
// id must be a string
id={`index + 1`}
image={getIconUrl(index)}
/>
))
}
</MapboxGL.MapView>
const layerStyles = Mapbox.StyleSheet.create({
icon: {
iconImage: "{icon}",
iconAllowOverlap: true,
iconSize: 0.5,
iconIgnorePlacement: true
}
});
const mapboxIcon = props => {
return (
<Mapbox.ShapeSource
shape={makeMapBoxGeoJson(props.datum, props.mapKey, props.name)}
key={`${props.name}_key_${props.mapKey}`}
id={`${props.name}_${props.mapKey}`}
images={getIcon(props.name)}
onPress={idx => (props.isActive ? props.onSelectId(props.mapKey) : null)}
>
<Mapbox.SymbolLayer
id={`${props.mapKey}_pointlayer`}
style={[layerStyles.icon, { iconSize: props.iconSize ? props.iconSize : 0.5 }]}
/>
</Mapbox.ShapeSource>
);
};