#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 // 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/conversion/feature.hpp" #include "jni.hpp" #include "attach_env.hpp" #include "map_renderer.hpp" #include "android_renderer_frontend.hpp" #include "file_source.hpp" #include "bitmap.hpp" #include "run_loop_impl.hpp" #include "java/util.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" #include "style/light.hpp" #include "bitmap_factory.hpp" namespace mbgl { namespace android { NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object _obj, jni::Object jFileSource, jni::Object jMapRenderer, jni::jfloat _pixelRatio) : javaPeer(_obj.NewWeakGlobalRef(_env)) , mapRenderer(MapRenderer::getNativePeer(_env, jMapRenderer)) , pixelRatio(_pixelRatio) , threadPool(sharedThreadPool()) { // Get a reference to the JavaVM for callbacks if (_env.GetJavaVM(&vm) < 0) { _env.ExceptionDescribe(); return; } // Get native peer for file source mbgl::FileSource& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource); // Create a renderer frontend rendererFrontend = std::make_unique(mapRenderer); // Create the core map map = std::make_unique(*rendererFrontend, *this, mbgl::Size{ static_cast(width), static_cast(height) }, pixelRatio, fileSource, *threadPool, MapMode::Continuous, ConstrainMode::HeightOnly, ViewportMode::Default); } /** * Called through NativeMapView#destroy() */ NativeMapView::~NativeMapView() { map.reset(); vm = nullptr; } /** * From mbgl::RendererBackend. Callback to java NativeMapView#onMapChanged(int). * * May be called from any thread */ void NativeMapView::notifyMapChange(mbgl::MapChange change) { assert(vm != nullptr); android::UniqueEnv _env = android::AttachEnv(); static auto onMapChanged = javaClass.GetMethod(*_env, "onMapChanged"); javaPeer->Call(*_env, onMapChanged, (int) change); } void NativeMapView::onCameraWillChange(MapObserver::CameraChangeMode mode) { if (mode == MapObserver::CameraChangeMode::Immediate) { notifyMapChange(MapChange::MapChangeRegionWillChange); } else { notifyMapChange(MapChange::MapChangeRegionWillChangeAnimated); } } void NativeMapView::onCameraIsChanging() { notifyMapChange(MapChange::MapChangeRegionIsChanging); } void NativeMapView::onCameraDidChange(MapObserver::CameraChangeMode mode) { if (mode == MapObserver::CameraChangeMode::Immediate) { notifyMapChange(MapChange::MapChangeRegionDidChange); } else { notifyMapChange(MapChange::MapChangeRegionDidChangeAnimated); } } void NativeMapView::onWillStartLoadingMap() { notifyMapChange(MapChange::MapChangeWillStartLoadingMap); } void NativeMapView::onDidFinishLoadingMap() { notifyMapChange(MapChange::MapChangeDidFinishLoadingMap); } void NativeMapView::onDidFailLoadingMap(std::exception_ptr) { notifyMapChange(MapChange::MapChangeDidFailLoadingMap); } void NativeMapView::onWillStartRenderingFrame() { notifyMapChange(MapChange::MapChangeWillStartRenderingFrame); } void NativeMapView::onDidFinishRenderingFrame(MapObserver::RenderMode mode) { if (mode == MapObserver::RenderMode::Partial) { notifyMapChange(MapChange::MapChangeDidFinishRenderingFrame); } else { notifyMapChange(MapChange::MapChangeDidFinishRenderingFrameFullyRendered); } } void NativeMapView::onWillStartRenderingMap() { notifyMapChange(MapChange::MapChangeWillStartRenderingMap); } void NativeMapView::onDidFinishRenderingMap(MapObserver::RenderMode mode) { if (mode == MapObserver::RenderMode::Partial) { notifyMapChange(MapChange::MapChangeDidFinishRenderingMap); } else { notifyMapChange(MapChange::MapChangeDidFinishRenderingMapFullyRendered); } } void NativeMapView::onDidFinishLoadingStyle() { notifyMapChange(MapChange::MapChangeDidFinishLoadingStyle); } void NativeMapView::onSourceChanged(mbgl::style::Source&) { notifyMapChange(MapChange::MapChangeSourceDidChange); } // 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::String NativeMapView::getStyleUrl(jni::JNIEnv& env) { return jni::Make(env, map->getStyle().getURL()); } void NativeMapView::setStyleUrl(jni::JNIEnv& env, jni::String url) { map->getStyle().loadURL(jni::Make(env, url)); } jni::String NativeMapView::getStyleJson(jni::JNIEnv& env) { return jni::Make(env, map->getStyle().getJSON()); } void NativeMapView::setStyleJson(jni::JNIEnv& env, jni::String json) { map->getStyle().loadJSON(jni::Make(env, json)); } void NativeMapView::setLatLngBounds(jni::JNIEnv& env, jni::Object jBounds) { if (jBounds) { map->setLatLngBounds(mbgl::android::LatLngBounds::getLatLngBounds(env, jBounds)); } else { map->setLatLngBounds(mbgl::LatLngBounds::world()); } } 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.25, 0.46, 0.45, 0.94}); } map->moveBy({dx, dy}, animationOptions); } void NativeMapView::jumpTo(jni::JNIEnv&, jni::jdouble angle, jni::jdouble latitude, jni::jdouble longitude, jni::jdouble pitch, jni::jdouble zoom) { mbgl::CameraOptions options; if (angle != -1) { options.angle = -angle * util::DEG2RAD; } options.center = mbgl::LatLng(latitude, longitude); options.padding = insets; if (pitch != -1) { options.pitch = pitch * util::DEG2RAD; } if (zoom != -1) { options.zoom = zoom; } map->jumpTo(options); } void NativeMapView::easeTo(jni::JNIEnv&, jni::jdouble angle, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration, jni::jdouble pitch, jni::jdouble zoom, jni::jboolean easing) { mbgl::CameraOptions cameraOptions; if (angle != -1) { cameraOptions.angle = -angle * util::DEG2RAD; } cameraOptions.center = mbgl::LatLng(latitude, longitude); cameraOptions.padding = insets; if (pitch != -1) { cameraOptions.pitch = pitch * util::DEG2RAD; } 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&, jni::jdouble angle, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration, jni::jdouble pitch, jni::jdouble zoom) { mbgl::CameraOptions cameraOptions; if (angle != -1) { cameraOptions.angle = -angle * util::DEG2RAD; } cameraOptions.center = mbgl::LatLng(latitude, longitude); cameraOptions.padding = insets; if (pitch != -1) { cameraOptions.pitch = pitch * util::DEG2RAD; } if (zoom != -1) { cameraOptions.zoom = zoom; } mbgl::AnimationOptions animationOptions; animationOptions.duration.emplace(mbgl::Milliseconds(duration)); map->flyTo(cameraOptions, animationOptions); } jni::Object NativeMapView::getLatLng(JNIEnv& env) { return LatLng::New(env, map->getLatLng(insets)); } void NativeMapView::setLatLng(jni::JNIEnv&, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration) { map->setLatLng(mbgl::LatLng(latitude, longitude), insets, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::Object NativeMapView::getCameraForLatLngBounds(jni::JNIEnv& env, jni::Object jBounds) { return CameraPosition::New(env, map->cameraForLatLngBounds(mbgl::android::LatLngBounds::getLatLngBounds(env, jBounds), insets)); } void NativeMapView::setReachability(jni::JNIEnv&, jni::jboolean reachable) { if (reachable) { mbgl::NetworkStatus::Reachable(); } } void NativeMapView::resetPosition(jni::JNIEnv&) { map->resetPosition(); } jni::jdouble NativeMapView::getPitch(jni::JNIEnv&) { return map->getPitch(); } void NativeMapView::setPitch(jni::JNIEnv&, jni::jdouble pitch, jni::jlong duration) { map->setPitch(pitch, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } void NativeMapView::setZoom(jni::JNIEnv&, jni::jdouble zoom, jni::jdouble x, jni::jdouble y, jni::jlong duration) { map->setZoom(zoom, mbgl::ScreenCoordinate{x,y}, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::jdouble NativeMapView::getZoom(jni::JNIEnv&) { return map->getZoom(); } void NativeMapView::resetZoom(jni::JNIEnv&) { map->resetZoom(); } void NativeMapView::setMinZoom(jni::JNIEnv&, jni::jdouble zoom) { map->setMinZoom(zoom); } jni::jdouble NativeMapView::getMinZoom(jni::JNIEnv&) { return map->getMinZoom(); } void NativeMapView::setMaxZoom(jni::JNIEnv&, jni::jdouble zoom) { map->setMaxZoom(zoom); } jni::jdouble NativeMapView::getMaxZoom(jni::JNIEnv&) { return map->getMaxZoom(); } 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->setBearing(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 center(cx, cy); map->setBearing(degrees, center, mbgl::AnimationOptions{mbgl::Milliseconds(duration)}); } jni::jdouble NativeMapView::getBearing(jni::JNIEnv&) { return map->getBearing(); } void NativeMapView::resetNorth(jni::JNIEnv&) { map->resetNorth(); } void NativeMapView::setVisibleCoordinateBounds(JNIEnv& env, jni::Array> coordinates, 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++) { auto latLng = coordinates.Get(env, i); latLngs.push_back(LatLng::getLatLng(env, latLng)); jni::DeleteLocalRef(env, latLng); } 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) { // convert from degrees to radians cameraOptions.angle = (-direction * M_PI) / 180; } 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::setContentPadding(JNIEnv&, double top, double left, double bottom, double right) { insets = {top, left, bottom, right}; } 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 onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); javaPeer->Call(*_env, onSnapshotReady, bitmap); }); } jni::Object NativeMapView::getCameraPosition(jni::JNIEnv& env) { return CameraPosition::New(env, map->getCameraOptions(insets)); } void NativeMapView::updateMarker(jni::JNIEnv& env, jni::jlong markerId, jni::jdouble lat, jni::jdouble lon, 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::Array NativeMapView::addMarkers(jni::JNIEnv& env, 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++) { jni::Object marker = jmarkers.Get(env, i); ids.push_back(map->addAnnotation(mbgl::SymbolAnnotation { Marker::getPosition(env, marker), Marker::getIconId(env, marker) })); jni::DeleteLocalRef(env, marker); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } void NativeMapView::onLowMemory(JNIEnv&) { rendererFrontend->onLowMemory(); } 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); } void NativeMapView::cycleDebugOptions(JNIEnv&) { map->cycleDebugOptions(); } jni::jboolean NativeMapView::getDebug(JNIEnv&) { return map->getDebug() != DebugOptions::NoDebug; } 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::Object 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::Object NativeMapView::latLngForProjectedMeters(JNIEnv& env, jdouble northing, jdouble easting) { return LatLng::New(env, mbgl::Projection::latLngForProjectedMeters(mbgl::ProjectedMeters(northing, easting))); } jni::Object 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)); } jni::Object NativeMapView::latLngForPixel(JNIEnv& env, jfloat x, jfloat y) { return LatLng::New(env, map->latLngForPixel(mbgl::ScreenCoordinate(x, y))); } jni::Array NativeMapView::addPolylines(JNIEnv& env, 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++) { auto polyline = polylines.Get(env, i); mbgl::LineAnnotation annotation = Polyline::toAnnotation(env, polyline); ids.push_back(map->addAnnotation(annotation)); jni::DeleteLocalRef(env, polyline); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } jni::Array NativeMapView::addPolygons(JNIEnv& env, 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++) { auto polygon = polygons.Get(env, i); mbgl::FillAnnotation annotation = Polygon::toAnnotation(env, polygon); ids.push_back(map->addAnnotation(annotation)); jni::DeleteLocalRef(env, polygon); } auto result = jni::Array::New(env, len); result.SetRegion>(env, 0, ids); return result; } //TODO: Move to Polyline class and make native peer void NativeMapView::updatePolyline(JNIEnv& env, jlong polylineId, jni::Object polyline) { mbgl::LineAnnotation annotation = Polyline::toAnnotation(env, polyline); map->updateAnnotation(polylineId, annotation); } //TODO: Move to Polygon class and make native peer void NativeMapView::updatePolygon(JNIEnv& env, jlong polygonId, jni::Object polygon) { mbgl::FillAnnotation annotation = Polygon::toAnnotation(env, polygon); map->updateAnnotation(polygonId, annotation); } void NativeMapView::removeAnnotations(JNIEnv& env, 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, jni::String symbol, jint w, jint h, jfloat scale, 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::SpriteImageException("Sprite image pixel count mismatch"); } jni::GetArrayRegion(env, *jpixels, 0, size, reinterpret_cast(premultipliedImage.data.get())); map->addAnnotationImage(std::make_unique( symbolName, std::move(premultipliedImage), float(scale))); } void NativeMapView::removeAnnotationIcon(JNIEnv& env, jni::String symbol) { const std::string symbolName = jni::Make(env, symbol); map->removeAnnotationImage(symbolName); } jdouble NativeMapView::getTopOffsetPixelsForAnnotationSymbol(JNIEnv& env, jni::String symbolName) { return map->getTopOffsetPixelsForAnnotationImage(jni::Make(env, symbolName)); } jlong NativeMapView::getTransitionDuration(JNIEnv&) { const auto transitionOptions = map->getStyle().getTransitionOptions(); return std::chrono::duration_cast(transitionOptions.duration.value_or(mbgl::Duration::zero())).count(); } void NativeMapView::setTransitionDuration(JNIEnv&, jlong duration) { auto transitionOptions = map->getStyle().getTransitionOptions(); transitionOptions.duration.emplace(mbgl::Milliseconds(duration)); map->getStyle().setTransitionOptions(transitionOptions); } jlong NativeMapView::getTransitionDelay(JNIEnv&) { const auto transitionOptions = map->getStyle().getTransitionOptions(); return std::chrono::duration_cast(transitionOptions.delay.value_or(mbgl::Duration::zero())).count(); } void NativeMapView::setTransitionDelay(JNIEnv&, jlong delay) { auto transitionOptions = map->getStyle().getTransitionOptions(); transitionOptions.delay.emplace(mbgl::Milliseconds(delay)); map->getStyle().setTransitionOptions(transitionOptions); } jni::Array NativeMapView::queryPointAnnotations(JNIEnv& env, 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); // 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::Array> NativeMapView::queryRenderedFeaturesForPoint(JNIEnv& env, jni::jfloat x, jni::jfloat y, jni::Array layerIds, jni::Array> jfilter) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; mbgl::optional> layers; if (layerIds != nullptr && layerIds.Length(env) > 0) { layers = android::conversion::toVector(env, layerIds); } mapbox::geometry::point point = {x, y}; return *convert>, std::vector>( env, rendererFrontend->queryRenderedFeatures(point, { layers, toFilter(env, jfilter) })); } jni::Array> NativeMapView::queryRenderedFeaturesForBox(JNIEnv& env, jni::jfloat left, jni::jfloat top, jni::jfloat right, jni::jfloat bottom, jni::Array layerIds, jni::Array> jfilter) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; mbgl::optional> layers; if (layerIds != nullptr && layerIds.Length(env) > 0) { layers = toVector(env, layerIds); } mapbox::geometry::box box = { mapbox::geometry::point{ left, top}, mapbox::geometry::point{ right, bottom } }; return *convert>, std::vector>( env, rendererFrontend->queryRenderedFeatures(box, { layers, toFilter(env, jfilter) })); } jni::Object NativeMapView::getLight(JNIEnv& env) { mbgl::style::Light* light = map->getStyle().getLight(); if (light) { return jni::Object(Light::createJavaLightPeer(env, *map, *light)); } else { return jni::Object(); } } jni::Array> NativeMapView::getLayers(JNIEnv& env) { // Get the core layers std::vector layers = map->getStyle().getLayers(); // Convert jni::Array> jLayers = jni::Array>::New(env, layers.size(), Layer::javaClass); int index = 0; for (auto layer : layers) { auto jLayer = jni::Object(createJavaLayerPeer(env, *map, *layer)); jLayers.Set(env, index, jLayer); jni::DeleteLocalRef(env, jLayer); index++; } return jLayers; } jni::Object NativeMapView::getLayer(JNIEnv& env, 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::Object(); } // Create and return the layer's native peer return jni::Object(createJavaLayerPeer(env, *map, *coreLayer)); } void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, jni::String before) { assert(nativeLayerPtr != 0); Layer *layer = reinterpret_cast(nativeLayerPtr); try { layer->addToMap(*map, before ? mbgl::optional(jni::Make(env, before)) : mbgl::optional()); } catch (const std::runtime_error& error) { jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/layers/CannotAddLayerException"), error.what()); } } void NativeMapView::addLayerAbove(JNIEnv& env, jlong nativeLayerPtr, 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 mbgl::optional before; if (index + 1 > layers.size()) { // Not found jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/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->addToMap(*map, before); } catch (const std::runtime_error& error) { jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/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: %i", index); jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/layers/CannotAddLayerException"), std::string("Invalid index").c_str()); return; } // Insert it below the current at that index try { layer->addToMap(*map, layers.at(index)->getID()); } catch (const std::runtime_error& error) { jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/layers/CannotAddLayerException"), error.what()); } } /** * Remove by layer id. */ jni::Object NativeMapView::removeLayerById(JNIEnv& env, jni::String id) { std::unique_ptr coreLayer = map->getStyle().removeLayer(jni::Make(env, id)); if (coreLayer) { return jni::Object(createJavaLayerPeer(env, *map, std::move(coreLayer))); } else { return jni::Object(); } } /** * Remove layer at index. */ jni::Object 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: %i", index); return jni::Object(); } std::unique_ptr coreLayer = map->getStyle().removeLayer(layers.at(index)->getID()); if (coreLayer) { return jni::Object(createJavaLayerPeer(env, *map, std::move(coreLayer))); } else { return jni::Object(); } } /** * Remove with wrapper object id. Ownership is transferred back to the wrapper */ void NativeMapView::removeLayer(JNIEnv&, jlong layerPtr) { assert(layerPtr != 0); mbgl::android::Layer *layer = reinterpret_cast(layerPtr); std::unique_ptr coreLayer = map->getStyle().removeLayer(layer->get().getID()); if (coreLayer) { layer->setLayer(std::move(coreLayer)); } } jni::Array> NativeMapView::getSources(JNIEnv& env) { // Get the core sources std::vector sources = map->getStyle().getSources(); // Convert jni::Array> jSources = jni::Array>::New(env, sources.size(), Source::javaClass); int index = 0; for (auto source : sources) { auto jSource = jni::Object(createJavaSourcePeer(env, *rendererFrontend, *source)); jSources.Set(env, index, jSource); jni::DeleteLocalRef(env, jSource); index++; } return jSources; } jni::Object NativeMapView::getSource(JNIEnv& env, 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::Object(); } // Create and return the source's native peer return jni::Object(createJavaSourcePeer(env, *rendererFrontend, *coreSource)); } void NativeMapView::addSource(JNIEnv& env, jni::jlong sourcePtr) { assert(sourcePtr != 0); Source *source = reinterpret_cast(sourcePtr); try { source->addToMap(*map); source->setRendererFrontend(*rendererFrontend); } catch (const std::runtime_error& error) { jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/sources/CannotAddSourceException"), error.what()); } } jni::Object NativeMapView::removeSourceById(JNIEnv& env, jni::String id) { std::unique_ptr coreSource = map->getStyle().removeSource(jni::Make(env, id)); if (coreSource) { return jni::Object(createJavaSourcePeer(env, *rendererFrontend, *coreSource)); } else { return jni::Object(); } } void NativeMapView::removeSource(JNIEnv&, jlong sourcePtr) { assert(sourcePtr != 0); mbgl::android::Source *source = reinterpret_cast(sourcePtr); std::unique_ptr coreSource = map->getStyle().removeSource(source->get().getID()); if (coreSource) { source->setSource(std::move(coreSource)); } } void NativeMapView::addImage(JNIEnv& env, jni::String name, jni::jint w, jni::jint h, jni::jfloat scale, jni::Array pixels) { jni::NullCheck(env, &pixels); std::size_t size = pixels.Length(env); mbgl::PremultipliedImage premultipliedImage({ static_cast(w), static_cast(h) }); if (premultipliedImage.bytes() != uint32_t(size)) { throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); } jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast(premultipliedImage.data.get())); map->getStyle().addImage(std::make_unique( jni::Make(env, name), std::move(premultipliedImage), float(scale))); } void NativeMapView::removeImage(JNIEnv& env, jni::String name) { map->getStyle().removeImage(jni::Make(env, name)); } jni::Object NativeMapView::getImage(JNIEnv& env, jni::String name) { const mbgl::style::Image *image = map->getStyle().getImage(jni::Make(env, name)); if (image) { return Bitmap::CreateBitmap(env, image->getImage()); } else { return jni::Object(); } } void NativeMapView::setPrefetchesTiles(JNIEnv&, jni::jboolean enable) { map->setPrefetchZoomDelta(enable ? util::DEFAULT_PREFETCH_ZOOM_DELTA : uint8_t(0)); } jni::jboolean NativeMapView::getPrefetchesTiles(JNIEnv&) { return jni::jboolean(map->getPrefetchZoomDelta() > 0); } // Static methods // jni::Class NativeMapView::javaClass; void NativeMapView::registerNative(jni::JNIEnv& env) { // Lookup the class NativeMapView::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) // Register the peer jni::RegisterNativePeer(env, NativeMapView::javaClass, "nativePtr", std::make_unique, jni::Object, jni::Object, jni::jfloat>, "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::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::rotateBy, "nativeRotateBy"), METHOD(&NativeMapView::setBearing, "nativeSetBearing"), METHOD(&NativeMapView::setBearingXY, "nativeSetBearingXY"), METHOD(&NativeMapView::getBearing, "nativeGetBearing"), METHOD(&NativeMapView::resetNorth, "nativeResetNorth"), METHOD(&NativeMapView::setVisibleCoordinateBounds, "nativeSetVisibleCoordinateBounds"), METHOD(&NativeMapView::setContentPadding, "nativeSetContentPadding"), METHOD(&NativeMapView::scheduleSnapshot, "nativeTakeSnapshot"), METHOD(&NativeMapView::getCameraPosition, "nativeGetCameraPosition"), METHOD(&NativeMapView::updateMarker, "nativeUpdateMarker"), METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"), METHOD(&NativeMapView::setDebug, "nativeSetDebug"), METHOD(&NativeMapView::cycleDebugOptions, "nativeCycleDebugOptions"), METHOD(&NativeMapView::getDebug, "nativeGetDebug"), METHOD(&NativeMapView::isFullyLoaded, "nativeIsFullyLoaded"), METHOD(&NativeMapView::onLowMemory, "nativeOnLowMemory"), METHOD(&NativeMapView::getMetersPerPixelAtLatitude, "nativeGetMetersPerPixelAtLatitude"), METHOD(&NativeMapView::projectedMetersForLatLng, "nativeProjectedMetersForLatLng"), METHOD(&NativeMapView::pixelForLatLng, "nativePixelForLatLng"), METHOD(&NativeMapView::latLngForProjectedMeters, "nativeLatLngForProjectedMeters"), METHOD(&NativeMapView::latLngForPixel, "nativeLatLngForPixel"), 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::getTransitionDuration, "nativeGetTransitionDuration"), METHOD(&NativeMapView::setTransitionDuration, "nativeSetTransitionDuration"), METHOD(&NativeMapView::getTransitionDelay, "nativeGetTransitionDelay"), METHOD(&NativeMapView::setTransitionDelay, "nativeSetTransitionDelay"), METHOD(&NativeMapView::queryPointAnnotations, "nativeQueryPointAnnotations"), METHOD(&NativeMapView::queryRenderedFeaturesForPoint, "nativeQueryRenderedFeaturesForPoint"), METHOD(&NativeMapView::queryRenderedFeaturesForBox, "nativeQueryRenderedFeaturesForBox"), METHOD(&NativeMapView::getLight, "nativeGetLight"), METHOD(&NativeMapView::getLayers, "nativeGetLayers"), METHOD(&NativeMapView::getLayer, "nativeGetLayer"), METHOD(&NativeMapView::addLayer, "nativeAddLayer"), METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"), METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"), METHOD(&NativeMapView::removeLayerById, "nativeRemoveLayerById"), METHOD(&NativeMapView::removeLayerAt, "nativeRemoveLayerAt"), METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"), METHOD(&NativeMapView::getSources, "nativeGetSources"), METHOD(&NativeMapView::getSource, "nativeGetSource"), METHOD(&NativeMapView::addSource, "nativeAddSource"), METHOD(&NativeMapView::removeSourceById, "nativeRemoveSourceById"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), METHOD(&NativeMapView::removeImage, "nativeRemoveImage"), METHOD(&NativeMapView::getImage, "nativeGetImage"), METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"), METHOD(&NativeMapView::setPrefetchesTiles, "nativeSetPrefetchesTiles"), METHOD(&NativeMapView::getPrefetchesTiles, "nativeGetPrefetchesTiles") ); } } // namespace android } // namespace mbgl