I'm using react-native-maps. I'm showing direction from source to destination using polyline. But it's not aligned to centre of screen. Zooming is also not proper.
How can I zoom map properly to show direction in the centre of screen properly.
This is my code:
const ASPECT_RATIO = width / height;
const LATITUDE = 40.7128;
const LONGITUDE = 74.0060;
const LATITUDE_DELTA = 0.0012;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
this.state = {
region: {
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}
}
<MapView
provider={PROVIDER_GOOGLE}
style={StyleSheet.absoluteFillObject}
region={this.state.region}>
<MapView.Polyline
coordinates={direction}
strokeWidth={4}
strokeColor="#000"/>
</MapView>
Thanks in advance
I'm able to zoom the map to fit the coordinates into the screen.
This is the code:
const { width, height } = Dimensions.get('window');
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 SPACE = 0.01;
function createMarker(modifier = 1) {
return {
latitude: LATITUDE - (SPACE * modifier),
longitude: LONGITUDE - (SPACE * modifier),
};
}
const MARKERS = [
createMarker(),
createMarker(2),
createMarker(3),
createMarker(4),
];
const DEFAULT_PADDING = { top: 40, right: 40, bottom: 40, left: 40 };
class FitToCoordinates extends Component {
fitPadding() {
this.map.fitToCoordinates([MARKERS[2], MARKERS[3]], {
edgePadding: { top: 100, right: 100, bottom: 100, left: 100 },
animated: true,
});
}
fitBottomTwoMarkers() {
this.map.fitToCoordinates([MARKERS[2], MARKERS[3]], {
edgePadding: DEFAULT_PADDING,
animated: true,
});
}
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) => (
<MapView.Marker
key={i}
coordinate={marker}
/>
))}
</MapView>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => this.fitPadding()}
style={[styles.bubble, styles.button]}
>
<Text>Fit Bottom Two Markers with Padding</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.fitBottomTwoMarkers()}
style={[styles.bubble, styles.button]}
>
<Text>Fit Bottom Two Markers</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.fitAllMarkers()}
style={[styles.bubble, styles.button]}
>
<Text>Fit All Markers</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Related
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>
in the device:
Error while updating property 'corrdinate' of view maneg by: AIRMapMarker
in the console terminal:
ERROR Warning: Failed prop type: The prop coordinate.latitude is marked as required in MapMarker, but its value is null.
my code:
import { useContext, useEffect, useState } from "react";
import Context from "../../global/Context";
import MapView, { Marker } from "react-native-maps";
import * as Position from "expo-location";
import {
View,
StyleSheet,
ImageBackground,
Dimensions,
Text,
TouchableOpacity,
} from "react-native";
const Location = (props) => {
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const { states } = useContext(Context);
const place = states.place;
useEffect(() => {
(async () => {
let { status } = await Position.requestBackgroundPermissionsAsync();
if (status !== "granted") {
setErrorMsg("App não tem permissão para acessar localizaçõa");
}
let location = await Position.getCurrentPositionAsync();
setLocation(location);
})();
}, []);
let text = "Carregando...";
if (errorMsg) {
text = errorMsg;
} else if (location) {
text = JSON.stringify(location);
}
return (
<ImageBackground
style={{ flex: 1 }}
source={require("../../img/mypoint-wallpaper.jpg")}
>
<View style={styles.container}>
<MapView
style={{
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
}}
initialRegion={{
latitude: place.latitude,
longitude: place.longitude,
longitudeDelta: 0.01,
latitudeDelta: 0.01,
}}
>
<Marker
coordinate={{
latitude: place.latitude,
longitude: place.longitude,
}}
/>
<Marker
title="Sua localização"
coordinate={{
latitude: location && location.coords.latitude,
longitude: location && location.coords.longitude,
}}
/>
</MapView>
<Text style={styles.txtStyle}>{text}</Text>
</View>
</ImageBackground>
);
};
To Draw a marker longitude and latitude is must and are required props.
your latitude and longitude is null here
<Marker
coordinate={{
latitude: place.latitude,
longitude: place.longitude
}}/>
so draw the marker only when you have coordinates
use something like this :-
{(place.latitude && place.longitude) ? <Marker
coordinate={{
latitude: place.latitude,
longitude: place.longitude
}}/> : null
}
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>
);
}
I'm trying to draw specific polygons in MapView, but I'm not sure how to add title to Polygon.
I tried to search official documentation, but only Marker has title property, so I'm not sure if it's supported.
Marker can be used something like this
<Marker
coordinate={latittude: x, longitude: y}
title="I am Marker"
/>
so is it possible to add title property to Polygon like this:
<Polygon
coordinates=[{latittude: x, longitude: y},...]
title="I am Polygon"
/>
???Expecting something like this...
I don't think you have any other option but you can use marker which works pretty nice.
<MapView.Marker key={`${key}Marker`} coordinate={center} anchor={centerAnchor} tracksViewChanges={false}>
<Text key={`${key}Text`} style={styles.clusterText}>
{data.properties.n}
</Text>
</MapView.Marker>
don't forget the tracksViewChanges={false} or the map will go crazy for many markers and put a huge load on the cpu
I also encountered the same problem, I know this is late but here's what I did. I also didn't know if react native maps have that method right now. So for alternative, I used Marker as title. And used this function from github to get the center of polygon.
center_polygon(coordinates) {
let x = coordinates.map(c => c.latitude);
let y = coordinates.map(c => c.longitude);
let minX = Math.min.apply(null, x);
let maxX = Math.max.apply(null, x);
let minY = Math.min.apply(null, y);
let maxY = Math.max.apply(null, y);
return {
latitude: (minX + maxX) / 2,
longitude: (minY + maxY) / 2
};
}
Polygon
return (
<View>
<Marker
coordinate={polygon_center}
>
<Text note style={{color:"#000", fontSize: 9}}>
{name}
</Text>
</Marker>
<Polygon
key={index}
coordinates={fixed_coordinates}
fillColor={this.hexToRgbA(color, 0.4)}
strokeColor={color}
strokeWidth={3}
>
</Polygon>
</View>
);
Proper complete solution for adding any component over polygon in center.
import {
View,
Text,
Button,
StyleSheet,
TouchableOpacity,
InteractionManager,
} from 'react-native';
import React, {useEffect, useRef, useState} from 'react';
import MapView, {Marker, Polygon} from 'react-native-maps';
import {useNavigation} from '#react-navigation/native';
import {COLOR_PLOT_BORDER} from '../../utils/colors';
let polygonsCoordinates = [
[
{latitude: 42.9348443, longitude: -72.287181},
{latitude: 42.9345929, longitude: -72.2832328},
{latitude: 42.9335415, longitude: -72.2834162},
{latitude: 42.9335572, longitude: -72.2852186},
{latitude: 42.9336671, longitude: -72.2869996},
{latitude: 42.934704, longitude: -72.2871498},
],
[
{latitude: 42.9345437, longitude: -72.2811199},
{latitude: 42.9347204, longitude: -72.2795911},
{latitude: 42.9343119, longitude: -72.2792585},
{latitude: 42.9341077, longitude: -72.2810394},
{latitude: 42.9345358, longitude: -72.2811146},
],
[
{latitude: 42.9329962, longitude: -72.2825093},
{latitude: 42.9333065, longitude: -72.2804494},
{latitude: 42.9322775, longitude: -72.2801382},
{latitude: 42.931979, longitude: -72.2824235},
{latitude: 42.9329726, longitude: -72.2824932},
],
];
export default function MapPlots() {
const mapRef = useRef(null);
const [region, setRegion] = useState({});
const [polygonCenters, setPolygonCenters] = useState([]);
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
goToLocation();
getCenterOfAllPolygon();
});
}, []);
const getCenterOfAllPolygon = () => {
let centers = [];
polygonsCoordinates.map((v, i) => {
let c = getCenterPolygon(v);
centers.push(c);
});
setPolygonCenters(centers);
console.log('centers', centers);
};
const keeneRegion = {
latitude: 42.9329962,
longitude: -72.2825093,
latitudeDelta: 0.02,
longitudeDelta: 0.02,
};
const goToLocation = () => {
//complete this animation in 5 seconds
mapRef.current.animateToRegion(keeneRegion, 1 * 1000);
};
let getCenterPolygon = coordinates => {
let x = coordinates.map(c => c.latitude);
let y = coordinates.map(c => c.longitude);
let minX = Math.min.apply(null, x);
let maxX = Math.max.apply(null, x);
let minY = Math.min.apply(null, y);
let maxY = Math.max.apply(null, y);
console.log('adsasdad', minX, maxX, minY, maxY);
return {
latitude: (minX + maxX) / 2,
longitude: (minY + maxY) / 2,
};
};
const onPressPolygon = coordinates => {
let newCoordinates = getCenterPolygon(coordinates);
const goToRegion = {
...newCoordinates,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
};
mapRef.current.animateToRegion(goToRegion, 1 * 1000);
};
return (
<View style={styles.container}>
<MapView
provider={MapView.PROVIDER_GOOGLE}
ref={mapRef}
style={styles.map}
initialRegion={{
latitude: 0, // This the position data
longitude: 0, // This is also position data
latitudeDelta: 200,
longitudeDelta: 1,
}}
loadingEnabled={true}
onRegionChangeComplete={r => setRegion(r)}>
{polygonsCoordinates.map((polygon, i) => (
<View key={i}>
<Polygon
coordinates={polygon}
strokeColor={COLOR_PLOT_BORDER} // fallback for when `strokeColors` is not supported by the map-provider
fillColor={i === 0 ? 'blue' : i === 2 ? 'green' : 'yellow'}
strokeWidth={0.3}
tappable
geodesic
onPress={() => onPressPolygon(polygon)}
/>
<Marker
anchor={{x: 0.5, y: 0.5}}
centerOffset={{x: 0.5, y: 0.5}}
zIndex={10}
coordinate={polygonCenters[i]}>
<Text note style={{color: '#000', fontSize: 15}}>
1001
</Text>
</Marker>
</View>
))}
</MapView>
<Button onPress={() => goToLocation()} title="Go to Current Location" />
<Text style={styles.text}>Current latitude{region.latitude}</Text>
<Text style={styles.text}>Current longitude{region.longitude}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
text: {
fontSize: 20,
backgroundColor: 'lightblue',
},
});
I am using MapView of react-native map clustering and Marker and callout of react-native-maps. I am unable to use animateToRegion.
It shows me this.mapView.animateToRegion is not a function
<MapView
ref={map=>{mapView = map}}
provider='google'
clustering={true}
onClusterPress={this.onPressCluster}
region={this.state.region}
onRegionChange={this.onRegionChange}
onRegionChangeComplete={this.onRegionChangeComplete}
style={styles.map}
showsUserLocation={true}
followUserLocation={true}
zoomEnabled={true}
ScrollEnabled={true}
showsBuildings={true}
showsMyLocationButton={false}/>
animate(){
let r = {
latitude: 42.5,
longitude: 15.2,
latitudeDelta: 7.5,
longitudeDelta: 7.5,
};
this.mapView.root.animateToRegion(r, 2000);
}
render(){
return(
<MapView
ref = {(ref)=>this.mapView=ref}
region={{
latitude: 35.688442,
longitude: 51.403753,
latitudeDelta: 0.5,
longitudeDelta: 0.5,
}}
onPress={()=>this.animate()}
>
...markers...
</MapView>
);
}
clickOnSearchedAddress = (placeId, index, dscrpt) => {
getLatLongFromAddress(placeId).then((response) => {
let r = {
[Constants.KEY_LATITUDE]: response.geometry.location.lat,
[Constants.KEY_LONGITUDE]: response.geometry.location.lng,
latitudeDelta: latitudeDelta,
longitudeDelta: longitudeDelta
}
this.mapView.animateToRegion(r, 1000)
Keyboard.dismiss()
isSetTextProgramatically = true;
this.setState({
search: dscrpt,
searchData: [],
})
}).then((error) => { })
}
<MapView
ref={ref => this.mapView = ref}
provider={PROVIDER_GOOGLE}
style={[styles.map, , { width: this.state.width }]}
initialRegion={region}
onRegionChangeComplete={this.onRegionChange}
showsMyLocationButton={true}
showsCompass={true}
showsUserLocation={true}
onMapReady={() => this.setState({ width: width - 1 })}}
/>
I believe the issue is that the prop is not called ref, but instead mapRef. Pass the ref in there, and it should work.
You would then also have to call it with something like this this.mapView.[current/map]animateToRegion. Just check the mapView object you get.