I have a question about react-native MapViewDirection with the expo.
I have my result received from the onReady function inside MapViewDirection.
I want to display the distance and duration field to the user.
onReady={result => {
console.log(result)
}};
I want to declare I am learning and am using someone else's code to practice. Below is full code:
import React, { Component } from 'react';
import { Dimensions, StyleSheet } from 'react-native';
import MapView from 'react-native-maps';
import MapViewDirections from 'react-native-maps-directions';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 40.3788;
const LONGITUDE = -105.5143;
const LATITUDE_DELTA = 0.0192;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const GOOGLE_MAPS_APIKEY = 'AIzaSyAkwAlX2w0S3ba6OxBqr13JGDuYIi5GRZ8';
class Example extends Component {
constructor(props) {
super(props);
this.state = {
coordinates: [
{
latitude:40.3788,
longitude:-105.5143,
},
],
};
this.mapView = null;
}
onMapPress = (e) => {
this.setState({
coordinates: [
...this.state.coordinates,
e.nativeEvent.coordinate,
],
});
}
render() {
return (
<MapView
initialRegion={{
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
style={StyleSheet.absoluteFill}
ref={c => this.mapView = c}
onPress={this.onMapPress}
>
{this.state.coordinates.map((coordinate, index) =>
<MapView.Marker key={`coordinate_${index}`} coordinate={coordinate} />
)}
{(this.state.coordinates.length >= 2) && (
<MapViewDirections
origin={this.state.coordinates[0]}
waypoints={ (this.state.coordinates.length > 2) ? this.state.coordinates.slice(1, -1): null}
destination={this.state.coordinates[this.state.coordinates.length-1]}
apikey={GOOGLE_MAPS_APIKEY}
strokeWidth={3}
strokeColor="hotpink"
optimizeWaypoints={true}
onStart={(params) => {
console.log(`Started routing between "${params.origin}" and "${params.destination}"`);
}}
onReady={result => {
console.log(result);
this.mapView.fitToCoordinates(result.coordinates, {
edgePadding: {
right: (width / 20),
bottom: (height / 20),
left: (width / 20),
top: (height / 20),
}
});
}}
onError={(errorMessage) => {
// console.log('GOT AN ERROR');
}}
/>
)}
</MapView>
);
}
}
export default Example;
Using the below given code you can get the distance between two locations using there latitude & longitude. We are using google distance matrix api here to get distance.
getDistanceOneToOne(lat1, lng1, lat2, lng2)
{
const Location1Str = lat1 + "," + lng1;
const Location2Str = lat2 + "," + lng2;
let GOOGLE_API_KEY = "API_KEY"
let ApiURL = "https://maps.googleapis.com/maps/api/distancematrix/json?"
let params = `origins=${Location1Str}&destinations=${Location2Str}&key=${GOOGLE_API_KEY}`; // you need to get a key
let finalApiURL = `${ApiURL}${encodeURI(params)}`;
this.getDistance(finalApiURL).then((getDistance) => {
console.log('Distance charges',getDistance);
if (getDistance.status == "OK")
{
let value = getDistance.rows[0].elements[0].distance.text; //Distance
let time = getDistance.rows[0].elements[0].duration.value; //time
}
});
}
getDistance = (url) => {
return this.getDistanceCall(url)
}
getDistanceCall = (url) => {
return fetch(url, {
method: 'GET',
})
.then((response) => { return response.json(); })
.catch((error) => {
return error;
});
}
Related
I'm building an application using react-leaflet and I recently updated all of my dependencies which brought on an error that I can't solve.
The error:
React has detected a change in the order of Hooks called by ForwardRef(ContainerComponent)
Removing the <MapContainer> component from my app fixes the error, but I cannot seem to figure out where the ForwardRef component is being rendered or why the order of hooks changes within it between renders.
This is my component:
const Map = ({ openModal }) => {
const [homeCoords, setHomeCoords] = useState([49.2, -123]);
const [bounds, setBounds] = useState({
lat_lower: 48.9,
lat_upper: 49.5,
lon_left: -123.8,
lon_right: -122.2
});
// Get the user's location with navigator.geolocation
useEffect(() => {
if(!window.navigator.geolocation) {
return;
}
window.navigator.geolocation.getCurrentPosition(
// success
(res) => {
setHomeCoords([res.coords.latitude, res.coords.longitude]);
},
// failure
() => {
console.error('Must allow pestlocations.com to access your location to use this feature.')
}
)
}, []);
// Helper function for BoundTracker component
const collectBounds = (e) => {
const bounds = e.target.getBounds();
const currLatLower = bounds._southWest.lat;
const currLatUpper = bounds._northEast.lat;
const currLonLeft = bounds._southWest.lng;
const currLonRight = bounds._northEast.lng;
setBounds({
lat_lower: currLatLower,
lat_upper: currLatUpper,
lon_left: currLonLeft,
lon_right: currLonRight
})
}
// Listen for dragging or zooming on map and update bounds
const BoundTracker = () => {
useMapEvents({
// Drag map
dragend: (e) => {
collectBounds(e);
},
// Zoom map
zoomend: (e) => {
collectBounds(e);
}
})
}
const HomeButton = () => {
const map = useMap();
return (
<div className="btn home" aria-disabled="false" onClick={() => {
map.panTo(homeCoords);
const bounds = map.getBounds();
setBounds({
lat_lower: bounds._southWest.lat,
lat_upper: bounds._northEast.lat,
lon_left: bounds._southWest.lng,
lon_right: bounds._northEast.lng
})
}}>
<AiFillHome />
</div>
)
}
return (
<>
<MapContainer className="Map" position={homeCoords} zoom={10}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<HomeButton />
<div className="btn info" onClick={() => openModal("Welcome")}><AiOutlineInfoCircle /></div>
<div className="btn legend" onClick={() => openModal("Legend")}><BsMap /></div>
<div className="search-btn" onClick={() => return}>Search This Area</div>
<PointClusters />
<BoundTracker />
</MapContainer>
</>
)
}
EDIT
Here is my PointClusters component:
import { useState } from 'react';
import MarkerClusterGroup from 'react-leaflet-cluster';
import { Marker, Popup } from 'react-leaflet';
import L from 'leaflet';
import './PointClusters.css';
// For testing purposes only - - - - - - - - - - - - - - - - - -
const testPoints = require('../../../testPoints.json').features;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PointClusters = () => {
// Initialize points as testpoints. ** CHANGE FOR PRODUCTION **
const [points, setPoints] = useState(testPoints);
const newicon = new L.divIcon({
className: "custom-marker",
html: "<span class='arrow'></span>"
});
const createClusterCustomIcon = function (cluster) {
return L.divIcon({
html: `<span>${cluster.getChildCount()}</span>`,
className: 'custom-marker-cluster',
iconSize: L.point(33, 33, true),
})
}
return (
<MarkerClusterGroup
iconCreateFunction={createClusterCustomIcon}
>
{
points.map((point, i) => {
const { name, address, type, numOfReports, url } = point.properties;
const coords = [point.geometry.coordinates[1], point.geometry.coordinates[0]];
return (
<Marker position={coords} icon={newicon} key={i}>
<Popup>
{
name === "None" ? null :
<>
<b>{name}</b>
<br />
</>
}
<strong>Address</strong>: {address}
<br />
<strong>Type</strong>: {type}
<hr />
At least <a href={url}>{numOfReports} reports</a> on bedbugregistry.com
</Popup>
</Marker>
)
})
}
</MarkerClusterGroup>
)
}
Since you only have the useState hook in PointClusters I assume the issue here is react-leaflet-cluster package. I know it did not have support för react-leaflet 4 when I wanted to use it. It now have a version 2.0.0 that should be compatible, however looking into the code they use hooks in the solution.
Since they did not support react-leaflet 4 when I needed it I decided to adapt the actual code and modify to work and to fit my needs. Below is that adaption:
import { createPathComponent } from "#react-leaflet/core";
import L, { LeafletMouseEventHandlerFn } from "leaflet";
import "leaflet.markercluster";
import { ReactElement, useMemo } from "react";
import { Building, BuildingStore, Circle } from "tabler-icons-react";
import { createLeafletIcon } from "./utils";
import styles from "./LeafletMarkerCluster.module.css";
import "leaflet.markercluster/dist/MarkerCluster.css";
type ClusterType = { [key in string]: any };
type ClusterEvents = {
onClick?: LeafletMouseEventHandlerFn;
onDblClick?: LeafletMouseEventHandlerFn;
onMouseDown?: LeafletMouseEventHandlerFn;
onMouseUp?: LeafletMouseEventHandlerFn;
onMouseOver?: LeafletMouseEventHandlerFn;
onMouseOut?: LeafletMouseEventHandlerFn;
onContextMenu?: LeafletMouseEventHandlerFn;
};
// Leaflet is badly typed, if more props needed add them to the interface.
// Look in this file to see what is available.
// node_modules/#types/leaflet.markercluster/index.d.ts
// MarkerClusterGroupOptions
export interface LeafletMarkerClusterProps {
spiderfyOnMaxZoom?: boolean;
children: React.ReactNode;
size?: number;
icon?: ReactElement;
}
const createMarkerCluster = (
{
children: _c,
size = 30,
icon = <Circle size={size} />,
...props
}: LeafletMarkerClusterProps,
context: any
) => {
const markerIcons = {
default: <Circle size={size} />,
property: <Building size={size} />,
business: <BuildingStore size={size} />,
} as { [key in string]: ReactElement };
const clusterProps: ClusterType = {
iconCreateFunction: (cluster: any) => {
const markers = cluster.getAllChildMarkers();
const types = markers.reduce(
(
acc: { [x: string]: number },
marker: {
key: string;
options: { icon: { options: { className: string } } };
}
) => {
const key = marker?.key || "";
const type =
marker.options.icon.options.className || key.split("-")[0];
const increment = (key.split("-")[1] as unknown as number) || 1;
if (type in markerIcons) {
return { ...acc, [type]: (acc[type] || 0) + increment };
}
return { ...acc, default: (acc.default || 0) + increment };
},
{}
) as { [key in string]: number };
const typeIcons = Object.entries(types).map(([type, count], index) => {
if (count > 0) {
const typeIcon = markerIcons[type];
return (
<div key={`${type}-${count}`} style={{ display: "flex" }}>
<span>{typeIcon}</span>
<span style={{ width: "max-content" }}>{count}</span>
</div>
);
}
});
const iconWidth = typeIcons.length * size;
return createLeafletIcon(
<div style={{ display: "flex" }} className={"cluster-marker"}>
{typeIcons}
</div>,
iconWidth,
undefined,
iconWidth,
30
);
},
showCoverageOnHover: false,
animate: true,
animateAddingMarkers: false,
removeOutsideVisibleBounds: false,
};
const clusterEvents: ClusterType = {};
// Splitting props and events to different objects
Object.entries(props).forEach(([propName, prop]) =>
propName.startsWith("on")
? (clusterEvents[propName] = prop)
: (clusterProps[propName] = prop)
);
const instance = new (L as any).MarkerClusterGroup(clusterProps);
instance.on("spiderfied", (e: any) => {
e.cluster._icon?.classList.add(styles.spiderfied);
});
instance.on("unspiderfied", (e: any) => {
e.cluster._icon?.classList.remove(styles.spiderfied);
});
// This is not used at the moment, but could be used to add events to the cluster.
// Initializing event listeners
Object.entries(clusterEvents).forEach(([eventAsProp, callback]) => {
const clusterEvent = `cluster${eventAsProp.substring(2).toLowerCase()}`;
instance.on(clusterEvent, callback);
});
return {
instance,
context: {
...context,
layerContainer: instance,
},
};
};
const updateMarkerCluster = (instance: any, props: any, prevProps: any) => {};
const LeafletMarkerCluster = createPathComponent(
createMarkerCluster,
updateMarkerCluster
);
const LeafletMarkerClusterWrapper: React.FC<LeafletMarkerClusterProps> = ({
children,
...props
}) => {
const markerCluster = useMemo(() => {
return <LeafletMarkerCluster>{children}</LeafletMarkerCluster>;
}, [children]);
return <>{markerCluster}</>;
};
export default LeafletMarkerClusterWrapper;
I combine different types of markers and show each icon with the number in the cluster. You should be able to replace iconCreateFunction to fit your needs.
The createLeafletIcon look like this:
import { divIcon } from "leaflet";
import { ReactElement } from "react";
import { renderToString } from "react-dom/server";
export const createLeafletIcon = (
icon: ReactElement,
size: number,
className?: string,
width: number = size,
height: number = size
) => {
return divIcon({
html: renderToString(icon),
iconSize: [width, height],
iconAnchor: [width / 2, height],
popupAnchor: [0, -height],
className: className ? className : "",
});
};
Another tip is to looking into using useMemo on props, that the system might otherwise see as "new", but the first order of action should make it work, then you can try to find props that cause rerenders. Best of luck and let me know if you have any questions regarding the implementation
I want to switch back and forth between two React components as shown below.
Every time the Photo Component is invoked and the user has taken a picture. I want the Game component to mount again (thus passing condition counter state variable as props, to be changed) before the photo component unmounts.
React is yelling at me for Warning: Can't perform a React state update on an unmounted component.
I understand the problem that my Photo component unmounts first before the state has updated.
I have looked into several similar questions and try to adopt their solutions e.g. having an additional variable to check if the component is mounted or not and few others.
Seems like none helped me.
Game.js
import React, { useState, useEffect } from 'react';
import { TextInput, Platform, Text, View, StyleSheet, Button, Alert } from 'react-native';
import Constants from 'expo-constants';
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';
import MapView, { Marker, Circle, Polyline } from 'react-native-maps';
import Header from '../Components/Header';
import Footer from '../Components/Footer';
import PhotoHandler from './Photo';
import * as geolib from 'geolib';
import { event } from 'react-native-reanimated';
export default function GameArea() {
const [isFetching, setIsFetching] = useState(false);
const [generateTargets, setGenerateTargets] = useState(true);
const [targets, setTargets] = useState([]);
const [distances, setDistances] = useState([]);
const [index, setIndex] = useState(null);
const [gesture, setGesture] = useState({});
const [draggingMap, setdraggingMap] = useState(false);
const [numofTargets, setNumofTargets] = useState(null);
const [conditionCounter, setConditionCounter] = useState(false);
const [photoTaken, setPhotoTaken] = useState(false);
const [pickedLocation, setPickedLocation] = useState({
latitude: 123,
longitude: 123
});
const [errorMsg, setErrorMsg] = useState(null);
const [region, setRegion] = useState({
latitude: 123,
longitude: 123,
latitudeDelta: 0.001,
longitudeDelta: 0.001
});
const [mapBounds, setMapBounds] = useState({
n: null,
s: null,
e: null,
w: null
});
const [mapRef, updateMapRef] = useState(null);
useEffect(() => {
const verifyPermissions = async () => {
const result = await Permissions.askAsync(Permissions.LOCATION);
if (result.status !== 'granted') {
Alert.alert(
'Insufficient permissions!',
'You need to grant location permissions to use this app.',
[{ text: 'Okay' }]
);
return false;
}
return true;
};
(async () => {
const hasPermission = await verifyPermissions();
if (!hasPermission) {
return;
}
try {
setIsFetching(true);
const location = await Location.getCurrentPositionAsync({
});
setPickedLocation({
latitude: location.coords.latitude,
longitude: location.coords.longitude
});
setRegion({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
latitudeDelta: 0.003,
longitudeDelta: 0.003
});
} catch (err) {
Alert.alert(
'Could not fetch location!',
'Please try again.',
[{ text: 'Okay' }]
);
}
setIsFetching(false);
})();
}, []);
const getBoundaries = () => {
if (mapRef === null) {
return;
}
mapRef
.getMapBoundaries()
.then((res) => {
setMapBounds({
n: res.northEast.latitude,
e: res.northEast.longitude,
s: res.southWest.latitude,
w: res.southWest.longitude
});
})
.catch((err) => console.log(err));
};
const onRegionChangeComplete = (region) => {
if (draggingMap === true && targets.length == 0) {
getBoundaries();
setRegion({
latitude: region.latitude,
longitude: region.longitude,
latitudeDelta: region.latitudeDelta,
longitudeDelta: region.longitudeDelta
})
setdraggingMap(false);
}
else {
return;
}
}
function handleChange(newValue) {
setConditionCounter(newValue);
}
const ondraggingMap = () => {
setdraggingMap(true);
}
async function generatedTargets() {
var url = `https://xxx.yy.zz`;
if (numofTargets == null) {
Alert.alert(
"Missing",
"Enter Number of targets first",
[
{ text: "OK" }
]
);
}
else {
try {
let response = await fetch(
url,
);
let responseJson = await response.json();
console.log("Targets generated");
console.log(responseJson);
setTargets(responseJson);
setGenerateTargets(false);
} catch (error) {
Alert.alert(
"Failed: Network Error",
"Try again",
[
{ text: "OK" }
]
);
console.error(error);
}
}
}
useEffect(() => {
console.log('inside useEffect hook for measuring targets distances');
var lat1 = pickedLocation.latitude;
var lng1 = pickedLocation.longitude;
console.log("targets distance");
const R = 6371;
let targets_distance = [];
for (i = 0; i < targets.length; i++) {
const φ1 = lat1 * Math.PI / 180;
const φ2 = targets[i].lat * Math.PI / 180;
const Δφ = (targets[i].lat - lat1) * Math.PI / 180;
const Δλ = (targets[i].lng - lng1) * Math.PI / 180;
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c * 1000;
console.log('distance is'+ distance);
if(distance <=20){
setConditionCounter(true); //switch to photo component
targets.splice(i, 1);
console.log("targets after removal");
console.log(targets);
}
else{
targets_distance.push(distance);
}
}
console.log(targets_distance);
var index = 0;
var value = targets_distance[0];
for (var i = 1; i < targets_distance.length; i++) {
if (targets_distance[i] < value) {
value = targets_distance[i];
index = i;
}
}
console.log("smallest element is " + value + " at index " + index);
setIndex(index);
setDistances(targets_distance);
}, [pickedLocation]);
useEffect(() => {
console.log("Inside UseEffect watch position");
(async () => {
try {
await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
distanceInterval: 10,
timeInterval: 3000,
},
(loc) => {
setPickedLocation({
latitude: loc.coords.latitude,
longitude: loc.coords.longitude
});
}
);
} catch (e) {
Alert.alert("Error");
}
})();
}, []);
const setNumberOfTargets = (numofTargets) =>{
setNumofTargets(numofTargets);
}
return (
<View style={styles.container}>
<Header title="Crowdsourcing" />
{conditionCounter?
<PhotoHandler conditionCounter={setConditionCounter} onChange={handleChange}/>:
<MapView style={styles.mapContainer}
region={{
latitude: region.latitude,
longitude: region.longitude,
latitudeDelta: region.latitudeDelta,
longitudeDelta: region.longitudeDelta
}}
showsUserLocation={true}
followUserLocation={true}
ref={(ref) => updateMapRef(ref)}
onPanDrag={ondraggingMap}
onRegionChangeComplete={onRegionChangeComplete}
>
{
targets.map((target, index) => (
<MapView.Marker
key={index}
coordinate={{
latitude: target.lat,
longitude: target.lng
}}
title={target.name}
/>
))}
</MapView>}
<Footer
Targets = {generateTargets}
numberOfTargets = {numofTargets}
setNumTargets = {setNumberOfTargets}
osmTargets = {generatedTargets}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%',
height: '100%'
},
mapContainer: {
width: '100%',
height: 500,
marginBottom: 20
},
footer: {
width: '100%',
height: 70,
backgroundColor: '#FF7F50',
alignItems: 'center',
justifyContent: 'center'
},
buttonContainer: {
flexDirection: 'row',
width: '100%',
justifyContent: 'space-evenly'
}
});
Photo.js
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Alert, Button, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from 'expo-permissions';
import * as Location from 'expo-location';
export default function PhotoHandler(props) {
const [pickedLocation, setPickedLocation] = useState({
latitude: null,
longitude: null
});
const [pickedImage, setPickedImage] = useState(null);
const verifyPermissions = async () =>{
const result = await Permissions.askAsync(Permissions.CAMERA_ROLL);
if(result.status != 'granted'){
Alert.alert(
'Insufficient Permissions!',
'You need to grant camera permissions to use this app',
[{text: 'Okay'}]
);
return false;
}
return true;
};
const imagetakenHandler = async () =>{
const hasPermission = await verifyPermissions();
if(!hasPermission){
return;
}
const image = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 1,
aspect: [1, 1],
base64: true,
exif: true
});
setPickedImage(image.base64);
};
useEffect(() => {
const requestOptions = {
method: 'POST',
headers: 'Content-type: application/x-www-form-urlencoded',
};
if(pickedImage != null){
props.onChange(false); // to switch back to Game component
let response = fetch(`https://xxx.yy.zz`, requestOptions)
.then(response => response.json())
.then(console.log('SUCCESS: '+response))
.catch(e => {
console.log(e);
});
}
}, [pickedImage])
return (
<View style={styles.container}>
{
Alert.alert(
"Success",
"Take photo",
[
{text: "Open Camera",
onPress: imagetakenHandler
}
]
)
}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 30
}
});
I'm trying to add the transform property to my create component. Then inside the transform, I call a function to get the lat and long based on the address parameters.
But before the geodecoder is done the dataprovider is already sending data to the API.
Is there anyway so that I can let the transform know the data is al set to be send?
Also kinda new to React. So, maybe I'm missing something obvious. Here's my code so far:
import * as React from "react";
import {Create, ReferenceArrayInput, SelectArrayInput, SimpleForm, TextInput} from 'react-admin';
import Geocode from "react-geocode";
Geocode.setApiKey("MY_API_KEY");
Geocode.setRegion("nl");
export const StoreCreate = props => {
function getLatLong(data) {
const addressObject = data.address;
if ((addressObject.postal_code.length >= 6 &&
addressObject.postal_code.length <= 7) &&
addressObject.street_name.length > 0 &&
addressObject.housenumber.length > 0
) {
const regex = /^[1-9][0-9]{3}[\s]?[A-Za-z]{2}$/i;
if (regex.test(addressObject.postal_code)) {
const address = addressObject.street_name + ' ' + addressObject.housenumber + ' ' + addressObject.postal_code;
Geocode.fromAddress(address).then(
response => {
const {lat, lng} = response.results[0].geometry.location;
console.log({
address: {
...addressObject, ...{
lat: lat,
lng: lng
}
}
});
return {
address: {
...addressObject, ...{
lat: lat,
lng: lng
}
}
};
},
error => {
console.error(error);
}
);
}
}
}
const transform = (data) => ({
...data,
...getLatLong(data)
});
return (
<Create {...props} transform={transform}>
<SimpleForm>
<TextInput source="name"/>
<TextInput source="email" type="email"/>
<TextInput source="address.street_name"/>
<TextInput source="address.housenumber"/>
<TextInput source="address.postal_code"/>
<TextInput source="address.city"/>
<ReferenceArrayInput source="store_types" reference="store-types">
<SelectArrayInput source="name"/>
</ReferenceArrayInput>
<TextInput source="address.lat"/>
<TextInput source="address.lng"/>
</SimpleForm>
</Create>
);
};
First, the getLatLong function does not return the promise.
function getLatLong(data) {
const addressObject = data.address;
if ((addressObject.postal_code.length >= 6 &&
addressObject.postal_code.length <= 7) &&
addressObject.street_name.length > 0 &&
addressObject.housenumber.length > 0
) {
const regex = /^[1-9][0-9]{3}[\s]?[A-Za-z]{2}$/i;
if (regex.test(addressObject.postal_code)) {
const address = addressObject.street_name + ' ' + addressObject.housenumber + ' ' + addressObject.postal_code;
return Geocode.fromAddress(address).then(
response => {
const {lat, lng} = response.results[0].geometry.location;
console.log({
address: {
...addressObject, ...{
lat: lat,
lng: lng
}
}
});
return {
address: {
...addressObject, ...{
lat: lat,
lng: lng
}
}
};
},
error => {
console.error(error);
throw error;
}
);
}
}
return {};
}
Then, you should use it correctly in your tranform function:
const transform = (data) => getLatLong(data).then((longData) => ({
...data,
...longData
}));
When I use Polyline it gives me this error:
attempt to invoke interface method 'java.util.iterator java.util.list.iterator()' on a null object
At first, I thought I made something wrong or I mis-used google-map API so I tried with some hard-coded coords (as shown in my code below) but without any progress.
The code will run fine if I remove the Polyline part.
I googled hoping to find any solution but without any success also.
some info my about dev platform:
expo version 2.19.1
yarn version 1.16.0
Node version 10.15.2
Testing on a physical device with android PI installed.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Permissions, MapView } from 'expo'
// import Polyline from '#mapbox/polyline'
import { Polyline } from 'react-native-maps'
const locations = require('./locations.json')
export default class App extends React.Component {
state = {
latitude: null,
longitude: null,
locations: locations
}
componentDidMount() {
this.GetLocations()
}
GetLocations = async () => {
const { status } = await Permissions.getAsync(Permissions.LOCATION)
if (status !== 'granted') {
const response = await Permissions.askAsync(Permissions.LOCATION)
}
navigator.geolocation.getCurrentPosition(
({ coords: { latitude, longitude }}) => this.setState({ latitude, longitude}, this.mergeCoords),
(err) => console.log(`Error: ${err}`)
)
const { locations: [sampleLocation] } = this.state
this.setState({
desLatitude: sampleLocation.coords.latitude,
desLongitude: sampleLocation.coords.longitude,
}, this.mergeCoords)
}
mergeCoords = () => {
const { latitude, longitude, desLatitude, desLongitude } = this.state
const hasStartAndEnd = ( latitude !== null && desLatitude !== null )
// if the line have start and end
if (hasStartAndEnd) {
const concatStart = `${latitude},${longitude}`
const concatEnd = `${desLatitude},${desLongitude}`
this.getDirections(concatStart, concatEnd)
}
}
async getDirections(startLoc, desLoc) {
try {
// const res = await fetch(`https://maps.googleapis.com/maps/api/directions/json?key=MY_API_KEY&origin=${startLoc}&destination=${desLoc}`)
// const resJson = await res.json()
// const points = Polyline.decode(resJson.routes[0].overview_polyline.points)
// const coords = points.map( point => {
// return {
// latitude: point[0],
// longitude: point[1]
// }
// })
const coords = {
latitude: 31.262353,
longitude: 29.989506,
}
console.log("point, coords: ", coord)
this.setState({coords})
} catch(err) {
console.log('Error: ', err)
}
}
render() {
const { latitude, longitude, coords } = this.state
if (latitude) {
return (
<MapView
style={{ flex: 1 }}
showsUserLocation
initialRegion={{
latitude,
longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
>
<MapView.Polyline
strokeWidth={2}
strokeColor="red"
coordinates={coords}
/>
</MapView>
)
}
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>We need your permissions !!</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
//use condional rendering
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Permissions, MapView } from 'expo'
// import Polyline from '#mapbox/polyline'
import { Polyline } from 'react-native-maps'
const locations = require('./locations.json')
export default class App extends React.Component {
state = {
latitude: null,
longitude: null,
locations: locations,
direct : false
}
componentDidMount() {
this.GetLocations()
}
GetLocations = async () => {
const { status } = await Permissions.getAsync(Permissions.LOCATION)
if (status !== 'granted') {
const response = await Permissions.askAsync(Permissions.LOCATION)
}
navigator.geolocation.getCurrentPosition(
({ coords: { latitude, longitude }}) => this.setState({ latitude, longitude},
this.mergeCoords),
(err) => console.log(`Error: ${err}`)
)
const { locations: [sampleLocation] } = this.state
this.setState({
desLatitude: sampleLocation.coords.latitude,
desLongitude: sampleLocation.coords.longitude,
}, this.mergeCoords)
}
mergeCoords = () => {
const { latitude, longitude, desLatitude, desLongitude } = this.state
const hasStartAndEnd = ( latitude !== null && desLatitude !== null )
// if the line have start and end
if (hasStartAndEnd) {
const concatStart = `${latitude},${longitude}`
const concatEnd = `${desLatitude},${desLongitude}`
this.getDirections(concatStart, concatEnd)
}
}
async getDirections(startLoc, desLoc) {
try {
// const res = await fetch(`https://maps.googleapis.com/maps/api/directions/json?
key=MY_API_KEY&origin=${startLoc}&destination=${desLoc}`)
// const resJson = await res.json()
// const points = Polyline.decode(resJson.routes[0].overview_polyline.points)
// const coords = points.map( point => {
// return {
// latitude: point[0],
// longitude: point[1]
// }
// })
const coords = {
latitude: 31.262353,
longitude: 29.989506,
}
console.log("point, coords: ", coord)
this.setState({coords, direct:true})
} catch(err) {
console.log('Error: ', err)
}
}
render() {
const { latitude, longitude, coords } = this.state
if (latitude) {
return (
<MapView
style={{ flex: 1 }}
showsUserLocation
initialRegion={{
latitude,
longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
>
{this.state.direct &&
<MapView.Polyline
strokeWidth={2}
strokeColor="red"
coordinates={coords}
/>}
</MapView>
)
}
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>We need your permissions !!</Text>
</View>
)
}
}
I have a screen that loads and places markers on a map based on a search form in the previous screen. I want the map window to also centre itself in the middle of all the markers. So when I use initialRegion, I set the latitude and longitude to state values whose states are set after fetching JSON from a URL. The lat and long are set to values at the centre of the markers. I want the map window to go to these coordinates, but instead, I get an error when the screen loads.
Here is the code:
import React, { Component } from 'react';
import { View, Text, AsyncStorage, Alert, FlatList, StyleSheet } from 'react-native';
import { PrimaryButton } from '../Buttons';
import styles from './styles';
import { ListItem } from '../ListItem';
import MapView, { Marker } from 'react-native-maps';
class RestOptions extends Component {
constructor() {
super();
this.state = {
jsonResults: [],
userPlaces: [],
lat_center: null,
lng_center: null
}
}
renderItem = ({ item }) => {
return (
<View>
<Text>{item.rest_name}</Text>
<Text>{item.counter}</Text>
<Text>Distance: {item.distance} Miles</Text>
<PrimaryButton
label="Set Reservation"
onPress={() => this.setReservation(item.rest_id)}
/>
</View>
)
}
componentDidMount() {
this.getSearchResults();
}
getSearchResults() {
fetch('fetch url here')
.then((response) => response.json())
.then((responseJson) => {
var placesArray = [];
var latArray = [];
var lngArray = [];
for (key = 0; key < responseJson.rest_array.length; key = key + 1) {
var lati_str = responseJson.rest_array[key].lat;
var long_str = responseJson.rest_array[key].lng;
var count_str = responseJson.rest_array[key].counter;
var lati = parseFloat(lati_str);
var long = parseFloat(long_str);
var count = parseFloat(count_str);
latArray.push(lati);
lngArray.push(long);
placesArray.push ({
coordinates: {
latitude: lati,
longitude: long
},
id: count
});
}
var max_lat = Math.max.apply(null, latArray);
var min_lat = Math.min.apply(null, latArray);
var max_lng = Math.max.apply(null, lngArray);
var min_lng = Math.min.apply(null, lngArray);
var latCenter = (max_lat + min_lat) / 2;
var lngCenter = (max_lng + min_lng) / 2;
this.setState({lat_center: latCenter}); //setting latitude state here
this.setState({lng_center: lngCenter}); //setting longitude state here
this.setState({userPlaces: placesArray});
this.setState({jsonResults: responseJson.rest_array});
}).catch((error) => {
console.error(error);
});
}
setReservation(rest_id) {
Alert.alert(rest_id);
//this.props.navigation.navigate('SetReservation');
}
render() {
return (
<View>
<View style={mapStyles.mapContainer}>
<MapView
style={mapStyles.map}
initialRegion={{
latitude: this.state.lat_center, //using latitude state here
longitude: this.state.lng_center, //using longitude state here
latitudeDelta: 0.1022,
longitudeDelta: 0.0821
}}
>
{this.state.userPlaces.map(userPlace => (
<MapView.Marker
coordinate={userPlace.coordinates}
key={userPlace.id}
/>
))}
</MapView>
</View>
<FlatList
data={this.state.jsonResults}
renderItem={this.renderItem}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
};
const mapStyles = StyleSheet.create({
mapContainer: {
width: '100%',
height: 200,
},
map: {
width: '100%',
height: '100%',
},
});
export default RestOptions;
I get this error:
And this warning:
I have already verified that the lat_center and lng_center successfully change state to the appropriate coordinates.
It’s probably because your initial values for the lat_center and lng_center are null in your state object in the constructor.
compondentDidMount gets called after the initial render.
https://reactjs.org/docs/react-component.html#mounting
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
This means that for a moment in time the values of your latitude and longitude will be null leading to these errors. You either need to set an initial value that is not null or not render your map until the coordinates have need set.
Also those 4 setState calls could be reduced to one, in something like this.
this.setState({
lat_center: latCenter,
lng_center: lngCenter,
userPlaces: placesArray,
jsonResults: responseJson.rest_array
});