#include "native_map_view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Java -> C++ conversion #include "style/android_conversion.hpp" #include #include // C++ -> Java conversion #include "conversion/conversion.hpp" #include "conversion/collection.hpp" #include "style/conversion/filter.hpp" #include "geojson/feature.hpp" #include "gson/json_object.hpp" #include "rendering_stats.hpp" #include "android_renderer_frontend.hpp" #include "attach_env.hpp" #include "bitmap.hpp" #include "bitmap_factory.hpp" #include "file_source.hpp" #include "geometry/lat_lng_bounds.hpp" #include "java/util.hpp" #include "jni.hpp" #include "map/camera_position.hpp" #include "map/image.hpp" #include "map_renderer.hpp" #include "run_loop_impl.hpp" #include "style/light.hpp" #include "tile/tile_operation.hpp" namespace mbgl { namespace android { NativeMapView::NativeMapView(jni::JNIEnv& _env, const jni::Object& _obj, const jni::Object& jFileSource, const jni::Object& jMapRenderer, const jni::Object& jNativeMapOptions) : javaPeer(_env, _obj), mapRenderer(MapRenderer::getNativePeer(_env, jMapRenderer)), pixelRatio(NativeMapOptions::pixelRatio(_env, jNativeMapOptions)) { // Get a reference to the JavaVM for callbacks if (_env.GetJavaVM(&vm) < 0) { _env.ExceptionDescribe(); return; } mapRenderer.SetAsyncRendererCleanup(NativeMapOptions::asyncRendererCleanup(_env, jNativeMapOptions)); // Create a renderer frontend rendererFrontend = AndroidRendererFrontend::create(_env, jMapRenderer); // Create Map options MapOptions options; options.withMapMode(MapMode::Continuous) .withSize(mbgl::Size{static_cast(width), static_cast(height)}) .withPixelRatio(pixelRatio) .withConstrainMode(ConstrainMode::HeightOnly) .withViewportMode(ViewportMode::Default) .withCrossSourceCollisions(NativeMapOptions::crossSourceCollisions(_env, jNativeMapOptions)) .withFastPFOREnabled(NativeMapOptions::fastPFOREnabled(_env, jNativeMapOptions)); // Create the core map map = std::make_unique( *rendererFrontend, *this, options, mbgl::android::FileSource::getSharedResourceOptions(_env, jFileSource), mbgl::android::FileSource::getSharedClientOptions(_env, jFileSource), mbgl::android::NativeMapOptions::getActionJournalOptions(_env, jNativeMapOptions)); } /** * Called through NativeMapView#destroy() */ NativeMapView::~NativeMapView() { map.reset(); vm = nullptr; } void NativeMapView::onCameraWillChange(MapObserver::CameraChangeMode mode) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onCameraWillChange = javaClass.GetMethod(*_env, "onCameraWillChange"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onCameraWillChange, (jboolean)(mode != MapObserver::CameraChangeMode::Immediate)); } } void NativeMapView::onCameraIsChanging() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onCameraIsChanging = javaClass.GetMethod(*_env, "onCameraIsChanging"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onCameraIsChanging); } } void NativeMapView::onCameraDidChange(MapObserver::CameraChangeMode mode) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onCameraDidChange = javaClass.GetMethod(*_env, "onCameraDidChange"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onCameraDidChange, (jboolean)(mode != MapObserver::CameraChangeMode::Immediate)); } } void NativeMapView::onWillStartLoadingMap() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onWillStartLoadingMap = javaClass.GetMethod(*_env, "onWillStartLoadingMap"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onWillStartLoadingMap); } } void NativeMapView::onDidFinishLoadingMap() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidFinishLoadingMap = javaClass.GetMethod(*_env, "onDidFinishLoadingMap"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onDidFinishLoadingMap); } } void NativeMapView::onDidFailLoadingMap(MapLoadError, const std::string& error) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidFailLoadingMap = javaClass.GetMethod(*_env, "onDidFailLoadingMap"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onDidFailLoadingMap, jni::Make(*_env, error)); } } void NativeMapView::onWillStartRenderingFrame() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onWillStartRenderingFrame = javaClass.GetMethod(*_env, "onWillStartRenderingFrame"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onWillStartRenderingFrame); } } void NativeMapView::onDidFinishRenderingFrame(const MapObserver::RenderFrameStatus& status) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidFinishRenderingFrame = javaClass.GetMethod)>( *_env, "onDidFinishRenderingFrame"); auto weakReference = javaPeer.get(*_env); if (weakReference) { if (!renderingStats) { renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env)); } RenderingStats::Update(*_env, renderingStats, status.renderingStats); weakReference.Call(*_env, onDidFinishRenderingFrame, (jboolean)(status.mode != MapObserver::RenderMode::Partial), renderingStats); } } void NativeMapView::onWillStartRenderingMap() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onWillStartRenderingMap = javaClass.GetMethod(*_env, "onWillStartRenderingMap"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onWillStartRenderingMap); } } void NativeMapView::onDidFinishRenderingMap(MapObserver::RenderMode mode) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidFinishRenderingMap = javaClass.GetMethod(*_env, "onDidFinishRenderingMap"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onDidFinishRenderingMap, (jboolean)(mode != MapObserver::RenderMode::Partial)); } } void NativeMapView::onDidBecomeIdle() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidBecomeIdle = javaClass.GetMethod(*_env, "onDidBecomeIdle"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onDidBecomeIdle); } } void NativeMapView::onDidFinishLoadingStyle() { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onDidFinishLoadingStyle = javaClass.GetMethod(*_env, "onDidFinishLoadingStyle"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onDidFinishLoadingStyle); } } void NativeMapView::onSourceChanged(mbgl::style::Source& source) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onSourceChanged = javaClass.GetMethod(*_env, "onSourceChanged"); auto sourceId = jni::Make(*_env, source.getID()); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onSourceChanged, sourceId); } } void NativeMapView::onStyleImageMissing(const std::string& imageId) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onStyleImageMissing = javaClass.GetMethod(*_env, "onStyleImageMissing"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onStyleImageMissing, jni::Make(*_env, imageId)); } } bool NativeMapView::onCanRemoveUnusedStyleImage(const std::string& imageId) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onCanRemoveUnusedStyleImage = javaClass.GetMethod(*_env, "onCanRemoveUnusedStyleImage"); auto weakReference = javaPeer.get(*_env); if (weakReference) { return weakReference.Call(*_env, onCanRemoveUnusedStyleImage, jni::Make(*_env, imageId)); } return true; } // JNI Methods // void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) { width = util::max(64, w); height = util::max(64, h); map->setSize({static_cast(width), static_cast(height)}); } jni::Local NativeMapView::getStyleUrl(jni::JNIEnv& env) { return jni::Make(env, map->getStyle().getURL()); } void NativeMapView::setStyleUrl(jni::JNIEnv& env, const jni::String& url) { map->getStyle().loadURL(jni::Make(env, url)); } jni::Local NativeMapView::getStyleJson(jni::JNIEnv& env) { return jni::Make(env, map->getStyle().getJSON()); } void NativeMapView::setStyleJson(jni::JNIEnv& env, const jni::String& json) { map->getStyle().loadJSON(jni::Make(env, json)); } void NativeMapView::setLatLngBounds(jni::JNIEnv& env, const jni::Object& jBounds) { mbgl::BoundOptions bounds; if (jBounds) { bounds.withLatLngBounds(mbgl::android::LatLngBounds::getLatLngBounds(env, jBounds)); } else { bounds.withLatLngBounds(mbgl::LatLngBounds()); } map->setBounds(bounds); } void NativeMapView::cancelTransitions(jni::JNIEnv&) { map->cancelTransitions(); } void NativeMapView::setGestureInProgress(jni::JNIEnv&, jni::jboolean inProgress) { map->setGestureInProgress(inProgress); } void NativeMapView::moveBy(jni::JNIEnv&, jni::jdouble dx, jni::jdouble dy, jni::jlong duration) { mbgl::AnimationOptions animationOptions; if (duration > 0) { animationOptions.duration.emplace(mbgl::Milliseconds(duration)); animationOptions.easing.emplace(mbgl::util::UnitBezier{0.1, 0.4, 0.35, 1.0}); } map->moveBy({dx, dy}, animationOptions); } void NativeMapView::jumpTo(jni::JNIEnv& env, jni::jdouble bearing, jni::jdouble latitude, jni::jdouble longitude, jni::jdouble pitch, jni::jdouble zoom, const jni::Array& padding) { mbgl::CameraOptions options; if (bearing != -1) { options.bearing = bearing; } options.center = mbgl::LatLng(latitude, longitude); if (padding) { assert(padding.Length(env) == 4); options.padding = mbgl::EdgeInsets{ padding.Get(env, 0), padding.Get(env, 1), padding.Get(env, 2), padding.Get(env, 3)}; } if (pitch != -1) { options.pitch = pitch; } if (zoom != -1) { options.zoom = zoom; } map->jumpTo(options); } void NativeMapView::easeTo(jni::JNIEnv& env, jni::jdouble bearing, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration, jni::jdouble pitch, jni::jdouble zoom, const jni::Array& padding, jni::jboolean easing) { mbgl::CameraOptions cameraOptions; if (bearing != -1) { cameraOptions.bearing = bearing; } cameraOptions.center = mbgl::LatLng(latitude, longitude); if (padding) { assert(padding.Length(env) == 4); cameraOptions.padding = mbgl::EdgeInsets{ padding.Get(env, 0), padding.Get(env, 1), padding.Get(env, 2), padding.Get(env, 3)}; } if (pitch != -1) { cameraOptions.pitch = pitch; } if (zoom != -1) { cameraOptions.zoom = zoom; } mbgl::AnimationOptions animationOptions; animationOptions.duration.emplace(mbgl::Milliseconds(duration)); if (!easing) { // add a linear interpolator instead of easing animationOptions.easing.emplace(mbgl::util::UnitBezier{0, 0, 1, 1}); } map->easeTo(cameraOptions, animationOptions); } void NativeMapView::flyTo(jni::JNIEnv& env, jni::jdouble bearing, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration, jni::jdouble pitch, jni::jdouble zoom, const jni::Array& padding) { mbgl::CameraOptions cameraOptions; if (bearing != -1) { cameraOptions.bearing = bearing; } cameraOptions.center = mbgl::LatLng(latitude, longitude); if (padding) { assert(padding.Length(env) == 4); cameraOptions.padding = mbgl::EdgeInsets{ padding.Get(env, 0), padding.Get(env, 1), padding.Get(env, 2), padding.Get(env, 3)}; } if (pitch != -1) { cameraOptions.pitch = pitch; } if (zoom != -1) { cameraOptions.zoom = zoom; } mbgl::AnimationOptions animationOptions; animationOptions.duration.emplace(mbgl::Milliseconds(duration)); map->flyTo(cameraOptions, animationOptions); } jni::Local> NativeMapView::getLatLng(JNIEnv& env) { return LatLng::New(env, *map->getCameraOptions(std::nullopt).center); } void NativeMapView::setLatLng(jni::JNIEnv& env, jni::jdouble latitude, jni::jdouble longitude, const jni::Array& padding, jni::jlong duration) { mbgl::CameraOptions cameraOptions; cameraOptions.center = mbgl::LatLng(latitude, longitude); if (padding) { assert(padding.Length(env) == 4); cameraOptions.padding = mbgl::EdgeInsets{ padding.Get(env, 0), padding.Get(env, 1), padding.Get(env, 2), padding.Get(env, 3)}; } map->easeTo(cameraOptions, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::Local> NativeMapView::getCameraForLatLngBounds( jni::JNIEnv& env, const jni::Object& jBounds, double top, double left, double bottom, double right, double bearing, double tilt) { mbgl::EdgeInsets padding = {top, left, bottom, right}; return CameraPosition::New( env, map->cameraForLatLngBounds(mbgl::android::LatLngBounds::getLatLngBounds(env, jBounds), padding, bearing, tilt), pixelRatio); } jni::Local> NativeMapView::getCameraForGeometry( jni::JNIEnv& env, const jni::Object& jGeometry, double top, double left, double bottom, double right, double bearing, double tilt) { auto geometry = geojson::Geometry::convert(env, jGeometry); mbgl::EdgeInsets padding = {top, left, bottom, right}; return CameraPosition::New(env, map->cameraForGeometry(geometry, padding, bearing, tilt), pixelRatio); } void NativeMapView::setReachability(jni::JNIEnv&, jni::jboolean reachable) { if (reachable) { mbgl::NetworkStatus::Reachable(); } } void NativeMapView::resetPosition(jni::JNIEnv&) { map->jumpTo(mbgl::CameraOptions().withCenter(mbgl::LatLng{}).withZoom(0.0).withBearing(0.0).withPitch(0.0)); } jni::jdouble NativeMapView::getPitch(jni::JNIEnv&) { return *map->getCameraOptions().pitch; } void NativeMapView::setPitch(jni::JNIEnv&, jni::jdouble pitch, jni::jlong duration) { map->easeTo(mbgl::CameraOptions().withPitch(pitch), mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } void NativeMapView::setZoom(jni::JNIEnv&, jni::jdouble zoom, jni::jdouble x, jni::jdouble y, jni::jlong duration) { map->easeTo(mbgl::CameraOptions().withZoom(zoom).withAnchor(mbgl::ScreenCoordinate{x, y}), mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::jdouble NativeMapView::getZoom(jni::JNIEnv&) { return *map->getCameraOptions().zoom; } void NativeMapView::resetZoom(jni::JNIEnv&) { map->jumpTo(mbgl::CameraOptions().withZoom(0.0)); } void NativeMapView::setMinZoom(jni::JNIEnv&, jni::jdouble zoom) { map->setBounds(BoundOptions().withMinZoom(zoom)); } jni::jdouble NativeMapView::getMinZoom(jni::JNIEnv&) { return *map->getBounds().minZoom; } void NativeMapView::setMaxZoom(jni::JNIEnv&, jni::jdouble zoom) { map->setBounds(BoundOptions().withMaxZoom(zoom)); } jni::jdouble NativeMapView::getMaxZoom(jni::JNIEnv&) { return *map->getBounds().maxZoom; } void NativeMapView::setMinPitch(jni::JNIEnv&, jni::jdouble pitch) { map->setBounds(BoundOptions().withMinPitch(pitch)); } jni::jdouble NativeMapView::getMinPitch(jni::JNIEnv&) { return *map->getBounds().minPitch; } void NativeMapView::setMaxPitch(jni::JNIEnv&, jni::jdouble pitch) { map->setBounds(BoundOptions().withMaxPitch(pitch)); } jni::jdouble NativeMapView::getMaxPitch(jni::JNIEnv&) { return *map->getBounds().maxPitch; } void NativeMapView::rotateBy( jni::JNIEnv&, jni::jdouble sx, jni::jdouble sy, jni::jdouble ex, jni::jdouble ey, jni::jlong duration) { mbgl::ScreenCoordinate first(sx, sy); mbgl::ScreenCoordinate second(ex, ey); map->rotateBy(first, second, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } void NativeMapView::setBearing(jni::JNIEnv&, jni::jdouble degrees, jni::jlong duration) { map->easeTo(mbgl::CameraOptions().withBearing(degrees), mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } void NativeMapView::setBearingXY( jni::JNIEnv&, jni::jdouble degrees, jni::jdouble cx, jni::jdouble cy, jni::jlong duration) { mbgl::ScreenCoordinate anchor(cx, cy); map->easeTo(mbgl::CameraOptions().withBearing(degrees).withAnchor(anchor), mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::jdouble NativeMapView::getBearing(jni::JNIEnv&) { return *map->getCameraOptions().bearing; } void NativeMapView::resetNorth(jni::JNIEnv&) { map->easeTo(mbgl::CameraOptions().withBearing(0.0), mbgl::AnimationOptions{{mbgl::Milliseconds(500)}}); } void NativeMapView::setVisibleCoordinateBounds(JNIEnv& env, const jni::Array>& coordinates, const jni::Object& padding, jdouble direction, jni::jlong duration) { NullCheck(env, &coordinates); std::size_t count = coordinates.Length(env); std::vector latLngs; latLngs.reserve(count); for (std::size_t i = 0; i < count; i++) { latLngs.push_back(LatLng::getLatLng(env, coordinates.Get(env, i))); } mbgl::EdgeInsets mbglInsets = {RectF::getTop(env, padding), RectF::getLeft(env, padding), RectF::getBottom(env, padding), RectF::getRight(env, padding)}; mbgl::CameraOptions cameraOptions = map->cameraForLatLngs(latLngs, mbglInsets); if (direction >= 0) { cameraOptions.bearing = direction; } mbgl::AnimationOptions animationOptions; if (duration > 0) { animationOptions.duration.emplace(mbgl::Milliseconds(duration)); // equivalent to kCAMediaTimingFunctionDefault in iOS animationOptions.easing.emplace(mbgl::util::UnitBezier{0.25, 0.1, 0.25, 0.1}); } map->easeTo(cameraOptions, animationOptions); } void NativeMapView::getVisibleCoordinateBounds(JNIEnv& env, jni::Array& output) { auto latlngBounds = map->latLngBoundsForCameraUnwrapped(map->getCameraOptions(std::nullopt)); double latNorth = latlngBounds.north(); double lonEast = latlngBounds.east(); double latSouth = latlngBounds.south(); double lonWest = latlngBounds.west(); std::vector buffer; buffer.reserve(4); // Order of the LatLngBounds: double latNorth, double lonEast, double latSouth, double lonWest buffer.push_back(latNorth); buffer.push_back(lonEast); buffer.push_back(latSouth); buffer.push_back(lonWest); output.SetRegion>(env, 0, buffer); } void NativeMapView::scheduleSnapshot(jni::JNIEnv&) { mapRenderer.requestSnapshot([&](PremultipliedImage image) { auto _env = android::AttachEnv(); // Convert image to bitmap auto bitmap = Bitmap::CreateBitmap(*_env, std::move(image)); // invoke Mapview#OnSnapshotReady static auto& javaClass = jni::Class::Singleton(*_env); static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onSnapshotReady, bitmap); } }); } jni::Local> NativeMapView::getCameraPosition(jni::JNIEnv& env) { return CameraPosition::New(env, map->getCameraOptions(std::nullopt), pixelRatio); } void NativeMapView::updateMarker( jni::JNIEnv& env, jni::jlong markerId, jni::jdouble lat, jni::jdouble lon, const jni::String& jid) { if (markerId == -1) { return; } std::string iconId = jni::Make(env, jid); // Because Java only has int, not unsigned int, we need to bump the annotation id up to a long. map->updateAnnotation(markerId, mbgl::SymbolAnnotation{mbgl::Point(lon, lat), iconId}); } jni::Local> NativeMapView::addMarkers(jni::JNIEnv& env, const jni::Array>& jmarkers) { jni::NullCheck(env, &jmarkers); std::size_t len = jmarkers.Length(env); std::vector ids; ids.reserve(len); for (std::size_t i = 0; i < len; i++) { auto marker = jmarkers.Get(env, i); ids.push_back(map->addAnnotation( mbgl::SymbolAnnotation{Marker::getPosition(env, marker), Marker::getIconId(env, marker)})); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } void NativeMapView::onLowMemory(JNIEnv&) { rendererFrontend->reduceMemoryUse(); } using DebugOptions = mbgl::MapDebugOptions; void NativeMapView::setDebug(JNIEnv&, jni::jboolean debug) { DebugOptions debugOptions = debug ? DebugOptions::TileBorders | DebugOptions::ParseStatus | DebugOptions::Collision : DebugOptions::NoDebug; map->setDebug(debugOptions); } jni::jboolean NativeMapView::getDebug(JNIEnv&) { return map->getDebug() != DebugOptions::NoDebug; } jni::Local> NativeMapView::getActionJournalLogFiles(JNIEnv& env) { const auto& actionJournal = map->getActionJournal(); if (!actionJournal) { return jni::Local>(); } const auto& files = actionJournal->getLogFiles(); auto jFiles = jni::Array::New(env, files.size()); uint32_t index = 0; for (const auto& file : files) { jFiles.Set(env, index++, jni::Make(env, file)); } return jFiles; } jni::Local> NativeMapView::getActionJournalLog(JNIEnv& env) { const auto& actionJournal = map->getActionJournal(); if (!actionJournal) { return jni::Local>(); } const auto& events = actionJournal->getLog(); auto jEvents = jni::Array::New(env, events.size()); uint32_t index = 0; for (const auto& event : events) { jEvents.Set(env, index++, jni::Make(env, event)); } return jEvents; } void NativeMapView::clearActionJournalLog(JNIEnv&) { const auto& actionJournal = map->getActionJournal(); if (actionJournal) { actionJournal->clearLog(); } } jni::jboolean NativeMapView::isFullyLoaded(JNIEnv&) { return map->isFullyLoaded(); } jni::jdouble NativeMapView::getMetersPerPixelAtLatitude(JNIEnv&, jni::jdouble lat, jni::jdouble zoom) { return mbgl::Projection::getMetersPerPixelAtLatitude(lat, zoom); } jni::Local> NativeMapView::projectedMetersForLatLng(JNIEnv& env, jni::jdouble latitude, jni::jdouble longitude) { mbgl::ProjectedMeters projectedMeters = mbgl::Projection::projectedMetersForLatLng( mbgl::LatLng(latitude, longitude)); return ProjectedMeters::New(env, projectedMeters.northing(), projectedMeters.easting()); } jni::Local> NativeMapView::latLngForProjectedMeters(JNIEnv& env, jdouble northing, jdouble easting) { return LatLng::New(env, mbgl::Projection::latLngForProjectedMeters(mbgl::ProjectedMeters(northing, easting))); } jni::Local> NativeMapView::pixelForLatLng(JNIEnv& env, jdouble latitude, jdouble longitude) { mbgl::ScreenCoordinate pixel = map->pixelForLatLng(mbgl::LatLng(latitude, longitude)); return PointF::New(env, static_cast(pixel.x), static_cast(pixel.y)); } void NativeMapView::pixelsForLatLngs(JNIEnv& env, const jni::Array& input, jni::Array& output, jfloat pixelRatio_) { jni::NullCheck(env, &input); std::size_t len = input.Length(env); std::vector latLngs; latLngs.reserve(len); for (std::size_t i = 0; i < len; i += 2) { auto latLng = mbgl::LatLng(input.Get(env, i), input.Get(env, i + 1)); latLngs.push_back(latLng); } std::vector buffer; buffer.reserve(len); std::vector coordinates = map->pixelsForLatLngs(latLngs); for (std::size_t i = 0; i < len / 2; i++) { buffer.push_back(coordinates[i].x * pixelRatio_); buffer.push_back(coordinates[i].y * pixelRatio_); } output.SetRegion>(env, 0, buffer); } jni::Local> NativeMapView::latLngForPixel(JNIEnv& env, jfloat x, jfloat y) { return LatLng::New(env, map->latLngForPixel(mbgl::ScreenCoordinate(x, y))); } void NativeMapView::latLngsForPixels(JNIEnv& env, const jni::Array& input, jni::Array& output, jfloat pixelRatio_) { jni::NullCheck(env, &input); std::size_t len = input.Length(env); std::vector coordinates; coordinates.reserve(len); for (std::size_t i = 0; i < len; i += 2) { auto coordinate = mbgl::ScreenCoordinate(input.Get(env, i) / pixelRatio_, input.Get(env, i + 1) / pixelRatio_); coordinates.push_back(coordinate); } std::vector buffer; buffer.reserve(len); std::vector latLngs = map->latLngsForPixels(coordinates); for (std::size_t i = 0; i < len / 2; i++) { buffer.push_back(latLngs[i].latitude()); buffer.push_back(latLngs[i].longitude()); } output.SetRegion>(env, 0, buffer); } jni::Local> NativeMapView::addPolylines(JNIEnv& env, const jni::Array>& polylines) { NullCheck(env, &polylines); std::size_t len = polylines.Length(env); std::vector ids; ids.reserve(len); for (std::size_t i = 0; i < len; i++) { mbgl::LineAnnotation annotation = Polyline::toAnnotation(env, polylines.Get(env, i)); ids.push_back(map->addAnnotation(annotation)); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } jni::Local> NativeMapView::addPolygons(JNIEnv& env, const jni::Array>& polygons) { NullCheck(env, &polygons); std::size_t len = polygons.Length(env); std::vector ids; ids.reserve(len); for (std::size_t i = 0; i < len; i++) { mbgl::FillAnnotation annotation = Polygon::toAnnotation(env, polygons.Get(env, i)); ids.push_back(map->addAnnotation(annotation)); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } void NativeMapView::updatePolyline(JNIEnv& env, jlong polylineId, const jni::Object& polyline) { mbgl::LineAnnotation annotation = Polyline::toAnnotation(env, polyline); map->updateAnnotation(polylineId, annotation); } void NativeMapView::updatePolygon(JNIEnv& env, jlong polygonId, const jni::Object& polygon) { mbgl::FillAnnotation annotation = Polygon::toAnnotation(env, polygon); map->updateAnnotation(polygonId, annotation); } void NativeMapView::removeAnnotations(JNIEnv& env, const jni::Array& ids) { NullCheck(env, &ids); std::size_t len = ids.Length(env); auto elements = jni::GetArrayElements(env, *ids); jlong* jids = std::get<0>(elements).get(); for (std::size_t i = 0; i < len; i++) { if (jids[i] == -1L) { continue; } map->removeAnnotation(jids[i]); } } void NativeMapView::addAnnotationIcon( JNIEnv& env, const jni::String& symbol, jint w, jint h, jfloat scale, const jni::Array& jpixels) { const std::string symbolName = jni::Make(env, symbol); NullCheck(env, &jpixels); std::size_t size = jpixels.Length(env); mbgl::PremultipliedImage premultipliedImage({static_cast(w), static_cast(h)}); if (premultipliedImage.bytes() != uint32_t(size)) { throw mbgl::util::StyleImageException("Annotation icon image pixel count mismatch"); } jni::GetArrayRegion(env, *jpixels, 0, size, reinterpret_cast(premultipliedImage.data.get())); map->addAnnotationImage( std::make_unique(symbolName, std::move(premultipliedImage), static_cast(scale))); } void NativeMapView::removeAnnotationIcon(JNIEnv& env, const jni::String& symbol) { const std::string symbolName = jni::Make(env, symbol); map->removeAnnotationImage(symbolName); } jdouble NativeMapView::getTopOffsetPixelsForAnnotationSymbol(JNIEnv& env, const jni::String& symbolName) { return map->getTopOffsetPixelsForAnnotationImage(jni::Make(env, symbolName)); } jni::Local> NativeMapView::getTransitionOptions(JNIEnv& env) { const auto transitionOptions = map->getStyle().getTransitionOptions(); const auto duration = std::chrono::duration_cast( transitionOptions.duration.value_or(mbgl::Duration::zero())) .count(); const auto delay = std::chrono::duration_cast( transitionOptions.delay.value_or(mbgl::Duration::zero())) .count(); const auto enablePlacementTransitions = (jboolean)transitionOptions.enablePlacementTransitions; return TransitionOptions::fromTransitionOptions(env, duration, delay, enablePlacementTransitions); } void NativeMapView::setTransitionOptions(JNIEnv& env, const jni::Object& options) { const mbgl::style::TransitionOptions transitionOptions( Duration(mbgl::Milliseconds(TransitionOptions::getDuration(env, options))), Duration(mbgl::Milliseconds(TransitionOptions::getDelay(env, options))), TransitionOptions::isEnablePlacementTransitions(env, options)); map->getStyle().setTransitionOptions(transitionOptions); } jni::Local> NativeMapView::queryPointAnnotations(JNIEnv& env, const jni::Object& rect) { using namespace mbgl::style; using namespace mbgl::style::conversion; // Convert input mbgl::ScreenBox box = { {RectF::getLeft(env, rect), RectF::getTop(env, rect)}, {RectF::getRight(env, rect), RectF::getBottom(env, rect)}, }; // Assume only points for now mbgl::AnnotationIDs ids = rendererFrontend->queryPointAnnotations(box, annotationRequestTimeout); // Convert result std::vector longIds(ids.begin(), ids.end()); auto result = jni::Array::New(env, ids.size()); result.SetRegion>(env, 0, longIds); return result; } jni::Local> NativeMapView::queryShapeAnnotations(JNIEnv& env, const jni::Object& rect) { using namespace mbgl::style; using namespace mbgl::style::conversion; // Convert input mbgl::ScreenBox box = { {RectF::getLeft(env, rect), RectF::getTop(env, rect)}, {RectF::getRight(env, rect), RectF::getBottom(env, rect)}, }; mbgl::AnnotationIDs ids = rendererFrontend->queryShapeAnnotations(box, annotationRequestTimeout); // Convert result std::vector longIds(ids.begin(), ids.end()); auto result = jni::Array::New(env, ids.size()); result.SetRegion>(env, 0, longIds); return result; } jni::Local>> NativeMapView::queryRenderedFeaturesForPoint( JNIEnv& env, jni::jfloat x, jni::jfloat y, const jni::Array& layerIds, const jni::Array>& jfilter) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; std::optional> layers; if (layerIds && layerIds.Length(env) > 0) { layers = android::conversion::toVector(env, layerIds); } mapbox::geometry::point point = {x, y}; return Feature::convert(env, rendererFrontend->queryRenderedFeatures(point, {layers, toFilter(env, jfilter)})); } jni::Local>> NativeMapView::queryRenderedFeaturesForBox( JNIEnv& env, jni::jfloat left, jni::jfloat top, jni::jfloat right, jni::jfloat bottom, const jni::Array& layerIds, const jni::Array>& jfilter) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; std::optional> layers; if (layerIds && layerIds.Length(env) > 0) { layers = toVector(env, layerIds); } mapbox::geometry::box box = {mapbox::geometry::point{left, top}, mapbox::geometry::point{right, bottom}}; return Feature::convert(env, rendererFrontend->queryRenderedFeatures(box, {layers, toFilter(env, jfilter)})); } void NativeMapView::setFeatureState(JNIEnv& env, const jni::String& sourceId, const jni::String& sourceLayerId, const jni::String& featureId, const jni::Object& state) { if (!sourceId || !featureId || !state) { return; } rendererFrontend->setFeatureState( jni::Make(env, sourceId), sourceLayerId ? std::optional(jni::Make(env, sourceLayerId)) : std::nullopt, jni::Make(env, featureId), gson::JsonObject::convert(env, state)); map->triggerRepaint(); } jni::Local> NativeMapView::getFeatureState(JNIEnv& env, const jni::String& sourceId, const jni::String& sourceLayerId, const jni::String& featureId) { if (!sourceId || !featureId) { return jni::Local>(); } const auto state = rendererFrontend->getFeatureState( jni::Make(env, sourceId), sourceLayerId ? std::optional(jni::Make(env, sourceLayerId)) : std::nullopt, jni::Make(env, featureId)); if (state.empty()) { return jni::Local>(); } return gson::JsonObject::New(env, state); } void NativeMapView::removeFeatureState(JNIEnv& env, const jni::String& sourceId, const jni::String& sourceLayerId, const jni::String& featureId, const jni::String& stateKey) { if (!sourceId) { return; } rendererFrontend->removeFeatureState( jni::Make(env, sourceId), sourceLayerId ? std::optional(jni::Make(env, sourceLayerId)) : std::nullopt, featureId ? std::optional(jni::Make(env, featureId)) : std::nullopt, stateKey ? std::optional(jni::Make(env, stateKey)) : std::nullopt); map->triggerRepaint(); } jni::Local> NativeMapView::getLight(JNIEnv& env) { mbgl::style::Light* light = map->getStyle().getLight(); if (light) { return Light::createJavaLightPeer(env, *map, *light); } else { return jni::Local>(); } } jni::Local>> NativeMapView::getLayers(JNIEnv& env) { // Get the core layers std::vector layers = map->getStyle().getLayers(); // Convert auto jLayers = jni::Array>::New(env, layers.size()); int index = 0; for (auto layer : layers) { jLayers.Set(env, index, LayerManagerAndroid::get()->createJavaLayerPeer(env, *layer)); index++; } return jLayers; } jni::Local> NativeMapView::getLayer(JNIEnv& env, const jni::String& layerId) { // Find the layer mbgl::style::Layer* coreLayer = map->getStyle().getLayer(jni::Make(env, layerId)); if (!coreLayer) { mbgl::Log::Debug(mbgl::Event::JNI, "No layer found"); return jni::Local>(); } // Create and return the layer's native peer return LayerManagerAndroid::get()->createJavaLayerPeer(env, *coreLayer); } jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env, const jni::String& layerId, const jni::String& propertyName, const jni::jfloat value) { auto* layer = map->getStyle().getLayer(jni::Make(env, layerId)); if (!layer) return false; const auto property = jni::Make(env, propertyName); if (property != "raster-particle-time-mix" && property != "raster-particle-particle-count" && property != "raster-particle-line-width" && property != "raster-particle-particle-length" && property != "raster-particle-particle-opacity" && property != "raster-particle-trail-opacity" && property != "raster-particle-trail-fade" && property != "raster-particle-speed-color-enabled") { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_PROPERTY"); return false; } auto* particleLayer = dynamic_cast(layer); if (!particleLayer) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_LAYER"); return false; } if (!std::isfinite(value)) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_FLOAT"); return false; } if (property == "raster-particle-time-mix") { particleLayer->setTimeMix(value); } else if (property == "raster-particle-particle-count") { if (value < 0.0f || std::floor(value) != value) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_PARTICLE_COUNT"); return false; } const auto requestedCount = static_cast(value); particleLayer->setParticleCount(requestedCount); if (particleLayer->getParticleCount() != requestedCount) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_PARTICLE_COUNT"); return false; } } else if (property == "raster-particle-line-width") { particleLayer->setLineWidth(value); } else if (property == "raster-particle-particle-length") { particleLayer->setParticleLength(value); } else if (property == "raster-particle-particle-opacity") { particleLayer->setParticleOpacity(value); } else if (property == "raster-particle-trail-opacity") { particleLayer->setTrailOpacity(value); } else if (property == "raster-particle-trail-fade") { particleLayer->setTrailFade(value); } else { if (value != 0.0f && value != 1.0f) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_COLOR_MODE"); return false; } particleLayer->setSpeedColorEnabled(value > 0.5f); } mbgl::Log::Info(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET layerId=" + jni::Make(env, layerId) + " property=" + property + " value=" + std::to_string(value) + " result=SUCCESS"); return true; } jni::jboolean NativeMapView::setStyleLayerVisibility(JNIEnv& env, const jni::String& layerId, const jni::jboolean visible) { auto* layer = map->getStyle().getLayer(jni::Make(env, layerId)); if (!layer) return false; layer->setVisibility(visible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None); return true; } jni::jboolean NativeMapView::setRasterParticleColors(JNIEnv& env, const jni::String& layerId, const jni::Array& particle, const jni::Array& trail) { auto* layer = dynamic_cast(map->getStyle().getLayer(jni::Make(env, layerId))); if (!layer || particle.Length(env) != 4 || trail.Length(env) != 4) return false; std::array particleColor, trailColor; particle.GetRegion(env, 0, particleColor); trail.GetRegion(env, 0, trailColor); if (!mbgl::style::validWeatherRGBA(particleColor) || !mbgl::style::validWeatherRGBA(trailColor)) return false; layer->setParticleColor(particleColor); layer->setTrailColor(trailColor); return true; } jni::jboolean NativeMapView::setRasterParticleColor(JNIEnv& env, const jni::String& layerId, const jni::Array& particle) { auto* layer = dynamic_cast(map->getStyle().getLayer(jni::Make(env, layerId))); if (!layer || particle.Length(env) != 4) return false; std::array color; particle.GetRegion(env, 0, color); if (!mbgl::style::validWeatherRGBA(color)) return false; layer->setParticleColor(color); return true; } jni::jboolean NativeMapView::setRasterParticleTrailColor(JNIEnv& env, const jni::String& layerId, const jni::Array& trail) { auto* layer = dynamic_cast(map->getStyle().getLayer(jni::Make(env, layerId))); if (!layer || trail.Length(env) != 4) return false; std::array color; trail.GetRegion(env, 0, color); if (!mbgl::style::validWeatherRGBA(color)) return false; layer->setTrailColor(color); return true; } jni::jboolean NativeMapView::setRasterArraySourceTargetFps(JNIEnv& env, const jni::String& sourceId, const jni::jint targetFps) { auto* source = dynamic_cast(map->getStyle().getSource(jni::Make(env, sourceId))); return source && source->setRuntimeTargetFps(static_cast(targetFps)); } jni::jint NativeMapView::getRasterArraySourceTargetFps(JNIEnv& env, const jni::String& sourceId) { auto* source = dynamic_cast(map->getStyle().getSource(jni::Make(env, sourceId))); return source ? source->runtimeTargetFps() : 0; } jni::jboolean NativeMapView::setWeatherColorStops(JNIEnv& env, const jni::String& layerId, const jni::Array& speeds, const jni::Array& rgba) { auto* layer = dynamic_cast(map->getStyle().getLayer(jni::Make(env, layerId))); const auto count = speeds.Length(env); if (!layer || count < 2 || count > static_cast(mbgl::style::MaxWeatherColorStops) || rgba.Length(env) != count * 4) return false; std::vector speedValues(count), rgbaValues(static_cast(count) * 4); speeds.GetRegion(env, 0, speedValues); rgba.GetRegion(env, 0, rgbaValues); std::vector stops; stops.reserve(count); for (jni::jsize i = 0; i < count; ++i) { std::array color; std::copy_n(rgbaValues.data() + i * 4, 4, color.data()); stops.push_back({speedValues[i], color}); } return layer->setColorStops(std::move(stops)); } jni::jint NativeMapView::getWeatherColorStopCapacity(JNIEnv&) { return static_cast(mbgl::style::MaxWeatherColorStops); } jni::jboolean NativeMapView::advanceRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) { const auto id = jni::Make(env, sourceId); auto* source = map->getStyle().getSource(id); auto* rasterArraySource = dynamic_cast(source); if (!rasterArraySource) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id); return false; } const auto result = rasterArraySource->advanceRuntimeFieldWindow(); const bool success = result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Applied || result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Noop; mbgl::Log::Info(mbgl::Event::JNI, "ParticleField: event=FIELD_WINDOW_UPDATE_BRIDGE sourceId=" + id + " result=" + (success ? "SUCCESS" : "REJECTED")); return success; } jni::jboolean NativeMapView::repeatRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) { const auto id = jni::Make(env, sourceId); auto* source = map->getStyle().getSource(id); auto* rasterArraySource = dynamic_cast(source); if (!rasterArraySource) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id); return false; } const auto result = rasterArraySource->repeatRuntimeFieldWindow(); const bool success = result == mbgl::style::RasterArrayRuntimeFieldUpdateResult::Noop; mbgl::Log::Info(mbgl::Event::JNI, "ParticleField: event=FIELD_WINDOW_UPDATE_DUPLICATE_BRIDGE sourceId=" + id + " result=" + (success ? "NOOP" : "REJECTED")); return success; } jni::jint NativeMapView::submitRasterArraySourceHostField(JNIEnv& env, const jni::String& sourceId, const jni::String& metadata, const jni::Array& payload) { const auto id = jni::Make(env, sourceId); auto* source = dynamic_cast(map->getStyle().getSource(id)); if (!source) { mbgl::Log::Error(mbgl::Event::JNI, "ParticleField: event=HOST_FIELD_SUBMIT_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id); return static_cast(mbgl::style::RasterArrayHostFieldSubmitResult::RejectedSourceMode); } NullCheck(env, &payload); std::vector bytes(payload.Length(env)); if (!bytes.empty()) { jni::GetArrayRegion(env, *payload, 0, bytes.size(), reinterpret_cast(bytes.data())); } const auto result = source->submitHostField(jni::Make(env, metadata), bytes); mbgl::Log::Info(mbgl::Event::JNI, "ParticleField: event=HOST_FIELD_SUBMIT_BRIDGE sourceId=" + id + " result=" + mbgl::style::rasterArrayHostFieldSubmitResultName(result)); return static_cast(result); } jni::jboolean NativeMapView::clearRasterArraySourceHostFields(JNIEnv& env, const jni::String& sourceId) { const auto id = jni::Make(env, sourceId); auto* source = dynamic_cast(map->getStyle().getSource(id)); if (!source) return false; const bool cleared = source->clearHostFields(); mbgl::Log::Info(mbgl::Event::JNI, "WeatherRuntime: event=CLEAR_FIELDS sourceId=" + id + " result=" + (cleared ? "SUCCESS" : "REJECTED")); return cleared; } jni::jboolean NativeMapView::seekRasterArraySourceTimelineToTimestamp(JNIEnv& env, const jni::String& sourceId, const jni::jlong timestamp) { const auto id = jni::Make(env, sourceId); auto* source = dynamic_cast(map->getStyle().getSource(id)); return source && timestamp >= 0 && source->seekRuntimeTimelineToTimestamp(static_cast(timestamp)); } jni::jboolean NativeMapView::controlRasterArraySourceTimeline(JNIEnv& env, const jni::String& sourceId, const jni::jint command, const jni::jfloat value) { const auto id = jni::Make(env, sourceId); auto* source = dynamic_cast(map->getStyle().getSource(id)); if (!source) return false; const bool success = source->controlRuntimeTimeline(command, value); mbgl::Log::Info(mbgl::Event::JNI, "ParticleTimeline: event=COMMAND sourceId=" + id + " command=" + std::to_string(command) + " result=" + (success ? "SUCCESS" : "REJECTED")); return success; } void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, const jni::String& before) { assert(nativeLayerPtr != 0); Layer* layer = reinterpret_cast(nativeLayerPtr); try { layer->addToStyle( map->getStyle(), before ? std::optional(jni::Make(env, before)) : std::optional()); } catch (const std::runtime_error& error) { jni::ThrowNew( env, jni::FindClass(env, "org/maplibre/android/style/layers/CannotAddLayerException"), error.what()); } } void NativeMapView::addLayerAbove(JNIEnv& env, jlong nativeLayerPtr, const jni::String& above) { assert(nativeLayerPtr != 0); Layer* layer = reinterpret_cast(nativeLayerPtr); // Find the sibling auto layers = map->getStyle().getLayers(); auto siblingId = jni::Make(env, above); size_t index = 0; for (auto l : layers) { if (l->getID() == siblingId) { break; } index++; } // Check if we found a sibling to place before std::optional before; if (index + 1 > layers.size()) { // Not found jni::ThrowNew(env, jni::FindClass(env, "org/maplibre/android/style/layers/CannotAddLayerException"), std::string("Could not find layer: ").append(siblingId).c_str()); return; } else if (index + 1 < layers.size()) { // Place before the sibling before = {layers.at(index + 1)->getID()}; } // Add the layer try { layer->addToStyle(map->getStyle(), before); } catch (const std::runtime_error& error) { jni::ThrowNew( env, jni::FindClass(env, "org/maplibre/android/style/layers/CannotAddLayerException"), error.what()); } } void NativeMapView::addLayerAt(JNIEnv& env, jlong nativeLayerPtr, jni::jint index) { assert(nativeLayerPtr != 0); Layer* layer = reinterpret_cast(nativeLayerPtr); auto layers = map->getStyle().getLayers(); // Check index int numLayers = layers.size() - 1; if (index > numLayers || index < 0) { Log::Error(Event::JNI, "Index out of range: " + std::to_string(index)); jni::ThrowNew(env, jni::FindClass(env, "org/maplibre/android/style/layers/CannotAddLayerException"), std::string("Invalid index").c_str()); return; } // Insert it below the current at that index try { layer->addToStyle(map->getStyle(), layers.at(index)->getID()); } catch (const std::runtime_error& error) { jni::ThrowNew( env, jni::FindClass(env, "org/maplibre/android/style/layers/CannotAddLayerException"), error.what()); } } /** * Remove layer at index. */ jni::jboolean NativeMapView::removeLayerAt(JNIEnv& env, jni::jint index) { auto layers = map->getStyle().getLayers(); // Check index int numLayers = layers.size() - 1; if (index > numLayers || index < 0) { Log::Warning(Event::JNI, "Index out of range: " + std::to_string(index)); return jni::jni_false; } std::unique_ptr coreLayer = map->getStyle().removeLayer(layers.at(index)->getID()); if (coreLayer) { jni::Local> layerObj = LayerManagerAndroid::get()->createJavaLayerPeer(env, std::move(coreLayer)); return jni::jni_true; } return jni::jni_false; } /** * Remove with wrapper object id. Ownership is transferred back to the wrapper */ jni::jboolean NativeMapView::removeLayer(JNIEnv& env, jlong layerPtr) { if (layerPtr == 0) { Log::Warning(Event::JNI, "Cannot remove layer: native layer pointer is null"); return jni::jni_false; } mbgl::android::Layer* layer = reinterpret_cast(layerPtr); const auto layerId = jni::Make(env, layer->getId(env)); if (layerId.empty()) { Log::Warning(Event::JNI, "Cannot remove layer: layer reference is detached or invalid"); return jni::jni_false; } std::unique_ptr coreLayer = map->getStyle().removeLayer(layerId); if (coreLayer) { layer->setLayer(std::move(coreLayer)); return jni::jni_true; } return jni::jni_false; } jni::Local>> NativeMapView::getSources(JNIEnv& env) { // Get the core sources std::vector sources = map->getStyle().getSources(); // Convert auto jSources = jni::Array>::New(env, sources.size()); int index = 0; for (auto source : sources) { jSources.Set(env, index, Source::peerForCoreSource(env, *source, *rendererFrontend, *map)); index++; } return jSources; } jni::Local> NativeMapView::getSource(JNIEnv& env, const jni::String& sourceId) { // Find the source mbgl::style::Source* coreSource = map->getStyle().getSource(jni::Make(env, sourceId)); if (!coreSource) { mbgl::Log::Debug(mbgl::Event::JNI, "No source found"); return jni::Local>(); } // Create and return the source's native peer return jni::NewLocal(env, Source::peerForCoreSource(env, *coreSource, *rendererFrontend, *map)); } void NativeMapView::addSource(JNIEnv& env, const jni::Object& obj, jlong sourcePtr) { assert(sourcePtr != 0); Source* source = reinterpret_cast(sourcePtr); try { source->addToMap(env, obj, *map, *rendererFrontend); } catch (const std::runtime_error& error) { jni::ThrowNew( env, jni::FindClass(env, "org/maplibre/android/style/sources/CannotAddSourceException"), error.what()); } } jni::jboolean NativeMapView::removeSource(JNIEnv& env, const jni::Object& obj, jlong sourcePtr) { assert(sourcePtr != 0); mbgl::android::Source* source = reinterpret_cast(sourcePtr); if (source->removeFromMap(env, obj, *map)) { source->releaseJavaPeer(); return jni::jni_true; } return jni::jni_false; } void NativeMapView::addImage( JNIEnv& env, const jni::String& name, const jni::Object& bitmap, jni::jfloat scale, jni::jboolean sdf) { jni::NullCheck(env, &bitmap); mbgl::PremultipliedImage premultipliedImage = Bitmap::GetImage(env, bitmap); map->getStyle().addImage(std::make_unique( jni::Make(env, name), std::move(premultipliedImage), static_cast(scale), sdf)); } void NativeMapView::addImages(JNIEnv& env, const jni::Array>& jimages) { jni::NullCheck(env, &jimages); std::size_t len = jimages.Length(env); for (std::size_t i = 0; i < len; i++) { auto image = mbgl::android::Image::getImage(env, jimages.Get(env, i)); map->getStyle().addImage(std::make_unique(image)); } } void NativeMapView::removeImage(JNIEnv& env, const jni::String& name) { map->getStyle().removeImage(jni::Make(env, name)); } jni::Local> NativeMapView::getImage(JNIEnv& env, const jni::String& name) { if (auto image = map->getStyle().getImage(jni::Make(env, name))) { return Bitmap::CreateBitmap(env, image->getImage()); } return jni::Local>(); } void NativeMapView::setPrefetchTiles(JNIEnv&, jni::jboolean enable) { map->setPrefetchZoomDelta(enable ? util::DEFAULT_PREFETCH_ZOOM_DELTA : uint8_t(0)); } jni::jboolean NativeMapView::getPrefetchTiles(JNIEnv&) { return jni::jboolean(map->getPrefetchZoomDelta() > 0); } void NativeMapView::setPrefetchZoomDelta(JNIEnv&, jni::jint delta) { map->setPrefetchZoomDelta(uint8_t(delta)); } jni::jint NativeMapView::getPrefetchZoomDelta(JNIEnv&) { return jni::jint(map->getPrefetchZoomDelta()); } void NativeMapView::setTileCacheEnabled(JNIEnv&, jni::jboolean enabled) { rendererFrontend->setTileCacheEnabled(enabled); } jni::jboolean NativeMapView::getTileCacheEnabled(JNIEnv&) { return jni::jboolean(rendererFrontend->getTileCacheEnabled()); } void NativeMapView::setTileLodMinRadius(JNIEnv&, jni::jdouble radius) { map->setTileLodMinRadius(radius); } jni::jdouble NativeMapView::getTileLodMinRadius(JNIEnv&) { return jni::jdouble(map->getTileLodMinRadius()); } void NativeMapView::setTileLodScale(JNIEnv&, jni::jdouble scale) { map->setTileLodScale(scale); } jni::jdouble NativeMapView::getTileLodScale(JNIEnv&) { return jni::jdouble(map->getTileLodScale()); } void NativeMapView::setTileLodPitchThreshold(JNIEnv&, jni::jdouble threshold) { map->setTileLodPitchThreshold(threshold); } jni::jdouble NativeMapView::getTileLodPitchThreshold(JNIEnv&) { return jni::jdouble(map->getTileLodPitchThreshold()); } void NativeMapView::setTileLodZoomShift(JNIEnv&, jni::jdouble shift) { map->setTileLodZoomShift(shift); } jni::jdouble NativeMapView::getTileLodZoomShift(JNIEnv&) { return jni::jdouble(map->getTileLodZoomShift()); } mbgl::Map& NativeMapView::getMap() { return *map; } void NativeMapView::triggerRepaint(JNIEnv&) { assert(map); map->triggerRepaint(); } jni::jboolean NativeMapView::isRenderingStatsViewEnabled(JNIEnv&) { assert(map); return jni::jboolean(map->isRenderingStatsViewEnabled()); } void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) { assert(map); map->enableRenderingStatsView(value); } void NativeMapView::setFrustumOffset(JNIEnv& env, const jni::Object& padding) { mbgl::EdgeInsets offset = {RectF::getTop(env, padding), RectF::getLeft(env, padding), RectF::getBottom(env, padding), RectF::getRight(env, padding)}; map->setFrustumOffset(offset); } // Static methods // void NativeMapView::registerNative(jni::JNIEnv& env) { // Lookup the class static auto& javaClass = jni::Class::Singleton(env); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) // Register the peer jni::RegisterNativePeer( env, javaClass, "nativePtr", jni::MakePeer&, const jni::Object&, const jni::Object&, const jni::Object&>, "nativeInitialize", "nativeDestroy", METHOD(&NativeMapView::resizeView, "nativeResizeView"), METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"), METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"), METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"), METHOD(&NativeMapView::setStyleJson, "nativeSetStyleJson"), METHOD(&NativeMapView::cancelTransitions, "nativeCancelTransitions"), METHOD(&NativeMapView::setGestureInProgress, "nativeSetGestureInProgress"), METHOD(&NativeMapView::moveBy, "nativeMoveBy"), METHOD(&NativeMapView::jumpTo, "nativeJumpTo"), METHOD(&NativeMapView::easeTo, "nativeEaseTo"), METHOD(&NativeMapView::flyTo, "nativeFlyTo"), METHOD(&NativeMapView::getLatLng, "nativeGetLatLng"), METHOD(&NativeMapView::setLatLng, "nativeSetLatLng"), METHOD(&NativeMapView::getCameraForLatLngBounds, "nativeGetCameraForLatLngBounds"), METHOD(&NativeMapView::getCameraForGeometry, "nativeGetCameraForGeometry"), METHOD(&NativeMapView::setReachability, "nativeSetReachability"), METHOD(&NativeMapView::resetPosition, "nativeResetPosition"), METHOD(&NativeMapView::getPitch, "nativeGetPitch"), METHOD(&NativeMapView::setPitch, "nativeSetPitch"), METHOD(&NativeMapView::getZoom, "nativeGetZoom"), METHOD(&NativeMapView::setZoom, "nativeSetZoom"), METHOD(&NativeMapView::resetZoom, "nativeResetZoom"), METHOD(&NativeMapView::setMinZoom, "nativeSetMinZoom"), METHOD(&NativeMapView::getMinZoom, "nativeGetMinZoom"), METHOD(&NativeMapView::setMaxZoom, "nativeSetMaxZoom"), METHOD(&NativeMapView::getMaxZoom, "nativeGetMaxZoom"), METHOD(&NativeMapView::setMinPitch, "nativeSetMinPitch"), METHOD(&NativeMapView::getMinPitch, "nativeGetMinPitch"), METHOD(&NativeMapView::setMaxPitch, "nativeSetMaxPitch"), METHOD(&NativeMapView::getMaxPitch, "nativeGetMaxPitch"), METHOD(&NativeMapView::rotateBy, "nativeRotateBy"), METHOD(&NativeMapView::setBearing, "nativeSetBearing"), METHOD(&NativeMapView::setBearingXY, "nativeSetBearingXY"), METHOD(&NativeMapView::getBearing, "nativeGetBearing"), METHOD(&NativeMapView::resetNorth, "nativeResetNorth"), METHOD(&NativeMapView::setVisibleCoordinateBounds, "nativeSetVisibleCoordinateBounds"), METHOD(&NativeMapView::scheduleSnapshot, "nativeTakeSnapshot"), METHOD(&NativeMapView::getCameraPosition, "nativeGetCameraPosition"), METHOD(&NativeMapView::updateMarker, "nativeUpdateMarker"), METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"), METHOD(&NativeMapView::setDebug, "nativeSetDebug"), METHOD(&NativeMapView::getDebug, "nativeGetDebug"), METHOD(&NativeMapView::getActionJournalLogFiles, "nativeGetActionJournalLogFiles"), METHOD(&NativeMapView::getActionJournalLog, "nativeGetActionJournalLog"), METHOD(&NativeMapView::clearActionJournalLog, "nativeClearActionJournalLog"), METHOD(&NativeMapView::isFullyLoaded, "nativeIsFullyLoaded"), METHOD(&NativeMapView::onLowMemory, "nativeOnLowMemory"), METHOD(&NativeMapView::getMetersPerPixelAtLatitude, "nativeGetMetersPerPixelAtLatitude"), METHOD(&NativeMapView::projectedMetersForLatLng, "nativeProjectedMetersForLatLng"), METHOD(&NativeMapView::pixelForLatLng, "nativePixelForLatLng"), METHOD(&NativeMapView::pixelsForLatLngs, "nativePixelsForLatLngs"), METHOD(&NativeMapView::getVisibleCoordinateBounds, "nativeGetVisibleCoordinateBounds"), METHOD(&NativeMapView::latLngForProjectedMeters, "nativeLatLngForProjectedMeters"), METHOD(&NativeMapView::latLngForPixel, "nativeLatLngForPixel"), METHOD(&NativeMapView::latLngsForPixels, "nativeLatLngsForPixels"), METHOD(&NativeMapView::addPolylines, "nativeAddPolylines"), METHOD(&NativeMapView::addPolygons, "nativeAddPolygons"), METHOD(&NativeMapView::updatePolyline, "nativeUpdatePolyline"), METHOD(&NativeMapView::updatePolygon, "nativeUpdatePolygon"), METHOD(&NativeMapView::removeAnnotations, "nativeRemoveAnnotations"), METHOD(&NativeMapView::addAnnotationIcon, "nativeAddAnnotationIcon"), METHOD(&NativeMapView::removeAnnotationIcon, "nativeRemoveAnnotationIcon"), METHOD(&NativeMapView::getTopOffsetPixelsForAnnotationSymbol, "nativeGetTopOffsetPixelsForAnnotationSymbol"), METHOD(&NativeMapView::getTransitionOptions, "nativeGetTransitionOptions"), METHOD(&NativeMapView::setTransitionOptions, "nativeSetTransitionOptions"), METHOD(&NativeMapView::queryPointAnnotations, "nativeQueryPointAnnotations"), METHOD(&NativeMapView::queryShapeAnnotations, "nativeQueryShapeAnnotations"), METHOD(&NativeMapView::queryRenderedFeaturesForPoint, "nativeQueryRenderedFeaturesForPoint"), METHOD(&NativeMapView::queryRenderedFeaturesForBox, "nativeQueryRenderedFeaturesForBox"), METHOD(&NativeMapView::setFeatureState, "nativeSetFeatureState"), METHOD(&NativeMapView::getFeatureState, "nativeGetFeatureState"), METHOD(&NativeMapView::removeFeatureState, "nativeRemoveFeatureState"), METHOD(&NativeMapView::getLight, "nativeGetLight"), METHOD(&NativeMapView::getLayers, "nativeGetLayers"), METHOD(&NativeMapView::getLayer, "nativeGetLayer"), METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"), METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"), METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"), METHOD(&NativeMapView::setRasterParticleColor, "nativeSetRasterParticleColor"), METHOD(&NativeMapView::setRasterParticleTrailColor, "nativeSetRasterParticleTrailColor"), METHOD(&NativeMapView::setRasterArraySourceTargetFps, "nativeSetRasterArraySourceTargetFps"), METHOD(&NativeMapView::getRasterArraySourceTargetFps, "nativeGetRasterArraySourceTargetFps"), METHOD(&NativeMapView::setWeatherColorStops, "nativeSetWeatherColorStops"), METHOD(&NativeMapView::getWeatherColorStopCapacity, "nativeGetWeatherColorStopCapacity"), METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"), METHOD(&NativeMapView::repeatRasterArraySourceFieldWindow, "nativeRepeatRasterArraySourceFieldWindow"), METHOD(&NativeMapView::submitRasterArraySourceHostField, "nativeSubmitRasterArraySourceHostField"), METHOD(&NativeMapView::clearRasterArraySourceHostFields, "nativeClearRasterArraySourceHostFields"), METHOD(&NativeMapView::seekRasterArraySourceTimelineToTimestamp, "nativeSeekRasterArraySourceTimelineToTimestamp"), METHOD(&NativeMapView::controlRasterArraySourceTimeline, "nativeControlRasterArraySourceTimeline"), METHOD(&NativeMapView::addLayer, "nativeAddLayer"), METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"), METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"), METHOD(&NativeMapView::removeLayerAt, "nativeRemoveLayerAt"), METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"), METHOD(&NativeMapView::getSources, "nativeGetSources"), METHOD(&NativeMapView::getSource, "nativeGetSource"), METHOD(&NativeMapView::addSource, "nativeAddSource"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), METHOD(&NativeMapView::addImages, "nativeAddImages"), METHOD(&NativeMapView::removeImage, "nativeRemoveImage"), METHOD(&NativeMapView::getImage, "nativeGetImage"), METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"), METHOD(&NativeMapView::setPrefetchTiles, "nativeSetPrefetchTiles"), METHOD(&NativeMapView::getPrefetchTiles, "nativeGetPrefetchTiles"), METHOD(&NativeMapView::setPrefetchZoomDelta, "nativeSetPrefetchZoomDelta"), METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"), METHOD(&NativeMapView::setTileCacheEnabled, "nativeSetTileCacheEnabled"), METHOD(&NativeMapView::getTileCacheEnabled, "nativeGetTileCacheEnabled"), METHOD(&NativeMapView::setTileLodMinRadius, "nativeSetTileLodMinRadius"), METHOD(&NativeMapView::getTileLodMinRadius, "nativeGetTileLodMinRadius"), METHOD(&NativeMapView::setTileLodScale, "nativeSetTileLodScale"), METHOD(&NativeMapView::getTileLodScale, "nativeGetTileLodScale"), METHOD(&NativeMapView::setTileLodPitchThreshold, "nativeSetTileLodPitchThreshold"), METHOD(&NativeMapView::getTileLodPitchThreshold, "nativeGetTileLodPitchThreshold"), METHOD(&NativeMapView::setTileLodZoomShift, "nativeSetTileLodZoomShift"), METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"), METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"), METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"), METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"), METHOD(&NativeMapView::setFrustumOffset, "nativeSetFrustumOffset")); } void NativeMapView::onRegisterShaders(gfx::ShaderRegistry&) {}; // Shader compilation void NativeMapView::onPreCompileShader(shaders::BuiltIn id, gfx::Backend::Type type, const std::string& additionalDefines) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onPreCompileShader = javaClass.GetMethod(*_env, "onPreCompileShader"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onPreCompileShader, static_cast(id), static_cast(type), jni::Make(*_env, additionalDefines)); } } void NativeMapView::onPostCompileShader(shaders::BuiltIn id, gfx::Backend::Type type, const std::string& additionalDefines) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onPostCompileShader = javaClass.GetMethod( *_env, "onPostCompileShader"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onPostCompileShader, static_cast(id), static_cast(type), jni::Make(*_env, additionalDefines)); } } void NativeMapView::onShaderCompileFailed(shaders::BuiltIn id, gfx::Backend::Type type, const std::string& additionalDefines) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onShaderCompileFailed = javaClass.GetMethod( *_env, "onShaderCompileFailed"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onShaderCompileFailed, static_cast(id), static_cast(type), jni::Make(*_env, additionalDefines)); } } // Glyph requests void NativeMapView::onGlyphsLoaded(const FontStack& stack, const GlyphRange& range) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onGlyphsLoaded = javaClass.GetMethod, jni::jint, jni::jint)>( *_env, "onGlyphsLoaded"); auto fontStack = jni::Array::New(*_env, stack.size()); for (std::size_t i = 0; i < stack.size(); i++) { fontStack.Set(*_env, i, jni::Make(*_env, stack.at(i))); } auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onGlyphsLoaded, fontStack, range.first, range.second); } } void NativeMapView::onGlyphsError(const FontStack& stack, const GlyphRange& range, std::exception_ptr) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onGlyphsError = javaClass.GetMethod, jni::jint, jni::jint)>( *_env, "onGlyphsError"); auto fontStack = jni::Array::New(*_env, stack.size()); for (std::size_t i = 0; i < stack.size(); i++) { fontStack.Set(*_env, i, jni::Make(*_env, stack.at(i))); } auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onGlyphsError, fontStack, range.first, range.second); } } void NativeMapView::onGlyphsRequested(const FontStack& stack, const GlyphRange& range) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onGlyphsRequested = javaClass.GetMethod, jni::jint, jni::jint)>( *_env, "onGlyphsRequested"); auto fontStack = jni::Array::New(*_env, stack.size()); for (std::size_t i = 0; i < stack.size(); i++) { fontStack.Set(*_env, i, jni::Make(*_env, stack.at(i))); } auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onGlyphsRequested, fontStack, range.first, range.second); } } // Tile requests void NativeMapView::onTileAction(mbgl::TileOperation op, const OverscaledTileID& id, const std::string& sourceID) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onTileAction = javaClass.GetMethod, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::String)>( *_env, "onTileAction"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onTileAction, mbgl::android::TileOperation::Create(*_env, op), static_cast(id.canonical.x), static_cast(id.canonical.y), static_cast(id.canonical.z), static_cast(id.wrap), static_cast(id.overscaledZ), jni::Make(*_env, sourceID)); } } // Sprite requests void NativeMapView::onSpriteLoaded(const std::optional& sprite) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onSpriteLoaded = javaClass.GetMethod(*_env, "onSpriteLoaded"); auto weakReference = javaPeer.get(*_env); if (weakReference) { if (sprite) { weakReference.Call(*_env, onSpriteLoaded, jni::Make(*_env, sprite->id), jni::Make(*_env, sprite->spriteURL)); } else { weakReference.Call( *_env, onSpriteLoaded, jni::Make(*_env, ""), jni::Make(*_env, "")); } } } void NativeMapView::onSpriteError(const std::optional& sprite, std::exception_ptr ex) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onSpriteError = javaClass.GetMethod(*_env, "onSpriteError"); auto weakReference = javaPeer.get(*_env); if (weakReference) { if (sprite) { weakReference.Call(*_env, onSpriteError, jni::Make(*_env, sprite->id), jni::Make(*_env, sprite->spriteURL)); } else { weakReference.Call( *_env, onSpriteError, jni::Make(*_env, ""), jni::Make(*_env, "")); } } } void NativeMapView::onSpriteRequested(const std::optional& sprite) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onSpriteRequested = javaClass.GetMethod(*_env, "onSpriteRequested"); auto weakReference = javaPeer.get(*_env); if (weakReference) { if (sprite) { weakReference.Call(*_env, onSpriteRequested, jni::Make(*_env, sprite->id), jni::Make(*_env, sprite->spriteURL)); } else { weakReference.Call( *_env, onSpriteRequested, jni::Make(*_env, ""), jni::Make(*_env, "")); } } } void NativeMapView::onRenderError(std::exception_ptr) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onRenderError = javaClass.GetMethod(*_env, "onRenderError"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onRenderError); } } } // namespace android } // namespace mbgl