From 9ecc0d95979ca2fa3154f4b47c8f9fa4717fe696 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 27 Jul 2016 20:18:41 +0300 Subject: [PATCH] GeoJSON point clustering (#5724) * add supercluster dependency * prepare GeoJSONTile for Supercluster * prepare GeoJSONSource for accepting options * try removing mbgl::GeoJSON * fix setGeoJSON types * add GeoJSONSource getURL * add geojson to include path * add Supercluster index in GeoJSONSource * fix GeoJSONSource getZoomRange * bring back mbgl::GeoJSON header * fix tidy warnings hopefully * try test-suite with enabled cluster test * fix formatting in clustering-related files --- .mason | 2 +- configure | 4 +- include/mbgl/style/conversion/source.hpp | 89 +++++++++++++++---- include/mbgl/style/sources/geojson_source.hpp | 35 +++++++- include/mbgl/util/geojson.hpp | 18 +--- mbgl.gypi | 4 + package.json | 2 +- platform/android/scripts/configure.sh | 2 + .../default/mbgl/storage/offline_download.cpp | 86 ++++++++++-------- platform/ios/ios.xcodeproj/project.pbxproj | 6 ++ platform/ios/scripts/configure.sh | 2 + platform/linux/scripts/configure.sh | 2 + .../macos/macos.xcodeproj/project.pbxproj | 4 + platform/macos/scripts/configure.sh | 2 + platform/qt/scripts/configure.sh | 2 + src/mbgl/style/sources/geojson_source.cpp | 15 ++-- .../style/sources/geojson_source_impl.cpp | 87 ++++++++++++------ .../style/sources/geojson_source_impl.hpp | 16 ++-- src/mbgl/tile/geojson_tile.cpp | 24 +++-- src/mbgl/tile/geojson_tile.hpp | 11 +++ src/mbgl/util/geojson.cpp | 11 --- test/test.gypi | 5 +- 22 files changed, 295 insertions(+), 134 deletions(-) delete mode 100644 src/mbgl/util/geojson.cpp diff --git a/.mason b/.mason index d3594e134..fc590c762 160000 --- a/.mason +++ b/.mason @@ -1 +1 @@ -Subproject commit d3594e134cd3f6d12aafb0b79d85f087751dea7d +Subproject commit fc590c762474eae5bb78c3c825247face0aed9aa diff --git a/configure b/configure index 4a62cd1be..c3a3ae319 100755 --- a/configure +++ b/configure @@ -119,7 +119,9 @@ print_flags nunicode static_libs cflags ldflags print_flags libzip static_libs cflags ldflags print_flags geometry cflags print_flags geojson static_libs cflags ldflags -print_flags geojsonvt static_libs cflags ldflags +print_flags geojsonvt cflags +print_flags supercluster cflags +print_flags kdbush cflags print_flags variant cflags print_flags rapidjson static_libs cflags ldflags print_flags gtest static_libs cflags ldflags diff --git a/include/mbgl/style/conversion/source.hpp b/include/mbgl/style/conversion/source.hpp index 00c6afb9f..c4b2fe303 100644 --- a/include/mbgl/style/conversion/source.hpp +++ b/include/mbgl/style/conversion/source.hpp @@ -1,12 +1,12 @@ #pragma once +#include +#include +#include #include #include #include #include -#include -#include -#include namespace mbgl { namespace style { @@ -18,17 +18,17 @@ public: template Result> operator()(const V& value, const std::string& id) const { if (!isObject(value)) { - return Error { "source must be an object" }; + return Error{ "source must be an object" }; } auto typeValue = objectMember(value, "type"); if (!typeValue) { - return Error { "source must have a type" }; + return Error{ "source must have a type" }; } optional type = toString(*typeValue); if (!type) { - return Error { "source type must be a string" }; + return Error{ "source type must be a string" }; } if (*type == "raster") { @@ -38,7 +38,7 @@ public: } else if (*type == "geojson") { return convertGeoJSONSource(id, value); } else { - return Error { "invalid source type" }; + return Error{ "invalid source type" }; } } @@ -57,14 +57,15 @@ private: optional url = toString(*urlVal); if (!url) { - return Error { "source url must be a string" }; + return Error{ "source url must be a string" }; } return *url; } template - Result> convertRasterSource(const std::string& id, const V& value) const { + Result> convertRasterSource(const std::string& id, + const V& value) const { Result> urlOrTileset = convertURLOrTileset(value); if (!urlOrTileset) { return urlOrTileset.error(); @@ -75,7 +76,7 @@ private: if (tileSizeValue) { optional size = toNumber(*tileSizeValue); if (!size || *size < 0 || *size > std::numeric_limits::max()) { - return Error { "invalid tileSize" }; + return Error{ "invalid tileSize" }; } tileSize = *size; } @@ -84,7 +85,8 @@ private: } template - Result> convertVectorSource(const std::string& id, const V& value) const { + Result> convertVectorSource(const std::string& id, + const V& value) const { Result> urlOrTileset = convertURLOrTileset(value); if (!urlOrTileset) { return urlOrTileset.error(); @@ -94,13 +96,70 @@ private: } template - Result> convertGeoJSONSource(const std::string& id, const V& value) const { + Result> convertGeoJSONSource(const std::string& id, + const V& value) const { auto dataValue = objectMember(value, "data"); if (!dataValue) { - return Error { "GeoJSON source must have a data value" }; + return Error{ "GeoJSON source must have a data value" }; } - auto result = std::make_unique(id); + GeoJSONOptions options; + + const auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + if (toNumber(*maxzoomValue)) { + options.maxzoom = static_cast(*toNumber(*maxzoomValue)); + } else { + return Error{ "GeoJSON source maxzoom value must be a number" }; + } + } + + const auto bufferValue = objectMember(value, "buffer"); + if (bufferValue) { + if (toNumber(*bufferValue)) { + options.buffer = static_cast(*toNumber(*bufferValue)); + } else { + return Error{ "GeoJSON source buffer value must be a number" }; + } + } + + const auto toleranceValue = objectMember(value, "tolerance"); + if (toleranceValue) { + if (toNumber(*toleranceValue)) { + options.tolerance = static_cast(*toNumber(*toleranceValue)); + } else { + return Error{ "GeoJSON source tolerance value must be a number" }; + } + } + + const auto clusterValue = objectMember(value, "cluster"); + if (clusterValue) { + if (toBool(*clusterValue)) { + options.cluster = *toBool(*clusterValue); + } else { + return Error{ "GeoJSON source cluster value must be a boolean" }; + } + } + + const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom"); + if (clusterMaxZoomValue) { + if (toNumber(*clusterMaxZoomValue)) { + options.clusterMaxZoom = static_cast(*toNumber(*clusterMaxZoomValue)); + } else { + return Error{ "GeoJSON source clusterMaxZoom value must be a number" }; + } + } + + const auto clusterRadiusValue = objectMember(value, "clusterRadius"); + if (clusterRadiusValue) { + if (toNumber(*clusterRadiusValue)) { + options.clusterRadius = static_cast(*toNumber(*clusterRadiusValue)); + } else { + return Error{ "GeoJSON source clusterRadius value must be a number" }; + } + } + + auto result = std::make_unique(id, options); if (isObject(*dataValue)) { Result geoJSON = convertGeoJSON(*dataValue); @@ -111,7 +170,7 @@ private: } else if (toString(*dataValue)) { result->setURL(*toString(*dataValue)); } else { - return Error { "GeoJSON data must be a URL or an object" }; + return Error{ "GeoJSON data must be a URL or an object" }; } return std::move(result); diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 3736dd44b..37ddce1bc 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -3,15 +3,46 @@ #include #include +#include + +namespace mapbox { + +namespace geojsonvt { +class GeoJSONVT; +} // namespace geojsonvt + +namespace supercluster { +class Supercluster; +} // namespace supercluster + +} // namespace mapbox + namespace mbgl { namespace style { +using GeoJSONVTPointer = std::unique_ptr; +using SuperclusterPointer = std::unique_ptr; + +struct GeoJSONOptions { + // GeoJSON-VT options + uint8_t maxzoom = 18; + uint16_t buffer = 128; + double tolerance = 0.375; + + // Supercluster options + bool cluster = false; + uint16_t clusterRadius = 50; + uint8_t clusterMaxZoom = 17; +}; + class GeoJSONSource : public Source { public: - GeoJSONSource(const std::string& id); + GeoJSONSource(const std::string& id, const GeoJSONOptions options_ = GeoJSONOptions()); void setURL(const std::string& url); - void setGeoJSON(GeoJSON&&); + void setGeoJSON(const GeoJSON&); + + std::string getURL(); // Private implementation diff --git a/include/mbgl/util/geojson.hpp b/include/mbgl/util/geojson.hpp index 3fd8c6ac4..b4e789a3a 100644 --- a/include/mbgl/util/geojson.hpp +++ b/include/mbgl/util/geojson.hpp @@ -1,22 +1,10 @@ #pragma once -#include - -namespace mapbox { -namespace geojsonvt { -class GeoJSONVT; -} // namespace geojsonvt -} // namespace mapbox +#include namespace mbgl { -class GeoJSON { -public: - GeoJSON(std::unique_ptr); - GeoJSON(GeoJSON&&); - ~GeoJSON(); - - std::unique_ptr impl; -}; +using GeoJSON = mapbox::geojson::geojson; +using FeatureCollection = mapbox::geojson::feature_collection; } // namespace mbgl diff --git a/mbgl.gypi b/mbgl.gypi index c3dd37b34..7f31d4619 100644 --- a/mbgl.gypi +++ b/mbgl.gypi @@ -196,6 +196,8 @@ '<@(geometry_cflags)', '<@(geojson_cflags)', '<@(geojsonvt_cflags)', + '<@(supercluster_cflags)', + '<@(kdbush_cflags)', '<@(rapidjson_cflags)', '<@(variant_cflags)', '<@(earcut_cflags)', @@ -313,12 +315,14 @@ 'cflags_cc': [ '<@(variant_cflags)', '<@(geometry_cflags)', + '<@(geojson_cflags)', '<@(unique_resource_cflags)', ], 'xcode_settings': { 'OTHER_CPLUSPLUSFLAGS': [ '<@(variant_cflags)', '<@(geometry_cflags)', + '<@(geojson_cflags)', '<@(unique_resource_cflags)', ], }, diff --git a/package.json b/package.json index ab3d95380..fe15f5ed6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "express": "^4.11.1", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#4d1f89514bf03536c8e682439df165c33a37122a", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#83b1a3e5837d785af582efd5ed1a212f2df6a4ae", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#fbd511a54a8c27d86c5b2d7cdab94d0e507ba8e8", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#346aebbb3cf7af572e066e37c703634ceda95be2", "node-gyp": "^3.3.1", "request": "^2.72.0", "tape": "^4.5.1" diff --git a/platform/android/scripts/configure.sh b/platform/android/scripts/configure.sh index 038d4ad0a..1c26108e1 100644 --- a/platform/android/scripts/configure.sh +++ b/platform/android/scripts/configure.sh @@ -12,6 +12,8 @@ LIBZIP_VERSION=0.11.2 GEOMETRY_VERSION=0.8.0 GEOJSON_VERSION=0.1.4 GEOJSONVT_VERSION=6.1.2 +SUPERCLUSTER_VERSION=0.2.0 +KDBUSH_VERSION=0.1.1 VARIANT_VERSION=1.1.0 RAPIDJSON_VERSION=1.0.2 JNI_HPP_VERSION=2.0.0 diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index 6ff605167..dd66abf98 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -1,15 +1,15 @@ -#include -#include #include +#include +#include #include #include #include -#include #include +#include #include -#include #include #include +#include #include #include @@ -66,7 +66,8 @@ std::vector OfflineDownload::glyphResources(const style::Parser& parse if (!parser.glyphURL.empty()) { for (const auto& fontStack : parser.fontStacks()) { for (uint32_t i = 0; i < 256; i++) { - result.push_back(Resource::glyphs(parser.glyphURL, fontStack, getGlyphRange(i * 256))); + result.push_back( + Resource::glyphs(parser.glyphURL, fontStack, getGlyphRange(i * 256))); } } } @@ -74,11 +75,13 @@ std::vector OfflineDownload::glyphResources(const style::Parser& parse return result; } -std::vector OfflineDownload::tileResources(SourceType type, uint16_t tileSize, const Tileset& tileset) const { +std::vector +OfflineDownload::tileResources(SourceType type, uint16_t tileSize, const Tileset& tileset) const { std::vector result; for (const auto& tile : definition.tileCover(type, tileSize, tileset.zoomRange)) { - result.push_back(Resource::tile(tileset.tiles[0], definition.pixelRatio, tile.x, tile.y, tile.z)); + result.push_back( + Resource::tile(tileset.tiles[0], definition.pixelRatio, tile.x, tile.y, tile.z)); } return result; @@ -106,18 +109,26 @@ OfflineRegionStatus OfflineDownload::getStatus() const { switch (source->baseImpl->type) { case SourceType::Vector: case SourceType::Raster: { - style::TileSourceImpl* tileSource = static_cast(source->baseImpl.get()); + style::TileSourceImpl* tileSource = + static_cast(source->baseImpl.get()); const variant& urlOrTileset = tileSource->getURLOrTileset(); if (urlOrTileset.is()) { - result.requiredResourceCount += tileResources(source->baseImpl->type, tileSource->getTileSize(), urlOrTileset.get()).size(); + result.requiredResourceCount += + tileResources(source->baseImpl->type, tileSource->getTileSize(), + urlOrTileset.get()) + .size(); } else { result.requiredResourceCount += 1; const std::string& url = urlOrTileset.get(); optional sourceResponse = offlineDatabase.get(Resource::source(url)); if (sourceResponse) { - result.requiredResourceCount += tileResources(source->baseImpl->type, tileSource->getTileSize(), - style::TileSourceImpl::parseTileJSON(*sourceResponse->data, url, source->baseImpl->type, tileSource->getTileSize())).size(); + result.requiredResourceCount += + tileResources(source->baseImpl->type, tileSource->getTileSize(), + style::TileSourceImpl::parseTileJSON( + *sourceResponse->data, url, source->baseImpl->type, + tileSource->getTileSize())) + .size(); } else { result.requiredResourceCountIsPrecise = false; } @@ -126,10 +137,10 @@ OfflineRegionStatus OfflineDownload::getStatus() const { } case SourceType::GeoJSON: { - style::GeoJSONSource::Impl* geojsonSource = static_cast(source->baseImpl.get()); - const variant& urlOrGeoJSON = geojsonSource->getURLOrGeoJSON(); + style::GeoJSONSource::Impl* geojsonSource = + static_cast(source->baseImpl.get()); - if (urlOrGeoJSON.is()) { + if (!geojsonSource->loaded) { result.requiredResourceCount += 1; } break; @@ -153,7 +164,7 @@ void OfflineDownload::activateDownload() { requiredSourceURLs.clear(); - ensureResource(Resource::style(definition.styleURL), [&] (Response styleResponse) { + ensureResource(Resource::style(definition.styleURL), [&](Response styleResponse) { status.requiredResourceCountIsPrecise = true; style::Parser parser; @@ -165,7 +176,8 @@ void OfflineDownload::activateDownload() { switch (type) { case SourceType::Vector: case SourceType::Raster: { - const style::TileSourceImpl* tileSource = static_cast(source->baseImpl.get()); + const style::TileSourceImpl* tileSource = + static_cast(source->baseImpl.get()); const variant& urlOrTileset = tileSource->getURLOrTileset(); const uint16_t tileSize = tileSource->getTileSize(); @@ -176,8 +188,9 @@ void OfflineDownload::activateDownload() { status.requiredResourceCountIsPrecise = false; requiredSourceURLs.insert(url); - ensureResource(Resource::source(url), [=] (Response sourceResponse) { - ensureTiles(type, tileSize, style::TileSourceImpl::parseTileJSON(*sourceResponse.data, url, type, tileSize)); + ensureResource(Resource::source(url), [=](Response sourceResponse) { + ensureTiles(type, tileSize, style::TileSourceImpl::parseTileJSON( + *sourceResponse.data, url, type, tileSize)); requiredSourceURLs.erase(url); if (requiredSourceURLs.empty()) { @@ -189,11 +202,11 @@ void OfflineDownload::activateDownload() { } case SourceType::GeoJSON: { - style::GeoJSONSource::Impl* geojsonSource = static_cast(source->baseImpl.get()); - const variant& urlOrGeoJSON = geojsonSource->getURLOrGeoJSON(); + style::GeoJSONSource::Impl* geojsonSource = + static_cast(source->baseImpl.get()); - if (urlOrGeoJSON.is()) { - ensureResource(Resource::source(urlOrGeoJSON.get())); + if (!geojsonSource->loaded) { + ensureResource(Resource::source(geojsonSource->getURL())); } break; } @@ -203,7 +216,7 @@ void OfflineDownload::activateDownload() { break; } } - + for (const auto& resource : spriteResources(parser)) { ensureResource(resource); } @@ -224,14 +237,16 @@ void OfflineDownload::ensureTiles(SourceType type, uint16_t tileSize, const Tile } } -void OfflineDownload::ensureResource(const Resource& resource, std::function callback) { +void OfflineDownload::ensureResource(const Resource& resource, + std::function callback) { status.requiredResourceCount++; auto workRequestsIt = requests.insert(requests.begin(), nullptr); - *workRequestsIt = util::RunLoop::Get()->invokeCancellable([=] () { + *workRequestsIt = util::RunLoop::Get()->invokeCancellable([=]() { requests.erase(workRequestsIt); - optional> offlineResponse = offlineDatabase.getRegionResource(id, resource); + optional> offlineResponse = + offlineDatabase.getRegionResource(id, resource); if (offlineResponse) { if (callback) { callback(offlineResponse->first); @@ -243,22 +258,22 @@ void OfflineDownload::ensureResource(const Resource& resource, std::functionsecond; } - + observer->statusChanged(status); - + if (status.complete()) { setState(OfflineRegionDownloadState::Inactive); } return; } - + if (checkTileCountLimit(resource)) { return; } auto fileRequestsIt = requests.insert(requests.begin(), nullptr); - *fileRequestsIt = onlineFileSource.request(resource, [=] (Response onlineResponse) { + *fileRequestsIt = onlineFileSource.request(resource, [=](Response onlineResponse) { if (onlineResponse.error) { observer->responseError(*onlineResponse.error); return; @@ -279,11 +294,11 @@ void OfflineDownload::ensureResource(const Resource& resource, std::functionstatusChanged(status); - + if (checkTileCountLimit(resource)) { return; } - + if (status.complete()) { setState(OfflineRegionDownloadState::Inactive); } @@ -292,14 +307,13 @@ void OfflineDownload::ensureResource(const Resource& resource, std::functionmapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit()); setState(OfflineRegionDownloadState::Inactive); return true; } - + return false; } diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index c9f7821e8..bfd5a5e8a 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -1713,6 +1713,7 @@ "$(OTHER_CFLAGS)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1731,6 +1732,7 @@ "$(OTHER_CFLAGS)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1763,6 +1765,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "$(sqlite_ldflags)", @@ -1804,6 +1807,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "$(sqlite_ldflags)", @@ -1862,6 +1866,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1897,6 +1902,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "-ObjC", diff --git a/platform/ios/scripts/configure.sh b/platform/ios/scripts/configure.sh index 200cedafa..82599f0fe 100644 --- a/platform/ios/scripts/configure.sh +++ b/platform/ios/scripts/configure.sh @@ -8,6 +8,8 @@ ZLIB_VERSION=system GEOMETRY_VERSION=0.8.0 GEOJSON_VERSION=0.1.4 GEOJSONVT_VERSION=6.1.2 +SUPERCLUSTER_VERSION=0.2.0 +KDBUSH_VERSION=0.1.1 VARIANT_VERSION=1.1.0 RAPIDJSON_VERSION=1.0.2 GTEST_VERSION=1.7.0 diff --git a/platform/linux/scripts/configure.sh b/platform/linux/scripts/configure.sh index c6cd419c5..3d3799486 100644 --- a/platform/linux/scripts/configure.sh +++ b/platform/linux/scripts/configure.sh @@ -17,6 +17,8 @@ NUNICODE_VERSION=1.6 GEOMETRY_VERSION=0.8.0 GEOJSON_VERSION=0.1.4${CXX11ABI:-} GEOJSONVT_VERSION=6.1.2 +SUPERCLUSTER_VERSION=0.2.0 +KDBUSH_VERSION=0.1.1 VARIANT_VERSION=1.1.0 RAPIDJSON_VERSION=1.0.2 GTEST_VERSION=1.7.0${CXX11ABI:-} diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 4c2b75659..ed980294a 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -1039,6 +1039,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "$(zlib_ldflags)", @@ -1078,6 +1079,7 @@ "$(rapidjson_cflags)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); OTHER_LDFLAGS = ( "$(zlib_ldflags)", @@ -1104,6 +1106,7 @@ "$(OTHER_CFLAGS)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1122,6 +1125,7 @@ "$(OTHER_CFLAGS)", "$(variant_cflags)", "$(geometry_cflags)", + "$(geojson_cflags)", ); PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/platform/macos/scripts/configure.sh b/platform/macos/scripts/configure.sh index 744597eb1..d408eef7e 100644 --- a/platform/macos/scripts/configure.sh +++ b/platform/macos/scripts/configure.sh @@ -11,6 +11,8 @@ NUNICODE_VERSION=1.6 GEOMETRY_VERSION=0.8.0 GEOJSON_VERSION=0.1.4 GEOJSONVT_VERSION=6.1.2 +SUPERCLUSTER_VERSION=0.2.0 +KDBUSH_VERSION=0.1.1 VARIANT_VERSION=1.1.0 RAPIDJSON_VERSION=1.0.2 GTEST_VERSION=1.7.0 diff --git a/platform/qt/scripts/configure.sh b/platform/qt/scripts/configure.sh index 8db059918..23f4b0bf5 100644 --- a/platform/qt/scripts/configure.sh +++ b/platform/qt/scripts/configure.sh @@ -8,6 +8,8 @@ BOOST_VERSION=1.60.0 GEOMETRY_VERSION=0.8.0 GEOJSON_VERSION=0.1.4${CXX11ABI:-} GEOJSONVT_VERSION=6.1.2 +SUPERCLUSTER_VERSION=0.2.0 +KDBUSH_VERSION=0.1.1 GTEST_VERSION=1.7.0${CXX11ABI:-} LIBJPEG_TURBO_VERSION=1.4.2 PIXELMATCH_VERSION=0.9.0 diff --git a/src/mbgl/style/sources/geojson_source.cpp b/src/mbgl/style/sources/geojson_source.cpp index a3eec4f4e..2bf27880b 100644 --- a/src/mbgl/style/sources/geojson_source.cpp +++ b/src/mbgl/style/sources/geojson_source.cpp @@ -4,17 +4,22 @@ namespace mbgl { namespace style { -GeoJSONSource::GeoJSONSource(const std::string& id) - : Source(SourceType::GeoJSON, std::make_unique(std::move(id), *this)) - , impl(static_cast(baseImpl.get())) { +GeoJSONSource::GeoJSONSource(const std::string& id, const GeoJSONOptions options) + : Source(SourceType::GeoJSON, + std::make_unique(std::move(id), *this, options)), + impl(static_cast(baseImpl.get())) { } void GeoJSONSource::setURL(const std::string& url) { impl->setURL(url); } -void GeoJSONSource::setGeoJSON(GeoJSON&& geoJSON) { - impl->setGeoJSON(std::move(geoJSON)); +void GeoJSONSource::setGeoJSON(const mapbox::geojson::geojson& geoJSON) { + impl->setGeoJSON(geoJSON); +} + +std::string GeoJSONSource::getURL() { + return impl->getURL(); } } // namespace style diff --git a/src/mbgl/style/sources/geojson_source_impl.cpp b/src/mbgl/style/sources/geojson_source_impl.cpp index 9e6b3d34f..0821ac023 100644 --- a/src/mbgl/style/sources/geojson_source_impl.cpp +++ b/src/mbgl/style/sources/geojson_source_impl.cpp @@ -1,43 +1,37 @@ -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include -using namespace mapbox::geojsonvt; - namespace mbgl { namespace style { namespace conversion { template <> Result convertGeoJSON(const JSValue& value) { - Options options; - options.buffer = util::EXTENT / util::tileSize * 128; - options.extent = util::EXTENT; - try { - const auto geojson = mapbox::geojson::convert(value); - return GeoJSON { std::make_unique(geojson, options) }; + return mapbox::geojson::convert(value); } catch (const std::exception& ex) { - return Error { ex.what() }; + return Error{ ex.what() }; } } } // namespace conversion -GeoJSONSource::Impl::Impl(std::string id_, Source& base_) - : Source::Impl(SourceType::GeoJSON, std::move(id_), base_) { +GeoJSONSource::Impl::Impl(std::string id_, Source& base_, const GeoJSONOptions options_) + : Source::Impl(SourceType::GeoJSON, std::move(id_), base_), options(options_) { } GeoJSONSource::Impl::~Impl() = default; @@ -46,12 +40,36 @@ void GeoJSONSource::Impl::setURL(std::string url) { urlOrGeoJSON = std::move(url); } -void GeoJSONSource::Impl::setGeoJSON(GeoJSON&& geoJSON) { - urlOrGeoJSON = std::move(geoJSON); +std::string GeoJSONSource::Impl::getURL() { + assert(urlOrGeoJSON.is()); + return urlOrGeoJSON.get(); +} + +void GeoJSONSource::Impl::setGeoJSON(const GeoJSON& geoJSON) { + double scale = util::EXTENT / util::tileSize; + + if (!options.cluster) { + mapbox::geojsonvt::Options vtOptions; + vtOptions.maxZoom = options.maxzoom; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = std::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + urlOrGeoJSON = std::make_unique(geoJSON, vtOptions); + + } else { + mapbox::supercluster::Options clusterOptions; + clusterOptions.maxZoom = options.clusterMaxZoom; + clusterOptions.extent = util::EXTENT; + clusterOptions.radius = std::round(scale * options.clusterRadius); + + const auto& features = geoJSON.get>(); + urlOrGeoJSON = + std::make_unique(features, clusterOptions); + } } void GeoJSONSource::Impl::load(FileSource& fileSource) { - if (urlOrGeoJSON.is()) { + if (!urlOrGeoJSON.is()) { loaded = true; return; } @@ -63,19 +81,23 @@ void GeoJSONSource::Impl::load(FileSource& fileSource) { const std::string& url = urlOrGeoJSON.get(); req = fileSource.request(Resource::source(url), [this](Response res) { if (res.error) { - observer->onSourceError(base, std::make_exception_ptr(std::runtime_error(res.error->message))); + observer->onSourceError( + base, std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified) { return; } else if (res.noContent) { - observer->onSourceError(base, std::make_exception_ptr(std::runtime_error("unexpectedly empty GeoJSON"))); + observer->onSourceError( + base, std::make_exception_ptr(std::runtime_error("unexpectedly empty GeoJSON"))); } else { rapidjson::GenericDocument, rapidjson::CrtAllocator> d; d.Parse<0>(res.data->c_str()); if (d.HasParseError()) { std::stringstream message; - message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError()); - observer->onSourceError(base, std::make_exception_ptr(std::runtime_error(message.str()))); + message << d.GetErrorOffset() << " - " + << rapidjson::GetParseError_En(d.GetParseError()); + observer->onSourceError(base, + std::make_exception_ptr(std::runtime_error(message.str()))); return; } @@ -83,13 +105,13 @@ void GeoJSONSource::Impl::load(FileSource& fileSource) { conversion::Result geoJSON = conversion::convertGeoJSON(d); if (!geoJSON) { - Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s", geoJSON.error().message.c_str()); + Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s", + geoJSON.error().message.c_str()); // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for // tiles to load. - mapbox::geojson::feature_collection features; - urlOrGeoJSON = GeoJSON { std::make_unique(features) }; + setGeoJSON(GeoJSON{ FeatureCollection{} }); } else { - urlOrGeoJSON = std::move(*geoJSON); + setGeoJSON(*geoJSON); } loaded = true; @@ -100,13 +122,20 @@ void GeoJSONSource::Impl::load(FileSource& fileSource) { Range GeoJSONSource::Impl::getZoomRange() { assert(loaded); - return { 0, urlOrGeoJSON.get().impl->options.maxZoom }; + return { 0, options.maxzoom }; } std::unique_ptr GeoJSONSource::Impl::createTile(const OverscaledTileID& tileID, const UpdateParameters& parameters) { assert(loaded); - return std::make_unique(tileID, base.getID(), parameters, *urlOrGeoJSON.get().impl); + if (urlOrGeoJSON.is()) { + return std::make_unique(tileID, base.getID(), parameters, + *urlOrGeoJSON.get()); + } else { + assert(urlOrGeoJSON.is()); + return std::make_unique(tileID, base.getID(), parameters, + *urlOrGeoJSON.get()); + } } } // namespace style diff --git a/src/mbgl/style/sources/geojson_source_impl.hpp b/src/mbgl/style/sources/geojson_source_impl.hpp index e6e01c06e..eb3563e85 100644 --- a/src/mbgl/style/sources/geojson_source_impl.hpp +++ b/src/mbgl/style/sources/geojson_source_impl.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include namespace mbgl { @@ -12,11 +12,13 @@ namespace style { class GeoJSONSource::Impl : public Source::Impl { public: - Impl(std::string id, Source&); + Impl(std::string id, Source&, const GeoJSONOptions); ~Impl() final; void setURL(std::string); - void setGeoJSON(GeoJSON&&); + void setGeoJSON(const GeoJSON&); + + std::string getURL(); void load(FileSource&) final; @@ -24,16 +26,14 @@ public: return util::tileSize; } - const variant& getURLOrGeoJSON() const { - return urlOrGeoJSON; - } - private: Range getZoomRange() final; std::unique_ptr createTile(const OverscaledTileID&, const UpdateParameters&) final; - variant urlOrGeoJSON; + variant urlOrGeoJSON; std::unique_ptr req; + + GeoJSONOptions options; }; } // namespace style diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index 0dc4ac910..ab596bd9b 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace mbgl { @@ -46,17 +47,16 @@ private: }; // Converts the geojsonvt::Tile to a a GeoJSONTile. They have a differing internal structure. -std::unique_ptr convertTile(const mapbox::geojsonvt::Tile& tile) { +std::unique_ptr convertTile(const mapbox::geometry::feature_collection& features) { std::shared_ptr layer; - if (!tile.features.empty()) { - std::vector> features; - GeometryCoordinates line; + if (!features.empty()) { + std::vector> convertedFeatures; ToFeatureType toFeatureType; ToGeometryCollection toGeometryCollection; - for (auto& feature : tile.features) { + for (auto& feature : features) { const FeatureType featureType = apply_visitor(toFeatureType, feature.geometry); if (featureType == FeatureType::Unknown) { @@ -72,11 +72,11 @@ std::unique_ptr convertTile(const mapbox::geojsonvt::Tile& tile PropertyMap properties = feature.properties; - features.emplace_back(std::make_shared( + convertedFeatures.emplace_back(std::make_shared( featureType, std::move(geometry), std::move(properties))); } - layer = std::make_unique(std::move(features)); + layer = std::make_unique(std::move(convertedFeatures)); } return std::make_unique(layer); @@ -87,7 +87,15 @@ GeoJSONTile::GeoJSONTile(const OverscaledTileID& overscaledTileID, const style::UpdateParameters& parameters, mapbox::geojsonvt::GeoJSONVT& geojsonvt) : GeometryTile(overscaledTileID, sourceID_, parameters.style, parameters.mode) { - setData(convertTile(geojsonvt.getTile(id.canonical.z, id.canonical.x, id.canonical.y))); + setData(convertTile(geojsonvt.getTile(id.canonical.z, id.canonical.x, id.canonical.y).features)); +} + +GeoJSONTile::GeoJSONTile(const OverscaledTileID& overscaledTileID, + std::string sourceID_, + const style::UpdateParameters& parameters, + mapbox::supercluster::Supercluster& supercluster) + : GeometryTile(overscaledTileID, sourceID_, parameters.style, parameters.mode) { + setData(convertTile(supercluster.getTile(id.canonical.z, id.canonical.x, id.canonical.y))); } void GeoJSONTile::setNecessity(Necessity) {} diff --git a/src/mbgl/tile/geojson_tile.hpp b/src/mbgl/tile/geojson_tile.hpp index 09fdd1ec9..422101b76 100644 --- a/src/mbgl/tile/geojson_tile.hpp +++ b/src/mbgl/tile/geojson_tile.hpp @@ -3,9 +3,15 @@ #include namespace mapbox { + namespace geojsonvt { class GeoJSONVT; } // namespace geojsonvt + +namespace supercluster { +class Supercluster; +} // namespace supercluster + } // namespace mapbox namespace mbgl { @@ -21,6 +27,11 @@ public: const style::UpdateParameters&, mapbox::geojsonvt::GeoJSONVT&); + GeoJSONTile(const OverscaledTileID&, + std::string sourceID, + const style::UpdateParameters&, + mapbox::supercluster::Supercluster&); + void setNecessity(Necessity) final; }; diff --git a/src/mbgl/util/geojson.cpp b/src/mbgl/util/geojson.cpp deleted file mode 100644 index a17d01f93..000000000 --- a/src/mbgl/util/geojson.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -#include - -namespace mbgl { - -GeoJSON::GeoJSON(std::unique_ptr impl_) : impl(std::move(impl_)) {} -GeoJSON::GeoJSON(GeoJSON&&) = default; -GeoJSON::~GeoJSON() = default; - -} // namespace mbgl diff --git a/test/test.gypi b/test/test.gypi index 304e0e973..82bd02bd8 100644 --- a/test/test.gypi +++ b/test/test.gypi @@ -103,6 +103,8 @@ '<@(boost_cflags)', '<@(sqlite_cflags)', '<@(geojsonvt_cflags)', + '<@(supercluster_cflags)', + '<@(kdbush_cflags)', '<@(rapidjson_cflags)', '<@(pixelmatch_cflags)', '<@(earcut_cflags)', @@ -113,8 +115,7 @@ ], 'libraries': [ '<@(gtest_static_libs)', - '<@(sqlite_static_libs)', - '<@(geojsonvt_static_libs)', + '<@(sqlite_static_libs)' ], },