#include "geojson_source.hpp" #include "../../attach_env.hpp" #include // Java -> C++ conversion #include "../android_conversion.hpp" #include "../conversion/filter.hpp" #include #include #include // C++ -> Java conversion #include "../../conversion/conversion.hpp" #include "../../conversion/collection.hpp" #include "../../geojson/feature.hpp" #include "../conversion/url_or_tileset.hpp" #include #include // GeoJSONSource uses a "coalescing" model for high frequency asynchronous data update calls, // which in practice means, that any update that started processing is going to finish // and the last scheduled update is going to finish as well. Any updates scheduled during processing can be canceled. // Conversion from Java features to core ones is done on a worker thread and once finished, // the ownership of the converted features is returned to the calling thread. namespace mbgl { namespace android { // This conversion is expected not to fail because it's used only in contexts where // the value was originally a GeoJsonOptions object on the Java side. If it fails // to convert, it's a bug in our serialization or Java-side static typing. static style::GeoJSONOptions convertGeoJSONOptions(jni::JNIEnv& env, const jni::Object<>& options) { using namespace mbgl::style::conversion; if (!options) { return style::GeoJSONOptions(); } Error error; optional result = convert( mbgl::android::Value(env, options), error); if (!result) { throw std::logic_error(error.message); } return *result; } GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, const jni::String& sourceId, const jni::Object<>& options) : Source(env, std::make_unique( jni::Make(env, sourceId), convertGeoJSONOptions(env, options))) , threadPool(sharedThreadPool()) , converter(std::make_unique>(*threadPool)) { } GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend) : Source(env, coreSource, createJavaPeer(env), frontend) , threadPool(sharedThreadPool()) , converter(std::make_unique>(*threadPool)) { } GeoJSONSource::~GeoJSONSource() = default; void GeoJSONSource::setGeoJSONString(jni::JNIEnv& env, const jni::String& jString) { std::shared_ptr json = std::make_shared(jni::Make(env, jString)); Update::Converter converterFn = [this, json](ActorRef _callback) { converter->self().invoke(&FeatureConverter::convertJson, json, _callback); }; setAsync(converterFn); } void GeoJSONSource::setFeatureCollection(jni::JNIEnv& env, const jni::Object& jFeatures) { setCollectionAsync(env, jFeatures); } void GeoJSONSource::setFeature(jni::JNIEnv& env, const jni::Object& jFeature) { setCollectionAsync(env, jFeature); } void GeoJSONSource::setGeometry(jni::JNIEnv& env, const jni::Object& jGeometry) { setCollectionAsync(env, jGeometry); } void GeoJSONSource::setURL(jni::JNIEnv& env, const jni::String& url) { // Update the core source source.as()->GeoJSONSource::setURL(jni::Make(env, url)); } jni::Local GeoJSONSource::getURL(jni::JNIEnv& env) { optional url = source.as()->GeoJSONSource::getURL(); return url ? jni::Make(env, *url) : jni::Local(); } jni::Local>> GeoJSONSource::querySourceFeatures(jni::JNIEnv& env, const jni::Array>& jfilter) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; std::vector features; if (rendererFrontend) { features = rendererFrontend->querySourceFeatures(source.getID(), { {}, toFilter(env, jfilter) }); } return Feature::convert(env, features); } jni::Local>> GeoJSONSource::getClusterChildren(jni::JNIEnv& env, const jni::Object& feature) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; if (rendererFrontend) { mbgl::Feature _feature = Feature::convert(env, feature); _feature.properties["cluster_id"] = static_cast(_feature.properties["cluster_id"].get()); const auto featureExtension = rendererFrontend->queryFeatureExtensions(source.getID(), _feature, "supercluster", "children", {}); if (featureExtension.is()) { return Feature::convert(env, featureExtension.get()); } } return jni::Array>::New(env, 0); } jni::Local>> GeoJSONSource::getClusterLeaves(jni::JNIEnv& env, const jni::Object& feature, jni::jlong limit, jni::jlong offset) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; if (rendererFrontend) { mbgl::Feature _feature = Feature::convert(env, feature); _feature.properties["cluster_id"] = static_cast(_feature.properties["cluster_id"].get()); const std::map options = { {"limit", static_cast(limit)}, {"offset", static_cast(offset)} }; auto featureExtension = rendererFrontend->queryFeatureExtensions(source.getID(), _feature, "supercluster", "leaves", options); if (featureExtension.is()) { return Feature::convert(env, featureExtension.get()); } } return jni::Array>::New(env, 0);; } jint GeoJSONSource::getClusterExpansionZoom(jni::JNIEnv& env, const jni::Object& feature) { using namespace mbgl::android::conversion; using namespace mbgl::android::geojson; if (rendererFrontend) { mbgl::Feature _feature = Feature::convert(env, feature); _feature.properties["cluster_id"] = static_cast(_feature.properties["cluster_id"].get()); auto featureExtension = rendererFrontend->queryFeatureExtensions(source.getID(), _feature, "supercluster", "expansion-zoom", {}); if (featureExtension.is()) { auto value = featureExtension.get(); if (value.is()) { return value.get(); } } } return 0; } jni::Local> GeoJSONSource::createJavaPeer(jni::JNIEnv& env) { static auto& javaClass = jni::Class::Singleton(env); static auto constructor = javaClass.GetConstructor(env); return javaClass.New(env, constructor, reinterpret_cast(this)); } template void GeoJSONSource::setCollectionAsync(jni::JNIEnv& env, const jni::Object& jObject) { auto global = jni::NewGlobal(env, jObject); auto object = std::make_shared(std::move(global)); Update::Converter converterFn = [this, object](ActorRef _callback) { converter->self().invoke(&FeatureConverter::convertObject, object, _callback); }; setAsync(converterFn); } void GeoJSONSource::setAsync(Update::Converter converterFn) { awaitingUpdate = std::make_unique( std::move(converterFn), std::make_unique>( *Scheduler::GetCurrent(), [this](GeoJSON /*geoJSON*/) { // conversion from Java features to core ones finished android::UniqueEnv _env = android::AttachEnv(); // Update the core source // source.as()->GeoJSONSource::setGeoJSON(geoJSON); // if there is an awaiting update, execute it, otherwise, release resources if (awaitingUpdate) { update = std::move(awaitingUpdate); update->converterFn(update->callback->self()); } else { update.reset(); } }) ); // If another update is running, wait if (update) { return; } // no updates are being processed, execute this one update = std::move(awaitingUpdate); update->converterFn(update->callback->self()); } void GeoJSONSource::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&>, "initialize", "finalize", METHOD(&GeoJSONSource::setGeoJSONString, "nativeSetGeoJsonString"), METHOD(&GeoJSONSource::setFeatureCollection, "nativeSetFeatureCollection"), METHOD(&GeoJSONSource::setFeature, "nativeSetFeature"), METHOD(&GeoJSONSource::setGeometry, "nativeSetGeometry"), METHOD(&GeoJSONSource::setURL, "nativeSetUrl"), METHOD(&GeoJSONSource::getURL, "nativeGetUrl"), METHOD(&GeoJSONSource::querySourceFeatures, "querySourceFeatures"), METHOD(&GeoJSONSource::getClusterChildren, "nativeGetClusterChildren"), METHOD(&GeoJSONSource::getClusterLeaves, "nativeGetClusterLeaves"), METHOD(&GeoJSONSource::getClusterExpansionZoom, "nativeGetClusterExpansionZoom") ); } void FeatureConverter::convertJson(std::shared_ptr json, ActorRef callback) { using namespace mbgl::style::conversion; android::UniqueEnv _env = android::AttachEnv(); // Convert the jni object Error error; optional converted = parseGeoJSON(*json, error); if(!converted) { mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message); return; } callback.invoke(&Callback::operator(), *converted); } template void FeatureConverter::convertObject(std::shared_ptr, jni::EnvAttachingDeleter>> jObject, ActorRef callback) { using namespace mbgl::android::geojson; android::UniqueEnv _env = android::AttachEnv(); // Convert the jni object auto geometry = JNIType::convert(*_env, *jObject); callback.invoke(&Callback::operator(), GeoJSON(geometry)); } Update::Update(Converter _converterFn, std::unique_ptr> _callback) : converterFn(std::move(_converterFn)) , callback(std::move(_callback)) {} } // namespace android } // namespace mbgl