From 32eff4681467de0e2e99a04902247fba39cc747f Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 28 Nov 2019 18:19:17 +0200 Subject: [PATCH 1/2] [core][android][darwin] Fix GeoJSONOptions handling - share the `GeoJSONOptions` instances using `Immutable` - avoid extra copying - fix wrapping of the `GeoJSONOptions` instances in supercluster map/reduce lambdas. Previously, local variables were wrapped by reference. --- include/mbgl/style/sources/geojson_source.hpp | 7 ++- .../src/style/sources/geojson_source.cpp | 62 +++++++++---------- .../src/style/sources/geojson_source.hpp | 4 +- platform/darwin/src/MGLShapeSource.mm | 26 ++++---- platform/darwin/src/MGLShapeSource_Private.h | 4 +- platform/darwin/test/MGLShapeSourceTests.mm | 16 ++--- src/mbgl/style/conversion/source.cpp | 8 +-- src/mbgl/style/sources/geojson_source.cpp | 12 +++- .../style/sources/geojson_source_impl.cpp | 32 +++++----- .../style/sources/geojson_source_impl.hpp | 6 +- test/api/query.test.cpp | 6 +- test/style/source.test.cpp | 8 +-- 12 files changed, 99 insertions(+), 92 deletions(-) diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 549393e9b26..1cc104b2455 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -34,10 +34,13 @@ struct GeoJSONOptions { std::shared_ptr>; using ClusterProperties = std::unordered_map; ClusterProperties clusterProperties; + + static Immutable defaultOptions(); }; class GeoJSONData { public: - static std::shared_ptr create(const GeoJSON&, const GeoJSONOptions&); + static std::shared_ptr create(const GeoJSON&, + Immutable = GeoJSONOptions::defaultOptions()); virtual ~GeoJSONData() = default; virtual mapbox::feature::feature_collection getTile(const CanonicalTileID&) = 0; @@ -52,7 +55,7 @@ class GeoJSONData { class GeoJSONSource final : public Source { public: - GeoJSONSource(const std::string& id, optional = nullopt); + GeoJSONSource(std::string id, Immutable = GeoJSONOptions::defaultOptions()); ~GeoJSONSource() final; void setURL(const std::string& url); diff --git a/platform/android/src/style/sources/geojson_source.cpp b/platform/android/src/style/sources/geojson_source.cpp index 0eece4b1ad0..f68c9d8f425 100644 --- a/platform/android/src/style/sources/geojson_source.cpp +++ b/platform/android/src/style/sources/geojson_source.cpp @@ -1,4 +1,5 @@ #include "geojson_source.hpp" +#include #include "../../attach_env.hpp" #include @@ -29,44 +30,42 @@ 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; +static Immutable convertGeoJSONOptions(jni::JNIEnv& env, const jni::Object<>& options) { + using namespace mbgl::style::conversion; + if (!options) { + return style::GeoJSONOptions::defaultOptions(); } + Error error; + optional result = convert(mbgl::android::Value(env, options), error); + if (!result) { + throw std::logic_error(error.message); + } + return makeMutable(std::move(*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))), - converter(std::make_unique>(Scheduler::GetBackground(), - source.as()->getOptions())) {} - - GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend) - : Source(env, coreSource, createJavaPeer(env), frontend), - converter(std::make_unique>(Scheduler::GetBackground(), - source.as()->getOptions())) {} +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))), + converter(std::make_unique>(Scheduler::GetBackground(), + source.as()->impl().getOptions())) {} - GeoJSONSource::~GeoJSONSource() = default; +GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend), + converter(std::make_unique>(Scheduler::GetBackground(), + source.as()->impl().getOptions())) {} - void GeoJSONSource::setGeoJSONString(jni::JNIEnv& env, const jni::String& jString) { +GeoJSONSource::~GeoJSONSource() = default; - std::shared_ptr json = std::make_shared(jni::Make(env, jString)); +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); - }; + Update::Converter converterFn = [this, json](ActorRef _callback) { + converter->self().invoke(&FeatureConverter::convertJson, json, _callback); + }; - setAsync(converterFn); - } + setAsync(converterFn); +} void GeoJSONSource::setFeatureCollection(jni::JNIEnv& env, const jni::Object& jFeatures) { setCollectionAsync(env, jFeatures); @@ -237,6 +236,7 @@ namespace android { mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message); return; } + callback.invoke(&GeoJSONDataCallback::operator(), style::GeoJSONData::create(*converted, options)); } diff --git a/platform/android/src/style/sources/geojson_source.hpp b/platform/android/src/style/sources/geojson_source.hpp index e506191ceb4..668e944e1cf 100644 --- a/platform/android/src/style/sources/geojson_source.hpp +++ b/platform/android/src/style/sources/geojson_source.hpp @@ -15,7 +15,7 @@ using GeoJSONDataCallback = std::function options_) : options(std::move(options_)) {} void convertJson(std::shared_ptr, ActorRef); template @@ -23,7 +23,7 @@ class FeatureConverter { ActorRef); private: - style::GeoJSONOptions options; + Immutable options; }; struct Update { diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index a4a100aaa29..2590865ac2d 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -27,15 +27,15 @@ const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; const MGLShapeSourceOption MGLShapeSourceOptionLineDistanceMetrics = @"MGLShapeSourceOptionLineDistanceMetrics"; -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary *options) { - auto geoJSONOptions = mbgl::style::GeoJSONOptions(); +mbgl::Immutable MGLGeoJSONOptionsFromDictionary(NSDictionary *options) { + auto geoJSONOptions = mbgl::makeMutable(); if (NSNumber *value = options[MGLShapeSourceOptionMinimumZoomLevel]) { if (![value isKindOfClass:[NSNumber class]]) { [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; } - geoJSONOptions.minzoom = value.integerValue; + geoJSONOptions->minzoom = value.integerValue; } if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { @@ -43,7 +43,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; } - geoJSONOptions.maxzoom = value.integerValue; + geoJSONOptions->maxzoom = value.integerValue; } if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { @@ -51,7 +51,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; } - geoJSONOptions.buffer = value.integerValue; + geoJSONOptions->buffer = value.integerValue; } if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { @@ -59,7 +59,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; } - geoJSONOptions.tolerance = value.doubleValue; + geoJSONOptions->tolerance = value.doubleValue; } if (NSNumber *value = options[MGLShapeSourceOptionClusterRadius]) { @@ -67,7 +67,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionClusterRadius must be an NSNumber."]; } - geoJSONOptions.clusterRadius = value.integerValue; + geoJSONOptions->clusterRadius = value.integerValue; } if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevelForClustering]) { @@ -75,7 +75,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; } - geoJSONOptions.clusterMaxZoom = value.integerValue; + geoJSONOptions->clusterMaxZoom = value.integerValue; } if (NSNumber *value = options[MGLShapeSourceOptionClustered]) { @@ -83,7 +83,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionClustered must be an NSNumber."]; } - geoJSONOptions.cluster = value.boolValue; + geoJSONOptions->cluster = value.boolValue; } if (NSDictionary *value = options[MGLShapeSourceOptionClusterProperties]) { @@ -133,7 +133,7 @@ std::string keyString = std::string([key UTF8String]); - geoJSONOptions.clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce))); + geoJSONOptions->clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce))); } } @@ -142,7 +142,7 @@ [NSException raise:NSInvalidArgumentException format:@"MGLShapeSourceOptionLineDistanceMetrics must be an NSNumber."]; } - geoJSONOptions.lineMetrics = value.boolValue; + geoJSONOptions->lineMetrics = value.boolValue; } return geoJSONOptions; @@ -159,7 +159,7 @@ @implementation MGLShapeSource - (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url options:(NSDictionary *)options { auto geoJSONOptions = MGLGeoJSONOptionsFromDictionary(options); - auto source = std::make_unique(identifier.UTF8String, geoJSONOptions); + auto source = std::make_unique(identifier.UTF8String, std::move(geoJSONOptions)); if (self = [super initWithPendingSource:std::move(source)]) { self.URL = url; } @@ -168,7 +168,7 @@ - (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url optio - (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLShape *)shape options:(NSDictionary *)options { auto geoJSONOptions = MGLGeoJSONOptionsFromDictionary(options); - auto source = std::make_unique(identifier.UTF8String, geoJSONOptions); + auto source = std::make_unique(identifier.UTF8String, std::move(geoJSONOptions)); if (self = [super initWithPendingSource:std::move(source)]) { if ([shape isMemberOfClass:[MGLShapeCollection class]]) { static dispatch_once_t onceToken; diff --git a/platform/darwin/src/MGLShapeSource_Private.h b/platform/darwin/src/MGLShapeSource_Private.h index c7eaf3d0a8b..940c80a17d6 100644 --- a/platform/darwin/src/MGLShapeSource_Private.h +++ b/platform/darwin/src/MGLShapeSource_Private.h @@ -1,6 +1,8 @@ #import "MGLFoundation.h" #import "MGLShapeSource.h" +#include + NS_ASSUME_NONNULL_BEGIN namespace mbgl { @@ -10,7 +12,7 @@ namespace mbgl { } MGL_EXPORT -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary *options); +mbgl::Immutable MGLGeoJSONOptionsFromDictionary(NSDictionary *options); @interface MGLShapeSource (Private) diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index 3bf3ef04bd0..9046607010a 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -26,14 +26,14 @@ - (void)testGeoJSONOptionsFromDictionary { MGLShapeSourceOptionLineDistanceMetrics: @YES}; auto mbglOptions = MGLGeoJSONOptionsFromDictionary(options); - XCTAssertTrue(mbglOptions.cluster); - XCTAssertEqual(mbglOptions.clusterRadius, 42); - XCTAssertEqual(mbglOptions.clusterMaxZoom, 98); - XCTAssertEqual(mbglOptions.maxzoom, 99); - XCTAssertEqual(mbglOptions.buffer, 1976); - XCTAssertEqual(mbglOptions.tolerance, 0.42); - XCTAssertTrue(mbglOptions.lineMetrics); - XCTAssertTrue(!mbglOptions.clusterProperties.empty()); + XCTAssertTrue(mbglOptions->cluster); + XCTAssertEqual(mbglOptions->clusterRadius, 42); + XCTAssertEqual(mbglOptions->clusterMaxZoom, 98); + XCTAssertEqual(mbglOptions->maxzoom, 99); + XCTAssertEqual(mbglOptions->buffer, 1976); + XCTAssertEqual(mbglOptions->tolerance, 0.42); + XCTAssertTrue(mbglOptions->lineMetrics); + XCTAssertTrue(!mbglOptions->clusterProperties.empty()); options = @{MGLShapeSourceOptionClustered: @"number 1"}; XCTAssertThrows(MGLGeoJSONOptionsFromDictionary(options)); diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp index 980a1a57723..b4a3d74720b 100644 --- a/src/mbgl/style/conversion/source.cpp +++ b/src/mbgl/style/conversion/source.cpp @@ -116,12 +116,12 @@ static optional> convertGeoJSONSource(const std::string& return nullopt; } - optional options = convert(value, error); - if (!options) { - return nullopt; + Immutable options = GeoJSONOptions::defaultOptions(); + if (optional converted = convert(value, error)) { + options = makeMutable(std::move(*converted)); } - auto result = std::make_unique(id, *options); + auto result = std::make_unique(id, std::move(options)); if (isObject(*dataValue)) { optional geoJSON = convert(*dataValue, error); diff --git a/src/mbgl/style/sources/geojson_source.cpp b/src/mbgl/style/sources/geojson_source.cpp index 5523336f127..fc8a563fd4e 100644 --- a/src/mbgl/style/sources/geojson_source.cpp +++ b/src/mbgl/style/sources/geojson_source.cpp @@ -12,8 +12,14 @@ namespace mbgl { namespace style { -GeoJSONSource::GeoJSONSource(const std::string& id, optional options) - : Source(makeMutable(id, options)) {} +// static +Immutable GeoJSONOptions::defaultOptions() { + static Immutable options = makeMutable(); + return options; +} + +GeoJSONSource::GeoJSONSource(std::string id, Immutable options) + : Source(makeMutable(std::move(id), std::move(options))) {} GeoJSONSource::~GeoJSONSource() = default; @@ -47,7 +53,7 @@ optional GeoJSONSource::getURL() const { } const GeoJSONOptions& GeoJSONSource::getOptions() const { - return impl().getOptions(); + return *impl().getOptions(); } void GeoJSONSource::loadDescription(FileSource& fileSource) { diff --git a/src/mbgl/style/sources/geojson_source_impl.cpp b/src/mbgl/style/sources/geojson_source_impl.cpp index 468deb6134e..f716b81c5b8 100644 --- a/src/mbgl/style/sources/geojson_source_impl.cpp +++ b/src/mbgl/style/sources/geojson_source_impl.cpp @@ -83,26 +83,26 @@ T evaluateFeature(const mapbox::feature::feature& f, } // static -std::shared_ptr GeoJSONData::create(const GeoJSON& geoJSON, const GeoJSONOptions& options) { +std::shared_ptr GeoJSONData::create(const GeoJSON& geoJSON, Immutable options) { constexpr double scale = util::EXTENT / util::tileSize; - if (options.cluster && geoJSON.is>() && + if (options->cluster && geoJSON.is>() && !geoJSON.get>().empty()) { mapbox::supercluster::Options clusterOptions; - clusterOptions.maxZoom = options.clusterMaxZoom; + clusterOptions.maxZoom = options->clusterMaxZoom; clusterOptions.extent = util::EXTENT; - clusterOptions.radius = ::round(scale * options.clusterRadius); + clusterOptions.radius = ::round(scale * options->clusterRadius); Feature feature; - clusterOptions.map = [&](const PropertyMap& properties) -> PropertyMap { + clusterOptions.map = [&, options](const PropertyMap& properties) -> PropertyMap { PropertyMap ret{}; if (properties.empty()) return ret; - for (const auto& p : options.clusterProperties) { + for (const auto& p : options->clusterProperties) { feature.properties = properties; ret[p.first] = evaluateFeature(feature, p.second.first); } return ret; }; - clusterOptions.reduce = [&](PropertyMap& toReturn, const PropertyMap& toFill) { - for (const auto& p : options.clusterProperties) { + clusterOptions.reduce = [&, options](PropertyMap& toReturn, const PropertyMap& toFill) { + for (const auto& p : options->clusterProperties) { if (toFill.count(p.first) == 0) { continue; } @@ -116,18 +116,16 @@ std::shared_ptr GeoJSONData::create(const GeoJSON& geoJSON, const G } mapbox::geojsonvt::Options vtOptions; - vtOptions.maxZoom = options.maxzoom; + vtOptions.maxZoom = options->maxzoom; vtOptions.extent = util::EXTENT; - vtOptions.buffer = ::round(scale * options.buffer); - vtOptions.tolerance = scale * options.tolerance; - vtOptions.lineMetrics = options.lineMetrics; + vtOptions.buffer = ::round(scale * options->buffer); + vtOptions.tolerance = scale * options->tolerance; + vtOptions.lineMetrics = options->lineMetrics; return std::make_shared(geoJSON, vtOptions); } -GeoJSONSource::Impl::Impl(std::string id_, optional options_) - : Source::Impl(SourceType::GeoJSON, std::move(id_)) { - options = options_ ? std::move(*options_) : GeoJSONOptions{}; -} +GeoJSONSource::Impl::Impl(std::string id_, Immutable options_) + : Source::Impl(SourceType::GeoJSON, std::move(id_)), options(std::move(options_)) {} GeoJSONSource::Impl::Impl(const GeoJSONSource::Impl& other, std::shared_ptr data_) : Source::Impl(other), options(other.options), data(std::move(data_)) {} @@ -135,7 +133,7 @@ GeoJSONSource::Impl::Impl(const GeoJSONSource::Impl& other, std::shared_ptr GeoJSONSource::Impl::getZoomRange() const { - return { options.minzoom, options.maxzoom }; + return {options->minzoom, options->maxzoom}; } std::weak_ptr GeoJSONSource::Impl::getData() const { diff --git a/src/mbgl/style/sources/geojson_source_impl.hpp b/src/mbgl/style/sources/geojson_source_impl.hpp index da2673a38ce..3b106e3c00f 100644 --- a/src/mbgl/style/sources/geojson_source_impl.hpp +++ b/src/mbgl/style/sources/geojson_source_impl.hpp @@ -13,18 +13,18 @@ namespace style { class GeoJSONSource::Impl : public Source::Impl { public: - Impl(std::string id, optional); + Impl(std::string id, Immutable); Impl(const GeoJSONSource::Impl&, std::shared_ptr); ~Impl() final; Range getZoomRange() const; std::weak_ptr getData() const; - const GeoJSONOptions& getOptions() const { return options; } + const Immutable& getOptions() const { return options; } optional getAttribution() const final; private: - GeoJSONOptions options; + Immutable options; std::shared_ptr data; }; diff --git a/test/api/query.test.cpp b/test/api/query.test.cpp index 9116a3746d4..63a2479b40b 100644 --- a/test/api/query.test.cpp +++ b/test/api/query.test.cpp @@ -48,9 +48,9 @@ std::vector getTopClusterFeature(QueryTest& test) { }; LatLng coordinate{0, 0}; - GeoJSONOptions options{}; - options.cluster = true; - auto source = std::make_unique("cluster_source"s, options); + Mutable options = makeMutable(); + options->cluster = true; + auto source = std::make_unique("cluster_source"s, std::move(options)); source->setURL("http://url"s); source->loadDescription(*test.fileSource); diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index a02935f93f6..5eb837d92bf 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -852,10 +852,8 @@ TEST(Source, RenderTileSetSourceUpdate) { TEST(Source, GeoJSONSourceTilesAfterDataReset) { SourceTest test; GeoJSONSource source("source"); - auto geoJSONData = GeoJSONData::create( - mapbox::geojson::parse( - R"({"geometry": {"type": "Point", "coordinates": [1.1, 1.1]}, "type": "Feature", "properties": {}})"), - {}); + auto geoJSONData = GeoJSONData::create(mapbox::geojson::parse( + R"({"geometry": {"type": "Point", "coordinates": [1.1, 1.1]}, "type": "Feature", "properties": {}})")); source.setGeoJSONData(geoJSONData); RenderGeoJSONSource renderSource{staticImmutableCast(source.baseImpl)}; @@ -879,4 +877,4 @@ TEST(Source, GeoJSONSourceTilesAfterDataReset) { static_cast(renderSource) .update(source.baseImpl, layers, true, true, test.tileParameters(MapMode::Static)); EXPECT_TRUE(renderSource.isLoaded()); // Tiles are reset in static mode. -} \ No newline at end of file +} From 86c726373469936270a1fe7a5ac9ca15f91546c1 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 28 Nov 2019 18:42:11 +0200 Subject: [PATCH 2/2] [core] Fix supercluster lambdas capturing --- src/mbgl/style/sources/geojson_source_impl.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mbgl/style/sources/geojson_source_impl.cpp b/src/mbgl/style/sources/geojson_source_impl.cpp index f716b81c5b8..962d6bd060e 100644 --- a/src/mbgl/style/sources/geojson_source_impl.cpp +++ b/src/mbgl/style/sources/geojson_source_impl.cpp @@ -91,24 +91,24 @@ std::shared_ptr GeoJSONData::create(const GeoJSON& geoJSON, Immutab clusterOptions.maxZoom = options->clusterMaxZoom; clusterOptions.extent = util::EXTENT; clusterOptions.radius = ::round(scale * options->clusterRadius); - Feature feature; - clusterOptions.map = [&, options](const PropertyMap& properties) -> PropertyMap { + auto feature = std::make_shared(); + clusterOptions.map = [feature, options](const PropertyMap& properties) -> PropertyMap { PropertyMap ret{}; if (properties.empty()) return ret; for (const auto& p : options->clusterProperties) { - feature.properties = properties; - ret[p.first] = evaluateFeature(feature, p.second.first); + feature->properties = properties; + ret[p.first] = evaluateFeature(*feature, p.second.first); } return ret; }; - clusterOptions.reduce = [&, options](PropertyMap& toReturn, const PropertyMap& toFill) { + clusterOptions.reduce = [feature, options](PropertyMap& toReturn, const PropertyMap& toFill) { for (const auto& p : options->clusterProperties) { if (toFill.count(p.first) == 0) { continue; } - feature.properties = toFill; + feature->properties = toFill; optional accumulated(toReturn[p.first]); - toReturn[p.first] = evaluateFeature(feature, p.second.second, accumulated); + toReturn[p.first] = evaluateFeature(*feature, p.second.second, accumulated); } }; return std::make_shared(geoJSON.get>(),