Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.MapSnapshotOptions
import com.mapbox.maps.Size
import com.mapbox.maps.SnapshotOverlayOptions
import com.mapbox.maps.Snapshotter
import com.rnmapbox.rnmbx.modules.RNMBXModule.Companion.getAccessToken
import com.rnmapbox.rnmbx.modules.RNMBXSnapshotModule
Expand Down Expand Up @@ -43,30 +46,38 @@ class RNMBXSnapshotModule(private val mContext: ReactApplicationContext) :
// FileSource.getInstance(mContext).activate();
mContext.runOnUiQueueThread {
val snapshotterID = UUID.randomUUID().toString()
val snapshotter = Snapshotter(mContext, getOptions(jsOptions))
val showLogo = if (jsOptions.hasKey("withLogo")) jsOptions.getBoolean("withLogo") else true
val overlayOptions = SnapshotOverlayOptions(showLogo = showLogo)
val snapshotter = Snapshotter(mContext, getOptions(jsOptions), overlayOptions)
snapshotter.setStyleUri(jsOptions.getString("styleURL")!!)
snapshotter.setCamera(getCameraOptions(jsOptions))
try {
snapshotter.setCamera(getCameraOptions(jsOptions, snapshotter))
} catch (e: IllegalArgumentException) {
promise.reject(REACT_CLASS, e.message, e)
return@runOnUiQueueThread
}
mSnapshotterMap[snapshotterID] = snapshotter
snapshotter.startV11 { image,error ->

snapshotter.start(null) { image, error ->
try {
if (image == null) {
Log.w(REACT_CLASS, "Snapshot failed: $error")
promise.reject(REACT_CLASS, "Snapshot failed: $error")
mSnapshotterMap.remove(snapshotterID)
} else {
val image = image.toMapboxImage()
val mapboxImage = image.toMapboxImage()
var result: String? = null
result = if (jsOptions.getBoolean("writeToDisk")) {
BitmapUtils.createImgTempFile(mContext, image)
BitmapUtils.createImgTempFile(mContext, mapboxImage)
} else {
BitmapUtils.createImgBase64(image)
BitmapUtils.createImgBase64(mapboxImage)
}
if (result == null) {
promise.reject(
REACT_CLASS,
"Could not generate snapshot, please check Android logs for more info."
)
return@startV11
return@start
}
promise.resolve(result)
mSnapshotterMap.remove(snapshotterID)
Expand All @@ -79,17 +90,44 @@ class RNMBXSnapshotModule(private val mContext: ReactApplicationContext) :
}
}

private fun getCameraOptions(jsOptions: ReadableMap): CameraOptions {
val centerPoint =
Feature.fromJson(jsOptions.getString("centerCoordinate")!!)
val point = centerPoint.geometry() as Point?
val cameraOptionsBuilder = CameraOptions.Builder()
return cameraOptionsBuilder
.center(point)
.pitch(jsOptions.getDouble("pitch"))
.bearing(jsOptions.getDouble("heading"))
.zoom(jsOptions.getDouble("zoomLevel"))
.build()
private fun getCameraOptions(jsOptions: ReadableMap, snapshotter: Snapshotter): CameraOptions {
val pitch = jsOptions.getDouble("pitch")
val heading = jsOptions.getDouble("heading")
val zoomLevel = jsOptions.getDouble("zoomLevel")

// Check if centerCoordinate is provided
if (jsOptions.hasKey("centerCoordinate") && !jsOptions.isNull("centerCoordinate")) {
val centerPoint = Feature.fromJson(jsOptions.getString("centerCoordinate")!!)
val point = centerPoint.geometry() as Point?
return CameraOptions.Builder()
.center(point)
.pitch(pitch)
.bearing(heading)
.zoom(zoomLevel)
.build()
}

// Check if bounds is provided
if (jsOptions.hasKey("bounds") && !jsOptions.isNull("bounds")) {
val boundsJson = jsOptions.getString("bounds")!!
val featureCollection = FeatureCollection.fromJson(boundsJson)
val coords = featureCollection.features()?.mapNotNull { feature ->
feature.geometry() as? Point
} ?: emptyList()

if (coords.isEmpty()) {
throw IllegalArgumentException("bounds contains no valid coordinates")
}

return snapshotter.cameraForCoordinates(
coords,
EdgeInsets(0.0, 0.0, 0.0, 0.0),
heading,
pitch
)
}

throw IllegalArgumentException("neither centerCoordinate nor bounds provided")
}

private fun getOptions(jsOptions: ReadableMap): MapSnapshotOptions {
Expand Down
175 changes: 141 additions & 34 deletions example/src/examples/Camera/TakeSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
Dimensions,
Text,
ActivityIndicator,
TouchableOpacity,
ScrollView,
} from 'react-native';

import BaseExamplePropTypes from '../common/BaseExamplePropTypes';
Expand All @@ -17,9 +19,31 @@ const styles = StyleSheet.create({
padding: 16,
},
snapshot: {
flex: 1,
width: '100%',
height: 200,
marginBottom: 16,
},
spinnerContainer: { alignItems: 'center', flex: 1, justifyContent: 'center' },
label: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 8,
color: '#333',
},
button: {
backgroundColor: '#4264fb',
padding: 12,
borderRadius: 8,
marginBottom: 16,
},
buttonText: {
color: 'white',
textAlign: 'center',
fontWeight: 'bold',
},
section: {
marginBottom: 24,
},
});

class TakeSnapshot extends React.Component {
Expand All @@ -31,54 +55,137 @@ class TakeSnapshot extends React.Component {
super(props);

this.state = {
snapshotURI: null,
withLogoURI: null,
withoutLogoURI: null,
boundsURI: null,
loading: true,
};
}

componentDidMount() {
this.takeSnapshot();
this.takeAllSnapshots();
}

async takeSnapshot() {
const { width, height } = Dimensions.get('window');

const uri = await snapshotManager.takeSnap({
centerCoordinate: [-74.12641, 40.797968],
width,
height,
zoomLevel: 12,
pitch: 30,
heading: 20,
styleURL: StyleURL.Dark,
writeToDisk: true,
});

this.setState({ snapshotURI: uri });
async takeAllSnapshots() {
const { width } = Dimensions.get('window');
const snapshotWidth = width - 32;
const snapshotHeight = 200;

try {
// Snapshot with logo (default)
const withLogoURI = await snapshotManager.takeSnap({
centerCoordinate: [-74.12641, 40.797968],
width: snapshotWidth,
height: snapshotHeight,
zoomLevel: 12,
pitch: 30,
heading: 20,
styleURL: StyleURL.Dark,
writeToDisk: true,
withLogo: true,
});

// Snapshot without logo
const withoutLogoURI = await snapshotManager.takeSnap({
centerCoordinate: [-74.12641, 40.797968],
width: snapshotWidth,
height: snapshotHeight,
zoomLevel: 12,
pitch: 30,
heading: 20,
styleURL: StyleURL.Dark,
writeToDisk: true,
withLogo: false,
});

// Snapshot using bounds instead of centerCoordinate
const boundsURI = await snapshotManager.takeSnap({
bounds: [
[-74.2, 40.7],
[-74.0, 40.9],
],
width: snapshotWidth,
height: snapshotHeight,
zoomLevel: 10,
pitch: 0,
heading: 0,
styleURL: StyleURL.Street,
writeToDisk: true,
withLogo: true,
});

this.setState({
withLogoURI,
withoutLogoURI,
boundsURI,
loading: false,
});
} catch (error) {
console.error('Snapshot error:', error);
this.setState({ loading: false });
}
}

render() {
let childView = null;
const { loading, withLogoURI, withoutLogoURI, boundsURI } = this.state;

if (!this.state.snapshotURI) {
childView = (
if (loading) {
return (
<View style={styles.spinnerContainer}>
<ActivityIndicator size="large" color="#0000ff" />
<Text>Generating Snapshot</Text>
</View>
);
} else {
childView = (
<View style={styles.container}>
<Image
source={{ uri: this.state.snapshotURI }}
resizeMode="contain"
style={styles.snapshot}
/>
<ActivityIndicator size="large" color="#4264fb" />
<Text>Generating Snapshots...</Text>
</View>
);
}

return childView;
return (
<ScrollView style={styles.container}>
<View style={styles.section}>
<Text style={styles.label}>With Logo (withLogo: true)</Text>
{withLogoURI && (
<Image
source={{ uri: withLogoURI }}
resizeMode="contain"
style={styles.snapshot}
/>
)}
</View>

<View style={styles.section}>
<Text style={styles.label}>Without Logo (withLogo: false)</Text>
{withoutLogoURI && (
<Image
source={{ uri: withoutLogoURI }}
resizeMode="contain"
style={styles.snapshot}
/>
)}
</View>

<View style={styles.section}>
<Text style={styles.label}>
Using Bounds (instead of centerCoordinate)
</Text>
{boundsURI && (
<Image
source={{ uri: boundsURI }}
resizeMode="contain"
style={styles.snapshot}
/>
)}
</View>

<TouchableOpacity
style={styles.button}
onPress={() => {
this.setState({ loading: true });
this.takeAllSnapshots();
}}
>
<Text style={styles.buttonText}>Retake Snapshots</Text>
</TouchableOpacity>
</ScrollView>
);
}
}

Expand Down
4 changes: 3 additions & 1 deletion ios/RNMBX/RNMBXSnapshotModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,12 @@ class RNMBXSnapshotModule : NSObject {
let height = jsOptions["height"] as? NSNumber else {
throw RNMBXError.paramError("width, height: is not a number")
}
let mapSnapshotOptions = MapSnapshotOptions(
let showsLogo = jsOptions["withLogo"] as? Bool ?? true
var mapSnapshotOptions = MapSnapshotOptions(
size: CGSize(width: width.doubleValue, height: height.doubleValue),
pixelRatio: 1.0
)
mapSnapshotOptions.showsLogo = showsLogo

return mapSnapshotOptions
}
Expand Down
Loading