react native maps marker custom image cannot change from default - react-native

I've spent about 5 hours trying to get this to work with many different permutations of code, and then rebuilding. I cannot for the life of me change the default "red pointer" marker as the default marker image in react native maps.
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
...
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
ref={ref => {this.map = ref;}}
minZoomLevel={4} // default => 0
maxZoomLevel={10} // default => 20
enableZoomControl={true}
showsUserLocation = {true}
showsMyLocationButton = {true}
zoomEnabled = {true}
initialRegion={{
latitude: 37.600425,
longitude: -122.385861,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
>
<MapView.Marker
coordinate={marker.location}
image={require('./images/test.png')} <------ HERE
width={48}
height={48}
/>
</MapView>
The images definitely exist in the right folder, I've tried different image formats png/gif/jpg/svg, I've tried using {{uri:...}} and icon/image, adding and removing width/height attributes. Nothing seems to work. I'm always getting the default red pointer.
Have I missed something obvious?
The project packager/compiler fails when I require an image that doesn't exist, or an unsupported type. It definitely can see the image, but just doesn't do anything with it. Same results on the emulator and on actual device.
image={require('./images/test.png')}
This line just does nothing, as if it's being ignored somehow.

<MapView
provider={PROVIDER_GOOGLE}
style={styles.container}
region={{
latitude: this.state.latitude,
longitude: this.state.longitude,
}}
>
<Marker
coordinate={{
latitude: this.state.latitude,
longitude: this.state.longitude,
}}
description={"This is a marker in React Natve"}
>
<Image source={require('./man_marker.png')} style={{height: 35, width:35 }} />
</Marker>
</MapView>

There are two solutions:
The first solution (recommended)
Resize your marker image with image editor(such as Photoshop,....) and use as icon in marker
To do this, you can make three photos of different sizes (YOUR_MARKER.png , YOUR_MARKER#2x.png , YOUR_MARKER#3x.png) (React Native automatically displays the appropriate item).
This is a good solution if you have a large number of markers.(You can refer here to clarify this)
<Marker
coordinate={ ... }
tracksViewChanges={false}
icon={require('./YOUR_MARKER.png')}
/>
The second solution
As #shubham-raitka said you can use the Image inside the marker
<Marker
coordinate={ ... }
>
<Image source={require('./YOUR_MARKER.png')} style={{height: 35, width:35 }} />
</Marker>
In this case, if your number of markers is high (about 50 or more) the map performance will be very low.Therefore, it is not recommended to use this method

Here's an approach that worked for me in a similar situation: Use Image in place of Marker. Pop-ups work the same as with a marker. If you try this, Image is imported from react-native. The actual image is imported as:
var dotImage = require('./pathToImage.png')
<Marker
coordinate={meter.latlng}
title={"Parking Meter"}
key={idx}
>
<Image
source={dotImage}
style={{height: 6, width: 6}}
/>
</Marker>

The way you give the width and height is a bit strange, please try with this way.
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
const markerImg = require('./images/test.png'); // <-- create a const with the path of the image
<------
------>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
ref={ref => {this.map = ref;}}
minZoomLevel={4} // default => 0
maxZoomLevel={10} // default => 20
enableZoomControl={true}
showsUserLocation = {true}
showsMyLocationButton = {true}
zoomEnabled = {true}
initialRegion={{
latitude: 37.600425,
longitude: -122.385861,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
>
<Marker
image={markerImg} // <----- add this the const with image
onPress={() => this.setState({ marker1: !this.state.marker1 })}
coordinate={{
latitude: 37.600425,
longitude: -122.385861,
}}
centerOffset={{ x: -18, y: -60 }}
anchor={{ x: 0.69, y: 1 }}
/>
</Marker>
</MapView>
I hope it works for you, works for me!

Not enough rep yet to just leave a comment, but the first solution works, I just had to add resizeMode or it cuts off the image if it's bigger.
<Marker
coordinate={ ... }
>
<Image source={require('./YOUR_MARKER.png')} style={{height: 35, width:35, resizeMode:"contain" }} />
</Marker>

<Marker
coordinate={d.driverLocation}
title={d.driverName}
description={d.autoNumber}
onPress={() => console.warn(d.mobaNumbers)}
image={require("../../../assets/bee.png")}
>
</Marker>

Try this it should work
import {Image} from 'react-native';
import MapView, {Marker} from 'react-native-maps';
<MapView
style={styles.mapStyle}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
customMapStyle={mapStyle}>
<Marker
draggable
coordinate={{
latitude: 37.78825,
longitude: -122.4324,
}}
onDragEnd={e => alert(JSON.stringify(e.nativeEvent.coordinate))}
title={'Test Marker'}
description={'This is a description of the marker'}>
<Image
source={require('./assests/custom_marker.png')}
style={{height: 35, width: 35}}
/>
</Marker>
</MapView>

Related

how to limit zoom on a map in react native with <MapView/> component

<Map
ref={mapRef}
key={mapShouldUpdate}
showsUserLocation
userLocationAnnotationTitle={`Ma position`}
showsMyLocationButton
showsPointsOfInterest={false}
showsBuildings={false}
showsTraffic={false}
showsIndoors={false}
minZoomLevel={9}
maxZoomLevel={12}
rotateEnabled={false}
pitchEnabled={false}
toolbarEnabled={false}
loadingEnabled={usersLoading || offersLoading}
moveOnMarkerPress={false}
mapType={getMapType()}
initialRegion={{
latitude: mapCenter?.latitude,
longitude: mapCenter?.longitude,
latitudeDelta: 0.4,
longitudeDelta: 0.2,
}}
onRegionChangeComplete={(region) => {
setMapCenter(region)
onRegionChangeComplete?.(region)
}}
style={{ ...eva.style.Map, ...style }}
>
I tried changing the minZoomLevel, maxZoomLevel, latitudeDelta, longitudeDelta but it doesn't render what I want. I would like the user zoom to be limited.
Any solution?
Could you try:
minDelta={0.05}
maxDelta={2}
it should work!

React Native Maps : moving myLocationButton

Is there a way to move the position of MyLocationButton?
I am using Mapview with some additional views on top of it. one of those views is on top of the MyLocationButton, and aesthetically I would like to keep it like that --> so I should move the MyLocationButton instead. Is there a way to do it? x
<MapView
style={styles.map}
showsMyLocationButton = {true}
showsUserLocation = {true}
provider={PROVIDER_GOOGLE}
customMapStyle={mapStyle}
initialRegion={{
latitude: this.state.mylocation.coords.latitude,
longitude: this.state.mylocation.coords.longitude,
latitudeDelta: 0.0922,//Zoom
longitudeDelta: 0.0421,//Zoom
}}>
In case it helps somebody, I found the solution to move the position of the button while the map continues to cover all the screen:
<MapView
(...)
mapPadding={{top:0, right:0, left:0, bottom:230}}>
(...)
</Mapview>

react-native-maps callout content not updated on change

I am trying to update the content of a <Callout> tag on a google maps view using the react-native-maps npm package. I would like to dynamically load extra content after a map marker is pressed. I have simplified my code to the example below:-
I have already tried using the tracksInfoWindowChanges attribute on the marker, and the showCallout() method to attempt to manually trigger a re-render. I am using android so the iOS only options such as redrawCallout() method are not available.
const MyMap = (props) => {
const [marker, setMarker] = useState({});
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({ title: 'Test'});
}}>
<View>
<Icon name='key' size={40} color='#384963'</Icon>
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Is it possible to trigger a re-render of the content after the Callout has been rendered once? From debugging I can see that the component renders after I update my marker state, however the content is not visible until I manually close the Callout and re-opened it.
I am having the exact same problem. The suggestion for android, as per this issue, is to use showCallout(). Below is my work-around for this issue using showCallout():
const MyMap = (props) => {
const [marker, setMarker] = useState({});
let markerRef = useRef(null);
useEffect(() => {
if (markerRef.current) {
markerRef.current.showCallout();
}
})
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
ref={markerRef}
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({title: `Test ${Math.floor(Math.random() * 10)}`});
}}>
<View>
<Icon name='key' size={40} color='#384963' />
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Briefly, I added a markerRef and call markerRef.current.showCallout() in useEffect(). What this achieves, if I am not mistaken, is that each time MyMap gets re-rendered, the callout will be forced to show again. I also changed the onPress such that the effect of callout re-rendering is more apparent.
Note 1: Each time the marker is pressed, the callout shall have its text updated.
Note 2: If you have multiple markers and callouts, and you want to update only the callout that is currently in view, you will have to add another check in useEffect(). Something like: if (markerRef.current && selectedMarker === 'markerID') to make sure that only the desired callout is re-rendered continuously.

Polyline is not rendering on maps while live reload is enabled

I am using react-native-map package to draw a polyline using coordinates i am receiving in API call. Line is being drawn when HOT reload is enabled on expo but not when LIVE reload is enabled.
I have converted all coordinates to array of objects in following template.
[{latitude:33.00, longitude:-74.00},{latitude:33.10, longitude:-74.02}]
And passed this array to coordinates in MapView.Polyline.
This how i am rendering MapView
<MapView
showsUserLocation
followsUserLocation
style={{ flex: 1 }}
initialRegion={{
latitude: 31.5623499,
longitude: 74.3287183,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}>
{this.state.allPlants.map((item, index) => {
return <MapView.Marker
key={item.id.toString()}
coordinate={{
latitude: item.latitude,
longitude: item.longitude,
}}>
<Image source={item.isDead?require("../../assets/dead_tree.png"):require("../../assets/tree.png")} key={item.id.toString()} /> </MapView.Marker>
})}
<MapView.Polyline
coordinates={this.allCoords}
strokeWidth={6}
strokeColor="red"
fillColor="rgba(100,0,0,0.5)"
/>
</MapView>
And this is how i am creating array of coordinates objects
let tmpArray=[]
if(tmp.length!==0){
for(let i=0; i<tmp.length;i++){
let tmpObj={
latitude:tmp[i].latitude,
longitude:tmp[i].longitude
}
tmpArray.push( tmpObj)
}
}
this.allCoords=tmpArray
It should be able to show polyline as it is already showing on HOT reloading, i don't understand if that is the expected behavior or this is some bug.
I wrapped MapView code into following
<View style={{ flex: 1 }}>
{this.state.isDataFetched && (
<MapView
showsUserLocation
followsUserLocation
style={{ flex: 1 }}
initialRegion={{
latitude: 31.5623499,
longitude: 74.3287183,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}>
{this.state.allPlants.map((item, index) => {
return <MapView.Marker
key={item.id.toString()}
coordinate={{
latitude: item.latitude,
longitude: item.longitude,
}}>
<Image source={item.isDead?require("../../assets/dead_tree.png"):require("../../assets/tree.png")} key={item.id.toString()} /> </MapView.Marker>
})}
<MapView.Polyline
coordinates={this.allCoords}
strokeWidth={6}
strokeColor="red"
fillColor="rgba(100,0,0,0.5)"
/>
</MapView>
)}
</View>
And used this.state.isDataFetched as a flag to condition rendering and it worked as expected.

react native maps clustering

I want to integrate maps in my react-native app, I am using the “react-native-maps” library for that
https://github.com/airbnb/react-native-maps
I want to use clustering but I am unable to find proper documentations related to that.
Please help me to find documentation of how to integrate maps with clustering and also tell which library is best for the implementation of clustering for both platforms, iOS and Android.
You can use mapbox/supercluster repo, and here's a gist showing how to implements supercluster to React Native. It's initially developed for browser/node applications but you can still simply npm install and use it (javascript is javascript everywhere). Add the clustered markers into your MapView (originally shared here):
<MapView ref={ref => { this.map = ref; }}>
{ this.state.markers.map((marker, index) => {
return (
<MapView.Marker
coordinate={{ latitude: marker.geometry.coordinates[1], longitude: marker.geometry.coordinates[0] }}
onPress={() => this.markerPressed(marker)}>
<Marker model={place} active={this.isSelected(place)} />
</MapView.Marker>
);
})}
</MapView>
Warning from an issue of react-native-maps:
The major issue is performance, if you need to display hundreds or
thousands of markers you're gonna have to optimize the hell out of it,
and this is where it gets really hard.
react-native-maps also have an active conflicting PR which solves this issue both in Android and iOS natively, but it waits for a merge. However, you can manually implement it.
You can use react-native-maps-super-cluster. This module wraps AirBnB's react-native-maps and uses MapBox's SuperCluster as clustering engine. This module is super easy to use.
Demo
There is one more library for clustering react-native-map-clustering.
yarn add react-native-map-clustering
import {Marker} from 'react-native-maps';
import MapView from "react-native-map-clustering";
const INITIAL_REGION = {
latitude: 52.5,
longitude: 19.2,
latitudeDelta: 8.5,
longitudeDelta: 8.5,
};
const App = () => (
<MapView initialRegion={INITIAL_REGION} style={{ flex: 1 }}>
<Marker coordinate={{ latitude: 52.4, longitude: 18.7 }} />
<Marker coordinate={{ latitude: 52.1, longitude: 18.4 }} />
<Marker coordinate={{ latitude: 52.6, longitude: 18.3 }} />
<Marker coordinate={{ latitude: 51.6, longitude: 18.0 }} />
<Marker coordinate={{ latitude: 53.1, longitude: 18.8 }} />
<Marker coordinate={{ latitude: 52.9, longitude: 19.4 }} />
<Marker coordinate={{ latitude: 52.2, longitude: 21 }} />
<Marker coordinate={{ latitude: 52.4, longitude: 21 }} />
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
</MapView>
);
How does this work with callouts?
Using this code gives me an error: Element type is invalid: expected a string...
<Marker>
<MapView.Callout tooltip>
<Text>Test Text</Text>
</MapView.Callout>
</Marker>