From 465c949153a838bb3159204ab268eb551fbd2e6c Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Thu, 16 Feb 2017 16:16:47 +0200 Subject: [android] jni high level binding refactor --- platform/android/src/native_map_view.cpp | 1163 ++++++++++++++++++++++++------ 1 file changed, 930 insertions(+), 233 deletions(-) (limited to 'platform/android/src/native_map_view.cpp') diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index b36b477f2f..a0bd06345f 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -1,6 +1,4 @@ #include "native_map_view.hpp" -#include "jni.hpp" -#include "default_file_source.hpp" #include #include @@ -11,90 +9,91 @@ #include -#include -#include -#include -#include +#include +#include + +#include + #include +#include #include #include +#include +#include +#include #include #include +#include +#include +#include + +#include "conversion/conversion.hpp" +#include "conversion/collection.hpp" +#include "geometry/conversion/feature.hpp" +#include "jni.hpp" +#include "attach_env.hpp" #include "bitmap.hpp" +#include "run_loop_impl.hpp" +#include "java/util.hpp" namespace mbgl { namespace android { -NativeMapView::NativeMapView(JNIEnv *env_, jobject obj_, float pixelRatio, int availableProcessors_, size_t totalMemory_) - : env(env_), - availableProcessors(availableProcessors_), - totalMemory(totalMemory_), - fileSource(defaultFileSource(mbgl::android::cachePath + "/mbgl-offline.db", mbgl::android::apkPath)), - threadPool(sharedThreadPool()) { - - assert(env_ != nullptr); - assert(obj_ != nullptr); - - if (env->GetJavaVM(&vm) < 0) { - env->ExceptionDescribe(); - return; - } - - obj = env->NewWeakGlobalRef(obj_); - if (obj == nullptr) { - env->ExceptionDescribe(); +NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object _obj, jni::Object jFileSource, + jni::jfloat _pixelRatio, jni::jint _availableProcessors, jni::jlong _totalMemory) : + javaPeer(_obj.NewWeakGlobalRef(_env)), + pixelRatio(_pixelRatio), + availableProcessors(_availableProcessors), + totalMemory(_totalMemory), + threadPool(sharedThreadPool()) { + + // Get a reference to the JavaVM for callbacks + if (_env.GetJavaVM(&vm) < 0) { + _env.ExceptionDescribe(); return; } + // Create the core map map = std::make_unique( *this, mbgl::Size{ static_cast(width), static_cast(height) }, - pixelRatio, fileSource, *threadPool, MapMode::Continuous); + pixelRatio, mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource) + , *threadPool, MapMode::Continuous); + //Calculate a fitting cache size based on device parameters float zoomFactor = map->getMaxZoom() - map->getMinZoom() + 1; float cpuFactor = availableProcessors; float memoryFactor = static_cast(totalMemory) / 1000.0f / 1000.0f / 1000.0f; float sizeFactor = (static_cast(map->getSize().width) / mbgl::util::tileSize) * - (static_cast(map->getSize().height) / mbgl::util::tileSize); - - size_t cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5f; + (static_cast(map->getSize().height) / mbgl::util::tileSize); - map->setSourceTileCacheSize(cacheSize); + map->setSourceTileCacheSize(zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5f); } +/** + * Called through NativeMapView#destroy() + */ NativeMapView::~NativeMapView() { - terminateContext(); - destroySurface(); - terminateDisplay(); - - assert(vm != nullptr); - assert(obj != nullptr); + _terminateContext(); + _destroySurface(); + _terminateDisplay(); map.reset(); - env->DeleteWeakGlobalRef(obj); - - obj = nullptr; - env = nullptr; vm = nullptr; } -mbgl::Size NativeMapView::getFramebufferSize() const { - return { static_cast(fbWidth), static_cast(fbHeight) }; -} - -void NativeMapView::updateViewBinding() { - getContext().bindFramebuffer.setCurrentValue(0); - assert(mbgl::gl::value::BindFramebuffer::Get() == getContext().bindFramebuffer.getCurrentValue()); - getContext().viewport.setCurrentValue({ 0, 0, getFramebufferSize() }); - assert(mbgl::gl::value::Viewport::Get() == getContext().viewport.getCurrentValue()); -} - +/** + * From mbgl::View + */ void NativeMapView::bind() { getContext().bindFramebuffer = 0; getContext().viewport = { 0, 0, getFramebufferSize() }; } +/** + * From mbgl::Backend. + */ void NativeMapView::activate() { if (active++) { return; @@ -123,6 +122,9 @@ void NativeMapView::activate() { } } +/** + * From mbgl::Backend. + */ void NativeMapView::deactivate() { if (--active) { return; @@ -147,17 +149,57 @@ void NativeMapView::deactivate() { } } +/** + * From mbgl::Backend. Callback to java NativeMapView#onInvalidate(). + * + * May be called from any thread + */ void NativeMapView::invalidate() { + android::UniqueEnv _env = android::AttachEnv(); + static auto onInvalidate = javaClass.GetMethod(*_env, "onInvalidate"); + javaPeer->Call(*_env, onInvalidate); +} + +/** + * From mbgl::Backend. Callback to java NativeMapView#onMapChanged(int). + * + * May be called from any thread + */ +void NativeMapView::notifyMapChange(mbgl::MapChange change) { assert(vm != nullptr); - assert(obj != nullptr); - env->CallVoidMethod(obj, onInvalidateId); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - } + android::UniqueEnv _env = android::AttachEnv(); + static auto onMapChanged = javaClass.GetMethod(*_env, "onMapChanged"); + javaPeer->Call(*_env, onMapChanged, (int) change); } -void NativeMapView::render() { +// JNI Methods // + +void NativeMapView::initializeDisplay(jni::JNIEnv&) { + _initializeDisplay(); +} + +void NativeMapView::terminateDisplay(jni::JNIEnv&) { + _terminateDisplay(); +} + +void NativeMapView::initializeContext(jni::JNIEnv&) { + _initializeContext(); +} + +void NativeMapView::terminateContext(jni::JNIEnv&) { + _terminateContext(); +} + +void NativeMapView::createSurface(jni::JNIEnv& env, jni::Object<> _surface) { + _createSurface(ANativeWindow_fromSurface(&env, jni::Unwrap(*_surface))); +} + +void NativeMapView::destroySurface(jni::JNIEnv&) { + _destroySurface(); +} + +void NativeMapView::render(jni::JNIEnv& env) { BackendScope guard(*this); if (framebufferSizeChanged) { @@ -173,13 +215,12 @@ void NativeMapView::render() { // take snapshot auto image = getContext().readFramebuffer(getFramebufferSize()); - auto bitmap = Bitmap::CreateBitmap(*env, std::move(image)); + auto bitmap = Bitmap::CreateBitmap(env, std::move(image)); // invoke Mapview#OnSnapshotReady - env->CallVoidMethod(obj, onSnapshotReadyId, jni::Unwrap(*bitmap)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - } + android::UniqueEnv _env = android::AttachEnv(); + static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); + javaPeer->Call(*_env, onSnapshotReady, bitmap); } if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) { @@ -195,13 +236,611 @@ void NativeMapView::render() { } } -mbgl::Map &NativeMapView::getMap() { return *map; } +void NativeMapView::update(jni::JNIEnv&) { + invalidate(); +} + +void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) { + width = w; + height = h; + map->setSize({ static_cast(width), static_cast(height) }); +} + +void NativeMapView::resizeFramebuffer(jni::JNIEnv&, int w, int h) { + fbWidth = w; + fbHeight = h; + framebufferSizeChanged = true; + invalidate(); +} + +jni::String NativeMapView::getStyleUrl(jni::JNIEnv& env) { + return jni::Make(env, map->getStyleURL()); +} + +void NativeMapView::setStyleUrl(jni::JNIEnv& env, jni::String url) { + map->setStyleURL(jni::Make(env, url)); +} + +jni::String NativeMapView::getStyleJson(jni::JNIEnv& env) { + return jni::Make(env, map->getStyleJSON()); +} + +void NativeMapView::setStyleJson(jni::JNIEnv& env, jni::String json) { + map->setStyleJSON(jni::Make(env, json)); +} + +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, 0.3, 0.6, 1.0 }); + } + 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 * M_PI) / 180; + } + options.center = mbgl::LatLng(latitude, longitude); + options.padding = insets; + if (pitch != -1) { + options.pitch = pitch * M_PI / 180; + } + 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 * M_PI) / 180; + } + cameraOptions.center = mbgl::LatLng(latitude, longitude); + cameraOptions.padding = insets; + if (pitch != -1) { + cameraOptions.pitch = pitch * M_PI / 180; + } + if (zoom != -1) { + cameraOptions.zoom = zoom; + } + + mbgl::AnimationOptions animationOptions; + animationOptions.duration.emplace(mbgl::Duration(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 * M_PI) / 180 ; + } + cameraOptions.center = mbgl::LatLng(latitude, longitude); + cameraOptions.padding = insets; + if (pitch != -1) { + cameraOptions.pitch = pitch * M_PI / 180; + } + if (zoom != -1) { + cameraOptions.zoom = zoom; + } + + mbgl::AnimationOptions animationOptions; + animationOptions.duration.emplace(mbgl::Duration(duration)); + map->flyTo(cameraOptions, animationOptions); +} + +jni::Object NativeMapView::getLatLng(JNIEnv& env) { + mbgl::LatLng latLng = map->getLatLng(insets); + return LatLng::New(env, latLng.latitude, latLng.longitude); +} + +void NativeMapView::setLatLng(jni::JNIEnv&, jni::jdouble latitude, jni::jdouble longitude, jni::jlong duration) { + map->setLatLng(mbgl::LatLng(latitude, longitude), insets, mbgl::Milliseconds(duration)); +} + +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::Milliseconds(duration)); +} + +void NativeMapView::scaleBy(jni::JNIEnv&, jni::jdouble ds, jni::jdouble cx, jni::jdouble cy, jni::jlong duration) { + mbgl::ScreenCoordinate center(cx, cy); + map->scaleBy(ds, center, mbgl::Milliseconds(duration)); +} + +void NativeMapView::setScale(jni::JNIEnv&, jni::jdouble scale, jni::jdouble cx, jni::jdouble cy, jni::jlong duration) { + mbgl::ScreenCoordinate center(cx, cy); + map->setScale(scale, center, mbgl::Milliseconds(duration)); +} + +jni::jdouble NativeMapView::getScale(jni::JNIEnv&) { + return map->getScale(); +} + +void NativeMapView::setZoom(jni::JNIEnv&, jni::jdouble zoom, jni::jlong duration) { + map->setZoom(zoom, 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::Milliseconds(duration)); +} + +void NativeMapView::setBearing(jni::JNIEnv&, jni::jdouble degrees, jni::jlong duration) { + map->setBearing(degrees, 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::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&) { + snapshot = true; +} + +void NativeMapView::enableFps(jni::JNIEnv&, jni::jboolean enable) { + fpsEnabled = enable; +} + +jni::Array NativeMapView::getCameraValues(jni::JNIEnv& env) { + //Create buffer with values + jdouble buf[5]; + mbgl::LatLng latLng = map->getLatLng(insets); + buf[0] = latLng.latitude; + buf[1] = latLng.longitude; + buf[2] = -map->getBearing(); + buf[3] = map->getPitch(); + buf[4] = map->getZoom(); + + //Convert to Java array + auto output = jni::Array::New(env, 5); + jni::SetArrayRegion(env, *output, 0, 5, buf); + + return output; +} + +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&) { + map->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); + fpsEnabled = debug; +} + +void NativeMapView::cycleDebugOptions(JNIEnv&) { + map->cycleDebugOptions(); + fpsEnabled = map->getDebug() != DebugOptions::NoDebug; +} + +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 map->getMetersPerPixelAtLatitude(lat, zoom); +} + +jni::Object NativeMapView::projectedMetersForLatLng(JNIEnv& env, jni::jdouble latitude, jni::jdouble longitude) { + mbgl::ProjectedMeters projectedMeters = map->projectedMetersForLatLng(mbgl::LatLng(latitude, longitude)); + return ProjectedMeters::New(env, projectedMeters.northing, projectedMeters.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::latLngForProjectedMeters(JNIEnv& env, jdouble northing, jdouble easting) { + mbgl::LatLng latLng = map->latLngForProjectedMeters(mbgl::ProjectedMeters(northing, easting)); + return LatLng::New(env, latLng.latitude, latLng.longitude); +} + +jni::Object NativeMapView::latLngForPixel(JNIEnv& env, jfloat x, jfloat y) { + mbgl::LatLng latLng = map->latLngForPixel(mbgl::ScreenCoordinate(x, y)); + return LatLng::New(env, latLng.latitude, latLng.longitude); +} + +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())); + auto iconImage = std::make_shared(std::move(premultipliedImage), float(scale)); + map->addAnnotationIcon(symbolName, iconImage); +} -mbgl::DefaultFileSource& NativeMapView::getFileSource() { - return fileSource; +jdouble NativeMapView::getTopOffsetPixelsForAnnotationSymbol(JNIEnv& env, jni::String symbolName) { + return map->getTopOffsetPixelsForAnnotationIcon(jni::Make(env, symbolName)); } -void NativeMapView::initializeDisplay() { +jlong NativeMapView::getTransitionDuration(JNIEnv&) { + const auto transitionOptions = map->getTransitionOptions(); + return transitionOptions.duration.value_or(mbgl::Duration::zero()).count(); +} + +void NativeMapView::setTransitionDuration(JNIEnv&, jlong duration) { + auto transitionOptions = map->getTransitionOptions(); + transitionOptions.duration = std::chrono::duration_cast(std::chrono::duration(duration)); + map->setTransitionOptions(transitionOptions); +} + +jlong NativeMapView::getTransitionDelay(JNIEnv&) { + const auto transitionOptions = map->getTransitionOptions(); + return transitionOptions.delay.value_or(mbgl::Duration::zero()).count(); +} + +void NativeMapView::setTransitionDelay(JNIEnv&, jlong delay) { + auto transitionOptions = map->getTransitionOptions(); + transitionOptions.delay = std::chrono::duration_cast(std::chrono::duration(delay)); + map->setTransitionOptions(transitionOptions); +} + +jni::Array NativeMapView::queryPointAnnotations(JNIEnv& env, jni::Object rect) { + // 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 = map->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) { + using namespace mbgl::android::conversion; + using namespace mapbox::geometry; + + mbgl::optional> layers; + if (layerIds != nullptr && layerIds.Length(env) > 0) { + layers = toVector(env, layerIds); + } + point point = {x, y}; + + return *convert>, std::vector>(env, map->queryRenderedFeatures(point, layers)); +} + +jni::Array> NativeMapView::queryRenderedFeaturesForBox(JNIEnv& env, jni::jfloat left, jni::jfloat top, jni::jfloat right, jni::jfloat bottom, jni::Array layerIds) { + using namespace mbgl::android::conversion; + using namespace mapbox::geometry; + + mbgl::optional> layers; + if (layerIds != nullptr && layerIds.Length(env) > 0) { + layers = toVector(env, layerIds); + } + box box = { point{ left, top}, point{ right, bottom } }; + + return *convert>, std::vector>(env, map->queryRenderedFeatures(box, layers)); +} + +jni::Object NativeMapView::getLayer(JNIEnv& env, jni::String layerId) { + + // Find the layer + mbgl::style::Layer* coreLayer = map->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()); + } +} + +/** + * Remove by layer id. Ownership is not transferred back + */ +void NativeMapView::removeLayerById(JNIEnv& env, jni::String id) { + map->removeLayer(jni::Make(env, id)); +} + +/** + * 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->removeLayer(layer->get().getID()); + if (coreLayer) { + layer->setLayer(std::move(coreLayer)); + } +} + + +jni::Object NativeMapView::getSource(JNIEnv& env, jni::String sourceId) { + // Find the source + mbgl::style::Source* coreSource = map->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, *map, *coreSource)); +} + +void NativeMapView::addSource(JNIEnv& env, jni::jlong sourcePtr) { + assert(sourcePtr != 0); + + Source *source = reinterpret_cast(sourcePtr); + try { + source->addToMap(*map); + } catch (const std::runtime_error& error) { + jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/sources/CannotAddSourceException"), error.what()); + } +} + +void NativeMapView::removeSourceById(JNIEnv& env, jni::String id) { + map->removeSource(jni::Make(env, id)); +} + +void NativeMapView::removeSource(JNIEnv&, jlong sourcePtr) { + assert(sourcePtr != 0); + + mbgl::android::Source *source = reinterpret_cast(sourcePtr); + std::unique_ptr coreSource = map->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())); + auto spriteImage = std::make_unique(std::move(premultipliedImage), float(scale)); + + map->addImage(jni::Make(env, name), std::move(spriteImage)); +} + +void NativeMapView::removeImage(JNIEnv& env, jni::String name) { + map->removeImage(jni::Make(env, name)); +} + +// Private methods // + +void NativeMapView::_initializeDisplay() { assert(display == EGL_NO_DISPLAY); assert(config == nullptr); assert(format < 0); @@ -286,145 +925,6 @@ void NativeMapView::initializeDisplay() { } } -void NativeMapView::terminateDisplay() { - if (display != EGL_NO_DISPLAY) { - // Destroy the surface first, if it still exists. This call needs a valid surface. - if (surface != EGL_NO_SURFACE) { - if (!eglDestroySurface(display, surface)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroySurface() failed"); - } - surface = EGL_NO_SURFACE; - } - - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - - if (!eglTerminate(display)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d", - eglGetError()); - throw std::runtime_error("eglTerminate() failed"); - } - } - - display = EGL_NO_DISPLAY; - config = nullptr; - format = -1; -} - -void NativeMapView::initializeContext() { - assert(display != EGL_NO_DISPLAY); - assert(context == EGL_NO_CONTEXT); - assert(config != nullptr); - - const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); - if (context == EGL_NO_CONTEXT) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d", - eglGetError()); - throw std::runtime_error("eglCreateContext() failed"); - } -} - -void NativeMapView::terminateContext() { - if (display != EGL_NO_DISPLAY) { - - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - - if (context != EGL_NO_CONTEXT) { - if (!eglDestroyContext(display, context)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroyContext() failed"); - } - } - } - - context = EGL_NO_CONTEXT; -} - -void NativeMapView::createSurface(ANativeWindow *window_) { - assert(window == nullptr); - assert(window_ != nullptr); - window = window_; - - assert(display != EGL_NO_DISPLAY); - assert(surface == EGL_NO_SURFACE); - assert(config != nullptr); - assert(format >= 0); - - ANativeWindow_setBuffersGeometry(window, 0, 0, format); - - const EGLint surfaceAttribs[] = {EGL_NONE}; - surface = eglCreateWindowSurface(display, config, window, surfaceAttribs); - if (surface == EGL_NO_SURFACE) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglCreateWindowSurface() failed"); - } - - if (!firstTime) { - firstTime = true; - - BackendScope guard(*this); - - if (!eglMakeCurrent(display, surface, surface, context)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", - eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - - mbgl::gl::InitializeExtensions([] (const char * name) { - return reinterpret_cast(eglGetProcAddress(name)); - }); - } -} - -void NativeMapView::destroySurface() { - if (surface != EGL_NO_SURFACE) { - if (!eglDestroySurface(display, surface)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroySurface() failed"); - } - } - - surface = EGL_NO_SURFACE; - - if (window != nullptr) { - ANativeWindow_release(window); - window = nullptr; - } -} - -void NativeMapView::scheduleTakeSnapshot() { - snapshot = true; -} - -// Speed -/* -typedef enum { - Format16Bit = 0, - Format32BitNoAlpha = 1, - Format32BitAlpha = 2, - Format24Bit = 3, - Unknown = 4 -} BufferFormat; - -typedef enum { - Format16Depth8Stencil = 0, - Format24Depth8Stencil = 1, -} DepthStencilFormat; -*/ - // Quality typedef enum { Format16Bit = 3, @@ -589,18 +1089,134 @@ EGLConfig NativeMapView::chooseConfig(const EGLConfig configs[], EGLint numConfi return configId; } -void NativeMapView::notifyMapChange(mbgl::MapChange change) { - assert(vm != nullptr); - assert(obj != nullptr); +void NativeMapView::_terminateDisplay() { + if (display != EGL_NO_DISPLAY) { + // Destroy the surface first, if it still exists. This call needs a valid surface. + if (surface != EGL_NO_SURFACE) { + if (!eglDestroySurface(display, surface)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", + eglGetError()); + throw std::runtime_error("eglDestroySurface() failed"); + } + surface = EGL_NO_SURFACE; + } - env->CallVoidMethod(obj, onMapChangedId, change); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); + throw std::runtime_error("eglMakeCurrent() failed"); + } + + if (!eglTerminate(display)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d", + eglGetError()); + throw std::runtime_error("eglTerminate() failed"); + } } + + display = EGL_NO_DISPLAY; + config = nullptr; + format = -1; } -void NativeMapView::enableFps(bool enable) { - fpsEnabled = enable; +void NativeMapView::_initializeContext() { + assert(display != EGL_NO_DISPLAY); + assert(context == EGL_NO_CONTEXT); + assert(config != nullptr); + + const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d", + eglGetError()); + throw std::runtime_error("eglCreateContext() failed"); + } +} + +void NativeMapView::_terminateContext() { + if (display != EGL_NO_DISPLAY) { + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); + throw std::runtime_error("eglMakeCurrent() failed"); + } + + if (context != EGL_NO_CONTEXT) { + if (!eglDestroyContext(display, context)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d", + eglGetError()); + throw std::runtime_error("eglDestroyContext() failed"); + } + } + } + + context = EGL_NO_CONTEXT; +} + +void NativeMapView::_createSurface(ANativeWindow *window_) { + assert(window == nullptr); + assert(window_ != nullptr); + window = window_; + + assert(display != EGL_NO_DISPLAY); + assert(surface == EGL_NO_SURFACE); + assert(config != nullptr); + assert(format >= 0); + + ANativeWindow_setBuffersGeometry(window, 0, 0, format); + + const EGLint surfaceAttribs[] = {EGL_NONE}; + surface = eglCreateWindowSurface(display, config, window, surfaceAttribs); + if (surface == EGL_NO_SURFACE) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d", + eglGetError()); + throw std::runtime_error("eglCreateWindowSurface() failed"); + } + + if (firstRender) { + firstRender = false; + + BackendScope guard(*this); + + if (!eglMakeCurrent(display, surface, surface, context)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", + eglGetError()); + throw std::runtime_error("eglMakeCurrent() failed"); + } + + mbgl::gl::InitializeExtensions([] (const char * name) { + return reinterpret_cast(eglGetProcAddress(name)); + }); + } +} + +void NativeMapView::_destroySurface() { + if (surface != EGL_NO_SURFACE) { + if (!eglDestroySurface(display, surface)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", + eglGetError()); + throw std::runtime_error("eglDestroySurface() failed"); + } + } + + surface = EGL_NO_SURFACE; + + if (window != nullptr) { + ANativeWindow_release(window); + window = nullptr; + } +} + +mbgl::Size NativeMapView::getFramebufferSize() const { + return { static_cast(fbWidth), static_cast(fbHeight) }; +} + +void NativeMapView::updateViewBinding() { + getContext().bindFramebuffer.setCurrentValue(0); + assert(mbgl::gl::value::BindFramebuffer::Get() == getContext().bindFramebuffer.getCurrentValue()); + getContext().viewport.setCurrentValue({ 0, 0, getFramebufferSize() }); + assert(mbgl::gl::value::Viewport::Get() == getContext().viewport.getCurrentValue()); } void NativeMapView::updateFps() { @@ -618,35 +1234,116 @@ void NativeMapView::updateFps() { if (currentTime - timeElapsed >= 1) { fps = frames / ((currentTime - timeElapsed) / 1E9); - mbgl::Log::Debug(mbgl::Event::Render, "FPS: %4.2f", fps); + mbgl::Log::Info(mbgl::Event::Render, "FPS: %4.2f", fps); timeElapsed = currentTime; frames = 0; } assert(vm != nullptr); - assert(obj != nullptr); - - env->CallVoidMethod(obj, onFpsChangedId, fps); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - } -} - -void NativeMapView::resizeView(int w, int h) { - width = w; - height = h; - map->setSize({ static_cast(width), static_cast(height) }); -} -void NativeMapView::resizeFramebuffer(int w, int h) { - fbWidth = w; - fbHeight = h; - framebufferSizeChanged = true; - invalidate(); + android::UniqueEnv _env = android::AttachEnv(); + static auto onFpsChanged = javaClass.GetMethod(*_env, "onFpsChanged"); + javaPeer->Call(*_env, onFpsChanged, fps); } -void NativeMapView::setInsets(mbgl::EdgeInsets insets_) { - insets = insets_; +// 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::jfloat, jni::jint, jni::jlong>, + "nativeInitialize", + "nativeDestroy", + METHOD(&NativeMapView::render, "nativeRender"), + METHOD(&NativeMapView::update, "nativeUpdate"), + METHOD(&NativeMapView::resizeView, "nativeResizeView"), + METHOD(&NativeMapView::resizeFramebuffer, "nativeResizeFramebuffer"), + METHOD(&NativeMapView::initializeDisplay, "nativeInitializeDisplay"), + METHOD(&NativeMapView::terminateDisplay, "nativeTerminateDisplay"), + METHOD(&NativeMapView::initializeContext, "nativeInitializeContext"), + METHOD(&NativeMapView::terminateContext, "nativeTerminateContext"), + METHOD(&NativeMapView::createSurface, "nativeCreateSurface"), + METHOD(&NativeMapView::destroySurface, "nativeDestroySurface"), + 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::setReachability, "nativeSetReachability"), + METHOD(&NativeMapView::resetPosition, "nativeResetPosition"), + METHOD(&NativeMapView::getPitch, "nativeGetPitch"), + METHOD(&NativeMapView::setPitch, "nativeSetPitch"), + METHOD(&NativeMapView::scaleBy, "nativeScaleBy"), + METHOD(&NativeMapView::getScale, "nativeGetScale"), + METHOD(&NativeMapView::setScale, "nativeSetScale"), + 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::enableFps, "nativeSetEnableFps"), + METHOD(&NativeMapView::getCameraValues, "nativeGetCameraValues"), + 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::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::getLayer, "nativeGetLayer"), + METHOD(&NativeMapView::addLayer, "nativeAddLayer"), + METHOD(&NativeMapView::removeLayerById, "nativeRemoveLayerById"), + METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"), + METHOD(&NativeMapView::getSource, "nativeGetSource"), + METHOD(&NativeMapView::addSource, "nativeAddSource"), + METHOD(&NativeMapView::removeSourceById, "nativeRemoveSourceById"), + METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), + METHOD(&NativeMapView::addImage, "nativeAddImage"), + METHOD(&NativeMapView::removeImage, "nativeRemoveImage") + ); } } -- cgit v1.2.1