React Native Maps - takeSnapshot not capturing markers - react-native

react-native: https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz
react: 16.13.1
react-native-maps: 0.28.0
I want to get markers as a part of the snapshot. When we use takeSnapshot method all markers are ignored.
const snapshot = this.viewRefTest.takeSnapshot({
format: 'png', // image formats: 'png', 'jpg' (default: 'png')
quality: 0.5, // image quality: 0..1 (only relevant for jpg, default: 1)
result: 'file', // result types: 'file', 'base64' (default: 'file')
});
<MapView
ref={(viewRefTest) => {
this.viewRefTest = viewRefTest;
}}
showsUserLocation={true}
followUserLocation={true}>
<MapView.Marker coordinate={item.location}>
<Image
style={{ width: 30, height: 30 }}
source={require('../../assets/images/trophy.png')}
/>
<Callout style={{ width: 250, flexDirection: 'row', alignItems: 'center' }}>
<Text>$23</Text>
<View>
<Text style={{ fontSize: 12 }}>Custom Text!</Text>
</View>
</Callout>
</MapView.Marker>
</MapView>;
Please let me know the possibility of this.

After too many tries & combinations of adding delays, height/width, I was not able to capture custom markers using takeSnapshot method.
As a workaround, I have used captureRef method of react-native-view-shot
https://github.com/gre/react-native-view-shot
const uri = await captureRef(this.viewRefTest, {
format: "png",
quality: 1
})
<MapView
ref={(viewRefTest) => {
this.viewRefTest = viewRefTest;
}}
showsUserLocation={true}
followUserLocation={true}>
<MapView.Marker coordinate={item.location}>
<Image
style={{ width: 30, height: 30 }}
source={require('../../assets/images/trophy.png')}
/>
<Callout style={{ width: 250, flexDirection: 'row', alignItems: 'center' }}>
<Text>$23</Text>
<View>
<Text style={{ fontSize: 12 }}>Custom Text!</Text>
</View>
</Callout>
</MapView.Marker>
</MapView>
CaptureRef Returns a Promise of the image URI. It helps capture a React Native view to an image. We can mention height, width, quality & format for the captured image.

Could you try use width and height?
const snapshot = this.viewRefTest.takeSnapshot({
width: 500,
height: 500,
format: 'png',
quality: 0.5,
result: 'file',
});
snapshot.then((uri) => {
console.log(uri);
});

You can solve that by creating a Snapshot with "react-native-view-shot"

I think this bug depends on when you call this.viewRefTest.takeSnapshot()
You can check in my https://expo.dev/#duongtungls/expo-map-view-example
I think call takeSnapshot just after map mounted won't get marker or map.
If call after callback onMapReady still need wait some hundreds of milisecond to take fully snapshot for both map and marker.
I hope this example code can help you solve problem.
import { StatusBar } from 'expo-status-bar';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { StyleSheet, Text, View, Image } from 'react-native';
import MapView, { Marker } from 'react-native-maps';
import { Ionicons } from '#expo/vector-icons';
export default function App() {
const mapRef = useRef(null);
const [uri, setUri] = useState(null);
const takeSnapshot = useCallback(() => {
if (!mapRef || !mapRef.current) {
return;
}
setTimeout(() => {
const snapshot = mapRef.current.takeSnapshot({
format: 'png', // image formats: 'png', 'jpg' (default: 'png')
quality: 0.5, // image quality: 0..1 (only relevant for jpg, default: 1)
result: 'file', // result types: 'file', 'base64' (default: 'file')
});
snapshot.then((uri) => {
setUri(uri);
});
}, 800); // I add some timeout delay because without delay snapnot won't have map or marker.
}, [mapRef]);
return (
<View style={styles.container}>
{!uri && (
<MapView
ref={mapRef}
style={{
width: '100%',
height: '100%',
}}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
onMapReady={takeSnapshot} // I think need wait for map ready to take snapshot but seem still need wait by setTimeout to get fully snapshot
>
<Marker
coordinate={{
latitude: 37.78825,
longitude: -122.4324,
}}
title={'Test'}
>
<Ionicons name="trophy" size={32} color="red" />
<Text>This is a marker</Text>
</Marker>
</MapView>
)}
{uri && (
<Image
style={{
width: '100%',
height: '100%',
resizeMode: 'contain',
borderColor: 'red',
borderWidth: 10,
}}
source={{
uri,
}}
/>
)}
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Snapshot take after map mounted
Snapshot take after onMapReady and 800ms delay
Best regards,

Related

White background in react-native-view-shot

I am using react-native-view-shot to save a screenshot of a view, whose height is not initially defined, as I am using padding and setting the height of the view using the onLayout method.
The problem is that, when the view has an initial fixed height, the screenshot taken does not have a white background, which is what I want. However, when I set the height when the onLayout is invoked, the screenshot has a white background.
Here's my code:
const [height, setHeight] = useState();
<View
onLayout={(e) => {
setHeight(e.nativeEvent.layout.height);
}}
ref={contentRef}
style={{
height,
width: width - 12,
backgroundColor: "darkblue",
borderRadius: 32,
}}
>
<Text style={styles.text}>This is a test using padding</Text>
</View>
https://snack.expo.dev/#pietroputelli/react-native-view-shot
=========== EDIT ===========
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<View ref={shotRef} style={{ backgroundColor: "transparent" }}>
<View
onLayout={(e) => {
setHeight(e.nativeEvent.layout.height);
}}
style={{
height,
width: width / 2 - 12,
backgroundColor: "darkblue",
borderRadius: 32,
}}
>
<Text style={{ color: "white" }}>This is a test using padding</Text>
</View>
</View>
<Button
onPress={() => {
captureRef(shotRef, {
format: "png",
quality: 0.8,
}).then(
async (uri) => {
await MediaLibrary.saveToLibraryAsync(uri);
},
(error) => console.error("Oops, snapshot failed", error)
);
}}
title="Take screenshot"
/>
</View>
I can able to generate the same from viewshot:
take another view and give a reference to that view and generate a screenshot from that. might be its issue of reference. Please check the below screenshot.
<View ref={viewshotRef}
style={{
// whatever you want to add as per your requirement
}} >
<View
onLayout={(e) => {
setHeight(e.nativeEvent.layout.height);
}}
style={{
height,
width: DEVICE_WIDTH / 2 - 12,
backgroundColor: "darkblue",
borderRadius: 32,
}}
>
<Text style={{ color: 'white' }}>This is a test using padding</Text>
</View>
</View>
For base64:
import * as MediaLibrary from "expo-media-library";
import React, { useRef, useState } from "react";
import {
Button,
Dimensions,
StyleSheet,
Text,
View,
} from "react-native";
import ViewShot, { captureRef } from "react-native-view-shot";
const { width } = Dimensions.get("window");
export default function ViewCapture() {
const contentRef = useRef();
const [height, setHeight] = useState(undefined);
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "transparent" }}>
<ViewShot ref={contentRef} options={{ result: 'base64' }}>
<View
onLayout={(e) => {
setHeight(e.nativeEvent.layout.height);
}}
style={{
height,
width: width - 12,
backgroundColor: "darkblue",
borderRadius: 32,
}}
>
<Text style={{ color: "white" }}>This is a test using padding</Text>
</View>
</ViewShot>
{/* </View> */}
<Button
onPress={() => {
contentRef.current.capture().then((url) => {
console.log("on success", "data:image/jpeg;base64,", url)
// await MediaLibrary.saveToLibraryAsync(uri);
},
(error) => console.error("Oops, snapshot failed", error)
);
}}
title="Take screenshot"
/>
</View>
);
}
For File:
You need to set the result as tmpfile so you will get file uri in the callback
<ViewShot ref={contentRef} options={{ result: 'tmpfile' }}>
I hope it will work!

React Native Maps UrlTile

I am trying to use openstreetmap tile in UrlTile but I am getting duplicate layers, openstreetmap and default google map, how can I remove google map layer?
This is my component:
import { StyleSheet, Text, View, Button } from "react-native";
import MapView, {
PROVIDER_GOOGLE,
MAP_TYPES,
PROVIDER_DEFAULT,
UrlTile,
Marker,
} from "react-native-maps";
import React, { useEffect } from "react";
export default function Home({ navigation }) {
let location = {
latitude: 23.259933,
longitude: 77.412613,
latitudeDelta: 0.009,
longitudeDelta: 0.009,
};
return (
<View style={styles.container}>
<View style={styles.myMap}>
<MapView
region={location}
rotateEnabled={false}
style={{ flex: 1 }}
style={styles.map}
showsUserLocation
>
<UrlTile
urlTemplate="https://a.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png"
maximumZ={19}
/>
<Marker
title="Home"
coordinate={{
latitude: location.latitude,
longitude: location.longitude,
}}
/>
</MapView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffd",
alignItems: "center",
justifyContent: "center",
},
btn1: {
margin: 20,
},
btn2: {
margin: 20,
},
btns: {
flex: 1,
flexDirection: "row",
backgroundColor: "grey",
},
myMap: {
flex: 2,
backgroundColor: "white",
width: "100%",
marginTop: 30,
marginBottom: 30,
},
map: {
width: "100%",
height: "100%",
},
});
Also, when I switch openstreetmap tile with my own MapTiler server tiles it doesn't show anything, just google map
'http://localhost:3650/api/maps/satellite-hybrid/{z}/{x}/{y}.png'
One last thing, I need to know if this going to cost me, I am required to use Google API key to run react-native-maps library, I need some explanation.
Thank you in advance.
have you tried to consult the MapTiler Documentation yet?
There might be some tips to help you:
https://docs.maptiler.com/maplibre-gl-native-android/?_ga=2.172534305.364226297.1644212649-2087445124.1628005081&_gl=1*1hky133*_ga*MjA4NzQ0NTEyNC4xNjI4MDA1MDgx*_ga_K4SXYBF4HT*MTY0NDI0NDA5My4yLjEuMTY0NDI0NTAxMC41Ng..
May I ask if the Google Map is essential? Since you use MapTiler Server, it could be also replaced with a MapTiler Data Layer.
If you are interested in getting to know more, feel free to contact us at support#maptiler.com
Add this to your MapView.
mapType={Platform.OS == "android" ? "none" : "standard"}
or
mapType={Platform.OS == "ios" ? "none" : "standard"}
You can find more info on this page https://github.com/react-native-maps/react-native-maps#tile-overlay-using-local-tiles

React Native Maps Google pricing

As of 2022, does React Native Maps still free, or does go under any list of Google pricing?
I read this line on Reddit but it's 2 years ago
"Embedding Google Maps through react-native-maps does require an API key, but it is at no cost. Can confirm as I have an app at 100K users and haven't paid a dime for maps."
I am not sure if it is still the same.
This is my way of using it:
import { StyleSheet, Text, View, Button } from "react-native";
import MapView, {
PROVIDER_GOOGLE,
MAP_TYPES,
PROVIDER_DEFAULT,
UrlTile,
Marker,
} from "react-native-maps";
import React, { useEffect } from "react";
export default function Home({ navigation }) {
let location = {
latitude: 30.259933,
longitude: 31.412613,
latitudeDelta: 0.009,
longitudeDelta: 0.009,
};
return (
<View style={styles.container}>
<View style={styles.myMap}>
<MapView
style={{ flex: 1 }}
showsUserLocation
PROVIDER_GOOGLE
MAP_TYPES="STANDARD"
>
<Marker
title="Home"
coordinate={{
latitude: location.latitude,
longitude: location.longitude,
}}
/>
</MapView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffd",
alignItems: "center",
justifyContent: "center",
},
myMap: {
flex: 2,
backgroundColor: "white",
width: "100%",
marginTop: 30,
marginBottom: 30,
},
map: {
width: "100%",
height: "100%",
},
});
I need some clarification
See https://developers.google.com/maps/documentation/android-sdk/usage-and-billing#dynamic-maps
If you use a Map ID you will be charged. (This is new functionality)

How to Overlay selected custom marker in react-native-maps

I use react-native-maps to display an array of positions using custom marker. I want to overlay the selected maker, but did not found the right solution. Using position: 'absolute' for the selected marker changes its position in the map.
Here is the code of the MapView:
<MapView
style={styles.map}
region={region} // initial region is user location if myPosition or coordinates of search
// onRegionChange={onRegionChange}
>
<View style={{position: 'absolute'}}>
{data.map((item) => (
<Marker
onPress={() => handleMarkerPress(item)}
coordinate={{
latitude: item.latitude,
longitude: item.longitude,
}}
// image={require('Resources/geoloc.png')}
// pinColor={item.id === visibleItemId ? 'red' : 'blue'}
key={item.id}>
<MosqueMarquer
mosqueImage={item.images}
itemId={item.id}
visibleItemId={visibleItemId}
distance={item.distance_result}
/>
</Marker>
))}
{isUserGeoLoc ? (
<Marker
coordinate={{
latitude: userLocation.latitude,
longitude: userLocation.longitude,
}}
description={'Your are here'}>
<Image
resizeMode="contain"
source={require('Resources/user_location.png')}
style={{tintColor: '#4280ee', height: 25}}
/>
</Marker>
) : null}
</View>
</MapView>
And here is the custom marker code:
const MosqueMarquer = (props) => {
const relativeStyle =
props.itemId == props.visibleItemId
? {position: 'absolute', tintColor: '#428947', color: '#fff', zIndex: 1}
: {position: null, tintColor: '#fff', color: '#3c423d', zIndex: 0};
return (
<ImageBackground
resizeMode="contain"
source={require('Resources/square_marker.png')}
style={{
...styles.imageBackground,
position: relativeStyle.position,
zIndex: relativeStyle.zIndex,
}}
imageStyle={{tintColor: relativeStyle.tintColor}}>
<Text style={{...styles.text, color: relativeStyle.color}}>
{props.distance}m
</Text>
</ImageBackground>
);
};
export default MosqueMarquer;
const styles = StyleSheet.create({
imageBackground: {
width: 70,
height: 70,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: common.FONT_SIZE_H38,
},
});
the selected marker in green is under the none selected one:
THANKS
<XMarksTheSpot coordinates={coordinatesOfYOurMarker} center={center} />
XMarksTheSpot ->
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { Polygon, Polyline, Marker } from 'react-native-maps';
class XMarksTheSpot extends React.Component {
render() {
return (
<View>
<Polygon
coordinates={this.props.coordinates}
strokeColor="rgba(0, 0, 0, 1)"
strokeWidth={3}
/>
<Polyline
coordinates={[this.props.coordinates[0], this.props.coordinates[2]]}
/>
<Polyline
coordinates={[this.props.coordinates[1], this.props.coordinates[3]]}
/>
<Marker coordinate={this.props.center} />
</View>
);
}
}
XMarksTheSpot.propTypes = {
coordinates: PropTypes.array,
center: PropTypes.object,
zIndex: PropTypes.number,
};
export default XMarksTheSpot;

Using react-native-camera, how to access saved pictures?

My goal is to use the react-native-camera and simply show a picture on the same screen, if a picture has been taken. I'm trying to save the picture source as "imageURI". If it exists, I want to show it, if a picture hasn't been taken yet, just show text saying No Image Yet. I've got the camera working, since I can trace the app is saving pictures to the disk. Having trouble with the following:
How to assign the capture functions data to a variable when I take the picture, that I can call later (imageURI).
Don't know how to do an if statement in Javascript to check if a variable exists yet.
import Camera from 'react-native-camera';
export default class camerahere extends Component {
_takePicture () {
this.camera.capture((err, data) => {
if (err) return;
imageURI = data;
});
}
render() {
if ( typeof imageURI == undefined) {
image = <Text> No Image Yet </Text>
} else {
image = <Image source={{uri: imageURI, isStatic:true}}
style={{width: 100, height: 100}} />
}
return (
<View style={styles.container}>
<Camera
captureTarget={Camera.constants.CaptureTarget.disk}
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={Camera.constants.Aspect.fill}>
{button}
<TouchableHighlight onPress={this._takePicture.bind(this)}>
<View style={{height:50,width:50,backgroundColor:"pink"}}></View>
</TouchableHighlight>
</Camera>
I found the answer to my own question. This is an example of the react-native-camera being used.
https://github.com/spencercarli/react-native-snapchat-clone/blob/master/app/routes/Camera.js
Found this answer in another earlier posted question answered by #vinayr. Thanks!
Get recently clicked image from camera on image view in react-native
Here's the code from the first link:
import React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions,
TouchableHighlight,
Image,
Text,
} from 'react-native';
import Camera from 'react-native-camera';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
},
preview: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
height: Dimensions.get('window').height,
width: Dimensions.get('window').width
},
capture: {
width: 70,
height: 70,
borderRadius: 35,
borderWidth: 5,
borderColor: '#FFF',
marginBottom: 15,
},
cancel: {
position: 'absolute',
right: 20,
top: 20,
backgroundColor: 'transparent',
color: '#FFF',
fontWeight: '600',
fontSize: 17,
}
});
class CameraRoute extends Component {
constructor(props) {
super(props);
this.state = {
path: null,
};
}
takePicture() {
this.camera.capture()
.then((data) => {
console.log(data);
this.setState({ path: data.path })
})
.catch(err => console.error(err));
}
renderCamera() {
return (
<Camera
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={Camera.constants.Aspect.fill}
captureTarget={Camera.constants.CaptureTarget.disk}
>
<TouchableHighlight
style={styles.capture}
onPress={this.takePicture.bind(this)}
underlayColor="rgba(255, 255, 255, 0.5)"
>
<View />
</TouchableHighlight>
</Camera>
);
}
renderImage() {
return (
<View>
<Image
source={{ uri: this.state.path }}
style={styles.preview}
/>
<Text
style={styles.cancel}
onPress={() => this.setState({ path: null })}
>Cancel
</Text>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.path ? this.renderImage() : this.renderCamera()}
</View>
);
}
};
export default CameraRoute;