From 028abaab00f2ff3a19c67366c3a9f8c803e27423 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 22 Sep 2015 16:58:38 -0700 Subject: [PATCH 1/9] Ensure that LiveTileData can be reparsed Annotation tiles may become partially parsed just like regular tiles, for example if a point annotation is added to the map before the style's sprite has been loaded. In such cases, they need to be reparsed or the annotation will not be rendered. Previously, the code path for reparsing would be short-circuited by a dynamic_cast followed by a null check. This commit removes that case and adds (back) a virtual reparse method to the TileData interface. --- .gitignore | 1 + src/mbgl/map/live_tile_data.cpp | 19 +++++++++++++++++-- src/mbgl/map/live_tile_data.hpp | 4 ++++ src/mbgl/map/source.cpp | 8 +++----- src/mbgl/map/tile_data.hpp | 1 + src/mbgl/map/vector_tile_data.hpp | 2 +- test/api/annotations.cpp | 28 +++++++++++++--------------- test/fixtures/api/empty.json | 6 ++++++ test/output/.gitkeep | 0 9 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/api/empty.json create mode 100644 test/output/.gitkeep diff --git a/.gitignore b/.gitignore index bd6707ba9a1..4d0b4074c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ /test/fixtures/api/1.png /test/fixtures/api/2.png /test/fixtures/database/*.db +/test/output /include/mbgl/shader/shaders.hpp /src/shader/shaders_gl.cpp /src/shader/shaders_gles2.cpp diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 0b73b52f36b..fbb9c10de06 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -13,7 +13,7 @@ using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, - const LiveTile* tile, + const LiveTile* tile_, Style& style_, const SourceInfo& source_, std::function callback) @@ -25,7 +25,8 @@ LiveTileData::LiveTileData(const TileID& id_, style_, style_.layers, state, - std::make_unique(0, 0, false)) { + std::make_unique(0, 0, false)), + tile(tile_) { state = State::loaded; if (!tile) { @@ -33,7 +34,19 @@ LiveTileData::LiveTileData(const TileID& id_, return; } + reparse(callback); +} + +bool LiveTileData::reparse(std::function callback) { + if (parsing || (state != State::loaded && state != State::partial)) { + return false; + } + + parsing = true; + workRequest = worker.parseLiveTile(tileWorker, *tile, [this, callback] (TileParseResult result) { + parsing = false; + if (result.is()) { state = result.get(); } else { @@ -43,6 +56,8 @@ LiveTileData::LiveTileData(const TileID& id_, callback(); }); + + return true; } LiveTileData::~LiveTileData() { diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index 5c4220fd676..c6507bbc3e2 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -20,6 +20,8 @@ class LiveTileData : public TileData { std::function callback); ~LiveTileData(); + bool reparse(std::function callback) override; + void cancel() override; Bucket* getBucket(const StyleLayer&) override; @@ -27,6 +29,8 @@ class LiveTileData : public TileData { Worker& worker; TileWorker tileWorker; std::unique_ptr workRequest; + bool parsing = false; + const LiveTile* tile; }; } diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 5899fea1ecc..572973b79aa 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -234,15 +234,13 @@ bool Source::handlePartialTile(const TileID& id, Worker&) { return true; } - // Note: this uses a raw pointer; we don't want the callback binding to have a - // shared pointer. - VectorTileData* data = dynamic_cast(it->second.lock().get()); + auto data = it->second.lock(); if (!data) { return true; } - return data->reparse([this, data]() { - emitTileLoaded(false); + return data->reparse([this]() { + emitTileLoaded(false); }); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 047ccd5cd02..fcbf25fb629 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -72,6 +72,7 @@ class TileData : private util::noncopyable { virtual Bucket* getBucket(const StyleLayer&) = 0; + virtual bool reparse(std::function) { return true; } virtual void redoPlacement(float, float, bool) {} bool isReady() const { diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 4db0cbe0049..c004e804b7a 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -28,7 +28,7 @@ class VectorTileData : public TileData { void request(float pixelRatio, const std::function& callback); - bool reparse(std::function callback); + bool reparse(std::function callback) override; void redoPlacement(float angle, float pitch, bool collisionDebug) override; diff --git a/test/api/annotations.cpp b/test/api/annotations.cpp index 33374d43864..39862b1069d 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -1,5 +1,3 @@ -#include "../fixtures/fixture_log_observer.hpp" -#include "../fixtures/mock_file_source.hpp" #include "../fixtures/util.hpp" #include @@ -7,30 +5,30 @@ #include #include #include +#include +#include +#include #include #include -TEST(API, PointAnnotation) { +TEST(Annotations, PointAnnotation) { using namespace mbgl; auto display = std::make_shared(); HeadlessView view(display, 1); - - MockFileSource fileSource(MockFileSource::Success, ""); + DefaultFileSource fileSource(nullptr); Map map(view, fileSource, MapMode::Still); - map.setStyleURL("test/fixtures/resources/style.json"); - - std::vector points; - points.emplace_back(PointAnnotation({ 50.0, 50.0 }, "default_marker")); - - map.addPointAnnotations(points); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + map.addPointAnnotation(PointAnnotation({ 0, 0 }, "default_marker")); - std::promise promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr) { - promise.set_value(true); + std::promise> promise; + map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { + promise.set_value(std::move(image)); }); - promise.get_future().get(); + auto result = promise.get_future().get(); + const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); + util::write_file("test/output/point_annotation.png", png); } diff --git a/test/fixtures/api/empty.json b/test/fixtures/api/empty.json new file mode 100644 index 00000000000..acc8a630cab --- /dev/null +++ b/test/fixtures/api/empty.json @@ -0,0 +1,6 @@ +{ + "version": 8, + "sources": {}, + "layers": [], + "sprite": "asset://TEST_DATA/fixtures/resources/sprite" +} diff --git a/test/output/.gitkeep b/test/output/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d From 931c56a5967f8183f990a7c5c52874ec1a00fd3a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 22 Sep 2015 17:32:39 -0700 Subject: [PATCH 2/9] Add some shape annotation tests --- test/api/annotations.cpp | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/api/annotations.cpp b/test/api/annotations.cpp index 39862b1069d..a9ebb4ab014 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -1,6 +1,8 @@ #include "../fixtures/util.hpp" #include +#include +#include #include #include #include @@ -32,3 +34,64 @@ TEST(Annotations, PointAnnotation) { const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); util::write_file("test/output/point_annotation.png", png); } + +TEST(Annotations, LineAnnotation) { + using namespace mbgl; + + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + + AnnotationSegments segments = {{ {{ { 0, 0 }, { 45, 45 } }} }}; + + LineProperties lineProperties; + lineProperties.color = {{ 255, 0, 0, 1 }}; + lineProperties.width = 5; + + StyleProperties styleProperties; + styleProperties.set(lineProperties); + + map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); + + std::promise> promise; + map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { + promise.set_value(std::move(image)); + }); + + auto result = promise.get_future().get(); + const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); + util::write_file("test/output/line_annotation.png", png); +} + +TEST(Annotations, FillAnnotation) { + using namespace mbgl; + + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + + AnnotationSegments segments = {{ {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }}; + + FillProperties fillProperties; + fillProperties.fill_color = {{ 255, 0, 0, 1 }}; + + StyleProperties styleProperties; + styleProperties.set(fillProperties); + + map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); + + std::promise> promise; + map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { + promise.set_value(std::move(image)); + }); + + auto result = promise.get_future().get(); + const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); + util::write_file("test/output/fill_annotation.png", png); +} From dd0e6e0257eee0b3c56190f7771e27f49813c7cf Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Sep 2015 16:45:06 -0700 Subject: [PATCH 3/9] Inline AnnotationManager::addTileFeature --- src/mbgl/map/annotation.cpp | 50 +++++++++++-------------------------- src/mbgl/map/annotation.hpp | 9 ------- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 9cfb857bcbc..917dc2f981d 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -77,27 +77,6 @@ vec2 AnnotationManager::projectPoint(const LatLng& point) { return { x, y }; } -std::unordered_set -AnnotationManager::addTileFeature(const uint32_t annotationID, - const AnnotationSegments& segments, - const std::vector>>& projectedFeature, - const AnnotationType& type, - const StyleProperties& styleProperties, - const std::unordered_map& featureProperties, - const uint8_t maxZoom) { - - assert(type != AnnotationType::Any); - - // track the annotation global ID and its original geometry - annotations.emplace(annotationID, std::make_unique(type, segments, styleProperties)); - - if (type == AnnotationType::Shape) { - return addShapeFeature(annotationID, segments, styleProperties, maxZoom); - } else { - return addPointFeature(annotationID, projectedFeature, featureProperties, maxZoom); - } -} - std::unordered_set AnnotationManager::addShapeFeature(const uint32_t annotationID, const AnnotationSegments& segments, @@ -233,16 +212,17 @@ AnnotationManager::addPointAnnotations(const std::vector& point pointFeatureProperties.emplace("sprite", defaultPointAnnotationSymbol); } - // add individual point tile feature - auto featureAffectedTiles = addTileFeature( - pointAnnotationID, + // track the annotation global ID and its original geometry + annotations.emplace(pointAnnotationID, std::make_unique( + AnnotationType::Point, AnnotationSegments({{ point.position }}), + StyleProperties({{ }}))); + + auto featureAffectedTiles = addPointFeature( + pointAnnotationID, std::vector>>({{ pp }}), - AnnotationType::Point, - {{ }}, pointFeatureProperties, - maxZoom - ); + maxZoom); std::copy(featureAffectedTiles.begin(), featureAffectedTiles.end(), std::inserter(affectedTiles, affectedTiles.begin())); @@ -273,15 +253,13 @@ AnnotationManager::addShapeAnnotations(const std::vector& shape // current shape tiles are on-the-fly, so we don't get any "affected tiles" // and just expire all annotation tiles for shape adds - addTileFeature( - shapeAnnotationID, - shape.segments, - {{ }}, + // track the annotation global ID and its original geometry + annotations.emplace(shapeAnnotationID, std::make_unique( AnnotationType::Shape, - shape.styleProperties, - {{ }}, - maxZoom - ); + shape.segments, + shape.styleProperties)); + + addShapeFeature(shapeAnnotationID, shape.segments, shape.styleProperties, maxZoom); annotationIDs.push_back(shapeAnnotationID); } diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index 7691e4508e2..ddae05b6ff6 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -81,15 +81,6 @@ class AnnotationManager : private util::noncopyable { inline uint32_t nextID(); static vec2 projectPoint(const LatLng& point); - std::unordered_set - addTileFeature(const uint32_t annotationID, - const AnnotationSegments&, - const std::vector>>& projectedFeature, - const AnnotationType&, - const StyleProperties&, - const std::unordered_map& featureProperties, - const uint8_t maxZoom); - std::unordered_set addShapeFeature(const uint32_t annotationID, const AnnotationSegments& segments, From bc9aaa5e9101d766f9f533110e2bf458a947b9cc Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Sep 2015 16:59:29 -0700 Subject: [PATCH 4/9] Move entire loop body to helper function --- src/mbgl/map/annotation.cpp | 118 ++++++++++++++---------------------- src/mbgl/map/annotation.hpp | 29 ++++----- 2 files changed, 57 insertions(+), 90 deletions(-) diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 917dc2f981d..1252399b85d 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -77,13 +77,14 @@ vec2 AnnotationManager::projectPoint(const LatLng& point) { return { x, y }; } -std::unordered_set -AnnotationManager::addShapeFeature(const uint32_t annotationID, - const AnnotationSegments& segments, - const StyleProperties& styleProperties, - const uint8_t maxZoom) { - // Currently unused. - std::unordered_set affectedTiles; +uint32_t +AnnotationManager::addShapeAnnotation(const ShapeAnnotation& shape, const uint8_t maxZoom) { + const uint32_t annotationID = nextID(); + + annotations.emplace(annotationID, std::make_unique( + AnnotationType::Shape, + shape.segments, + shape.styleProperties)); orderedShapeAnnotations.push_back(annotationID); @@ -99,14 +100,14 @@ AnnotationManager::addShapeFeature(const uint32_t annotationID, std::vector points; - for (size_t i = 0; i < segments[0].size(); ++i) { // first segment for now (no holes) - const double constraintedLatitude = ::fmin(::fmax(segments[0][i].latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); - points.push_back(LonLat(segments[0][i].longitude, constraintedLatitude)); + for (size_t i = 0; i < shape.segments[0].size(); ++i) { // first segment for now (no holes) + const double constraintedLatitude = ::fmin(::fmax(shape.segments[0][i].latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); + points.push_back(LonLat(shape.segments[0][i].longitude, constraintedLatitude)); } ProjectedFeatureType featureType; - if (styleProperties.is()) { + if (shape.styleProperties.is()) { featureType = ProjectedFeatureType::Polygon; if (points.front().lon != points.back().lon || points.front().lat != points.back().lat) { @@ -124,21 +125,31 @@ AnnotationManager::addShapeFeature(const uint32_t annotationID, shapeTilers.emplace(annotationID, std::make_unique(features, maxZoom, 4, 100, 10)); - return affectedTiles; + return annotationID; } -std::unordered_set -AnnotationManager::addPointFeature(const uint32_t annotationID, - const std::vector>>& projectedFeature, - const std::unordered_map& featureProperties, - const uint8_t maxZoom) { - std::unordered_set affectedTiles; +uint32_t +AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_t maxZoom, + std::unordered_set& affectedTiles) { + const uint32_t annotationID = nextID(); - auto anno_it = annotations.find(annotationID); - assert(anno_it != annotations.end()); + // at render time we style the point according to its {sprite} field + std::unordered_map featureProperties; + if (point.icon.length()) { + featureProperties.emplace("sprite", point.icon); + } else { + featureProperties.emplace("sprite", defaultPointAnnotationSymbol); + } + + std::unique_ptr annotation = std::make_unique( + AnnotationType::Point, + AnnotationSegments({{ point.position }}), + StyleProperties({{ }})); const uint16_t extent = 4096; - auto& pp = projectedFeature[0][0]; + + // projection conversion into unit space + vec2 pp = projectPoint(point.position); for (int8_t z = maxZoom; z >= 0; z--) { uint32_t z2 = 1 << z; @@ -176,16 +187,18 @@ AnnotationManager::addPointFeature(const uint32_t annotationID, // Record annotation association with tile and tile feature. This is used to determine stale tiles, // as well as to remove the feature from the tile upon annotation deletion. - anno_it->second->tilePointFeatures.emplace(featureTileID, std::weak_ptr(feature)); + annotation->tilePointFeatures.emplace(featureTileID, std::weak_ptr(feature)); // track affected tile affectedTiles.insert(featureTileID); } - return affectedTiles; + annotations.emplace(annotationID, std::move(annotation)); + + return annotationID; } -std::pair, AnnotationIDs> +std::pair AnnotationManager::addPointAnnotations(const std::vector& points, const uint8_t maxZoom) { // We pre-generate tiles to contain each annotation up to the map's max zoom. @@ -197,43 +210,17 @@ AnnotationManager::addPointAnnotations(const std::vector& point AnnotationIDs annotationIDs; annotationIDs.reserve(points.size()); - std::unordered_set affectedTiles; - - for (const PointAnnotation& point : points) { - // projection conversion into unit space - const auto pp = projectPoint(point.position); - const uint32_t pointAnnotationID = nextID(); + AffectedTiles affectedTiles; - // at render time we style the point according to its {sprite} field - std::unordered_map pointFeatureProperties; - if (point.icon.length()) { - pointFeatureProperties.emplace("sprite", point.icon); - } else { - pointFeatureProperties.emplace("sprite", defaultPointAnnotationSymbol); - } - - // track the annotation global ID and its original geometry - annotations.emplace(pointAnnotationID, std::make_unique( - AnnotationType::Point, - AnnotationSegments({{ point.position }}), - StyleProperties({{ }}))); - - auto featureAffectedTiles = addPointFeature( - pointAnnotationID, - std::vector>>({{ pp }}), - pointFeatureProperties, - maxZoom); - - std::copy(featureAffectedTiles.begin(), featureAffectedTiles.end(), std::inserter(affectedTiles, affectedTiles.begin())); - - annotationIDs.push_back(pointAnnotationID); + for (const auto& point : points) { + annotationIDs.push_back(addPointAnnotation(point, maxZoom, affectedTiles)); } // Tile:IDs that need refreshed and the annotation identifiers held onto by the client. return std::make_pair(affectedTiles, annotationIDs); } -std::pair, AnnotationIDs> +std::pair AnnotationManager::addShapeAnnotations(const std::vector& shapes, const uint8_t maxZoom) { // We pre-generate tiles to contain each annotation up to the map's max zoom. @@ -245,27 +232,14 @@ AnnotationManager::addShapeAnnotations(const std::vector& shape AnnotationIDs annotationIDs; annotationIDs.reserve(shapes.size()); - std::unordered_set affectedTiles; - - for (const ShapeAnnotation& shape : shapes) { - const uint32_t shapeAnnotationID = nextID(); - - // current shape tiles are on-the-fly, so we don't get any "affected tiles" - // and just expire all annotation tiles for shape adds - - // track the annotation global ID and its original geometry - annotations.emplace(shapeAnnotationID, std::make_unique( - AnnotationType::Shape, - shape.segments, - shape.styleProperties)); - - addShapeFeature(shapeAnnotationID, shape.segments, shape.styleProperties, maxZoom); - - annotationIDs.push_back(shapeAnnotationID); + for (const auto& shape : shapes) { + annotationIDs.push_back(addShapeAnnotation(shape, maxZoom)); } // Tile:IDs that need refreshed and the annotation identifiers held onto by the client. - return std::make_pair(affectedTiles, annotationIDs); + // Shapes are tiled "on-the-fly", so we don't get any "affected tiles" and just expire + // all annotation tiles for shape adds. + return std::make_pair(AffectedTiles(), annotationIDs); } std::unordered_set AnnotationManager::removeAnnotations(const AnnotationIDs& ids, diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index ddae05b6ff6..6f76b697fe2 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -48,6 +48,8 @@ class Annotation : private util::noncopyable { class AnnotationManager : private util::noncopyable { public: + typedef std::unordered_set AffectedTiles; + AnnotationManager(); ~AnnotationManager(); @@ -57,15 +59,15 @@ class AnnotationManager : private util::noncopyable { void setDefaultPointAnnotationSymbol(const std::string& symbol); - std::pair, AnnotationIDs> - addPointAnnotations(const std::vector&, - const uint8_t maxZoom); + std::pair + addPointAnnotations(const std::vector&, const uint8_t maxZoom); + + std::pair + addShapeAnnotations(const std::vector&, const uint8_t maxZoom); - std::pair, AnnotationIDs> - addShapeAnnotations(const std::vector&, - const uint8_t maxZoom); + AffectedTiles + removeAnnotations(const AnnotationIDs&, const uint8_t maxZoom); - std::unordered_set removeAnnotations(const AnnotationIDs&, const uint8_t maxZoom); AnnotationIDs getOrderedShapeAnnotations() const { return orderedShapeAnnotations; } const StyleProperties getAnnotationStyleProperties(uint32_t) const; @@ -81,17 +83,8 @@ class AnnotationManager : private util::noncopyable { inline uint32_t nextID(); static vec2 projectPoint(const LatLng& point); - std::unordered_set - addShapeFeature(const uint32_t annotationID, - const AnnotationSegments& segments, - const StyleProperties& styleProperties, - const uint8_t maxZoom); - - std::unordered_set - addPointFeature(const uint32_t annotationID, - const std::vector>>& projectedFeature, - const std::unordered_map& featureProperties, - const uint8_t maxZoom); + uint32_t addShapeAnnotation(const ShapeAnnotation&, const uint8_t maxZoom); + uint32_t addPointAnnotation(const PointAnnotation&, const uint8_t maxZoom, AffectedTiles&); private: std::string defaultPointAnnotationSymbol; From 588fed9a9d01e03165ff70b537b892995a1d24e3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Sep 2015 17:32:50 -0700 Subject: [PATCH 5/9] Move Annotation class to cpp file --- src/mbgl/map/annotation.cpp | 17 +++++++++++++++-- src/mbgl/map/annotation.hpp | 20 -------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 1252399b85d..cfc9b4fae57 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -8,12 +8,25 @@ #include #include - #include -#include namespace mbgl { +class Annotation : private util::noncopyable { +public: + Annotation(AnnotationType, const AnnotationSegments&, const StyleProperties&); + + const StyleProperties styleProperties; + + LatLng getPoint() const; + LatLngBounds getBounds() const { return bounds; } + + const AnnotationType type = AnnotationType::Point; + const AnnotationSegments geometry; + std::unordered_map, TileID::Hash> tilePointFeatures; + const LatLngBounds bounds; +}; + Annotation::Annotation(AnnotationType type_, const AnnotationSegments& geometry_, const StyleProperties& styleProperties_) diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index 6f76b697fe2..60cafdf342d 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -23,29 +23,9 @@ class Annotation; class PointAnnotation; class ShapeAnnotation; class LiveTile; -class LiveTileFeature; using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT; -class Annotation : private util::noncopyable { - friend class AnnotationManager; -public: - Annotation(AnnotationType, const AnnotationSegments&, const StyleProperties&); - -public: - const StyleProperties styleProperties; - -private: - LatLng getPoint() const; - LatLngBounds getBounds() const { return bounds; } - -private: - const AnnotationType type = AnnotationType::Point; - const AnnotationSegments geometry; - std::unordered_map, TileID::Hash> tilePointFeatures; - const LatLngBounds bounds; -}; - class AnnotationManager : private util::noncopyable { public: typedef std::unordered_set AffectedTiles; From 0c4c7ed901a2c984f120b1f23abf0dd528991b38 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Sep 2015 17:39:17 -0700 Subject: [PATCH 6/9] =?UTF-8?q?map/annotation.*=20=E2=87=A2=20annotation/a?= =?UTF-8?q?nnotation=5Fmanager.*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{map/annotation.cpp => annotation/annotation_manager.cpp} | 2 +- .../{map/annotation.hpp => annotation/annotation_manager.hpp} | 0 src/mbgl/map/live_tile_data.cpp | 1 - src/mbgl/map/map_context.cpp | 1 - src/mbgl/map/map_data.hpp | 2 +- src/mbgl/style/style_parser.cpp | 2 +- 6 files changed, 3 insertions(+), 5 deletions(-) rename src/mbgl/{map/annotation.cpp => annotation/annotation_manager.cpp} (99%) rename src/mbgl/{map/annotation.hpp => annotation/annotation_manager.hpp} (100%) diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/annotation/annotation_manager.cpp similarity index 99% rename from src/mbgl/map/annotation.cpp rename to src/mbgl/annotation/annotation_manager.cpp index cfc9b4fae57..427401eea47 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/annotation/annotation_manager.hpp similarity index 100% rename from src/mbgl/map/annotation.hpp rename to src/mbgl/annotation/annotation_manager.hpp diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index fbb9c10de06..dec7d40263a 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 0ebcd73f8b4..5858918ae9c 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include diff --git a/src/mbgl/map/map_data.hpp b/src/mbgl/map/map_data.hpp index aadc8dcd640..9cc76e8b825 100644 --- a/src/mbgl/map/map_data.hpp +++ b/src/mbgl/map/map_data.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include namespace mbgl { diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index 6dc92a8d3dc..2e298e9a6c3 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include From 24a58344284ca8c882bf2ab83b9129ca8ce9d4b3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 22 Sep 2015 18:09:40 -0700 Subject: [PATCH 7/9] Move updateAnnotationTiles[IfNeeded] to AnnotationManager --- src/mbgl/annotation/annotation_manager.cpp | 103 ++++++++++++++++--- src/mbgl/annotation/annotation_manager.hpp | 12 +-- src/mbgl/map/map_context.cpp | 111 +-------------------- src/mbgl/map/map_context.hpp | 1 - 4 files changed, 97 insertions(+), 130 deletions(-) diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index 427401eea47..31b50b1c51c 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include @@ -63,14 +66,6 @@ AnnotationManager::~AnnotationManager() { // Annotation so we can't destruct the object with just the header file. } -void AnnotationManager::markStaleTiles(std::unordered_set ids) { - std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin())); -} - -std::unordered_set AnnotationManager::resetStaleTiles() { - return std::move(staleTiles); -} - void AnnotationManager::setDefaultPointAnnotationSymbol(const std::string& symbol) { defaultPointAnnotationSymbol = symbol; } @@ -322,13 +317,6 @@ std::unordered_set AnnotationManager::removeAnnotations(co return affectedTiles; } -const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t annotationID) const { - auto anno_it = annotations.find(annotationID); - assert(anno_it != annotations.end()); - - return anno_it->second->styleProperties; -} - AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds, const uint8_t maxZoom, const AnnotationType& type) const { @@ -490,6 +478,91 @@ const LiveTile* AnnotationManager::getTile(const TileID& id) { return renderTile; } +void AnnotationManager::updateTilesIfNeeded(Style* style) { + if (!staleTiles.empty()) { + updateTiles(staleTiles, style); + } +} + +void AnnotationManager::updateTiles(const AffectedTiles& ids, Style* style) { + std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin())); + + if (!style) { + return; + } + + // grab existing, single shape annotations source + const auto& shapeID = AnnotationManager::ShapeLayerID; + Source* shapeAnnotationSource = style->getSource(shapeID); + + // Style not parsed yet + if (!shapeAnnotationSource) { + return; + } + + shapeAnnotationSource->enabled = true; + + const auto& layers = style->layers; + + // create (if necessary) layers and buckets for each shape + for (const auto& shapeAnnotationID : orderedShapeAnnotations) { + const std::string shapeLayerID = shapeID + "." + util::toString(shapeAnnotationID); + + if (std::find_if(layers.begin(), layers.end(), [&](auto l) { return l->id == shapeLayerID; }) != layers.end()) { + continue; + } + + // apply shape paint properties + const StyleProperties& shapeStyle = annotations.at(shapeAnnotationID)->styleProperties; + ClassProperties paintProperties; + + if (shapeStyle.is()) { + const LineProperties& lineProperties = shapeStyle.get(); + paintProperties.set(PropertyKey::LineOpacity, ConstantFunction(lineProperties.opacity)); + paintProperties.set(PropertyKey::LineWidth, ConstantFunction(lineProperties.width)); + paintProperties.set(PropertyKey::LineColor, ConstantFunction(lineProperties.color)); + } else if (shapeStyle.is()) { + const FillProperties& fillProperties = shapeStyle.get(); + paintProperties.set(PropertyKey::FillOpacity, ConstantFunction(fillProperties.opacity)); + paintProperties.set(PropertyKey::FillColor, ConstantFunction(fillProperties.fill_color)); + paintProperties.set(PropertyKey::FillOutlineColor, ConstantFunction(fillProperties.stroke_color)); + } + + std::map shapePaints; + shapePaints.emplace(ClassID::Default, std::move(paintProperties)); + + // create shape layer + util::ptr shapeLayer = std::make_shared(shapeLayerID, std::move(shapePaints)); + shapeLayer->type = (shapeStyle.is() ? StyleLayerType::Line : StyleLayerType::Fill); + + // add to end of other shape layers just before (last) point layer + style->layers.emplace((style->layers.end() - 1), shapeLayer); + + // create shape bucket & connect to source + util::ptr shapeBucket = std::make_shared(shapeLayer->type); + shapeBucket->name = shapeLayer->id; + shapeBucket->source = shapeID; + shapeBucket->source_layer = shapeLayer->id; + + // apply line layout properties to bucket + if (shapeStyle.is()) { + shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction(JoinType::Round)); + } + + // connect layer to bucket + shapeLayer->bucket = shapeBucket; + } + + // invalidate annotations layer tiles + for (const auto &source : style->sources) { + if (source->info.type == SourceType::Annotations) { + source->invalidateTiles(ids); + } + } + + staleTiles.clear(); +} + const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points"; const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape"; diff --git a/src/mbgl/annotation/annotation_manager.hpp b/src/mbgl/annotation/annotation_manager.hpp index 60cafdf342d..cf8d8e3e771 100644 --- a/src/mbgl/annotation/annotation_manager.hpp +++ b/src/mbgl/annotation/annotation_manager.hpp @@ -23,6 +23,7 @@ class Annotation; class PointAnnotation; class ShapeAnnotation; class LiveTile; +class Style; using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT; @@ -33,10 +34,6 @@ class AnnotationManager : private util::noncopyable { AnnotationManager(); ~AnnotationManager(); - void markStaleTiles(std::unordered_set); - size_t getStaleTileCount() const { return staleTiles.size(); } - std::unordered_set resetStaleTiles(); - void setDefaultPointAnnotationSymbol(const std::string& symbol); std::pair @@ -48,8 +45,8 @@ class AnnotationManager : private util::noncopyable { AffectedTiles removeAnnotations(const AnnotationIDs&, const uint8_t maxZoom); - AnnotationIDs getOrderedShapeAnnotations() const { return orderedShapeAnnotations; } - const StyleProperties getAnnotationStyleProperties(uint32_t) const; + void updateTilesIfNeeded(Style*); + void updateTiles(const AffectedTiles&, Style*); AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const uint8_t maxZoom, const AnnotationType& = AnnotationType::Any) const; LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const; @@ -66,7 +63,8 @@ class AnnotationManager : private util::noncopyable { uint32_t addShapeAnnotation(const ShapeAnnotation&, const uint8_t maxZoom); uint32_t addPointAnnotation(const PointAnnotation&, const uint8_t maxZoom, AffectedTiles&); -private: + const StyleProperties getAnnotationStyleProperties(uint32_t) const; + std::string defaultPointAnnotationSymbol; std::unordered_map> annotations; std::vector orderedShapeAnnotations; diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 5858918ae9c..1c0c98b5994 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include #include @@ -143,113 +141,10 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base) asyncUpdate->send(); } -void MapContext::updateAnnotationTilesIfNeeded() { - if (data.getAnnotationManager()->getStaleTileCount()) { - auto staleTiles = data.getAnnotationManager()->resetStaleTiles(); - updateAnnotationTiles(staleTiles); - } -} - void MapContext::updateAnnotationTiles(const std::unordered_set& ids) { - assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - - util::exclusive annotationManager = data.getAnnotationManager(); - annotationManager->markStaleTiles(ids); - - if (!style) { - return; - } - - // grab existing, single shape annotations source - const auto& shapeID = AnnotationManager::ShapeLayerID; - Source* shapeAnnotationSource = style->getSource(shapeID); - - // Style not parsed yet - if (!shapeAnnotationSource) { - return; - } - - shapeAnnotationSource->enabled = true; - - // create (if necessary) layers and buckets for each shape - for (const auto &shapeAnnotationID : annotationManager->getOrderedShapeAnnotations()) { - const std::string shapeLayerID = shapeID + "." + util::toString(shapeAnnotationID); - - const auto layer_it = std::find_if(style->layers.begin(), style->layers.end(), - [&shapeLayerID](util::ptr layer) { - return (layer->id == shapeLayerID); - }); - - if (layer_it == style->layers.end()) { - // query shape styling - auto& shapeStyle = annotationManager->getAnnotationStyleProperties(shapeAnnotationID); - - // apply shape paint properties - ClassProperties paintProperties; - - if (shapeStyle.is()) { - // opacity - PropertyValue lineOpacity = ConstantFunction(shapeStyle.get().opacity); - paintProperties.set(PropertyKey::LineOpacity, lineOpacity); - - // line width - PropertyValue lineWidth = ConstantFunction(shapeStyle.get().width); - paintProperties.set(PropertyKey::LineWidth, lineWidth); - - // stroke color - PropertyValue strokeColor = ConstantFunction(shapeStyle.get().color); - paintProperties.set(PropertyKey::LineColor, strokeColor); - } else if (shapeStyle.is()) { - // opacity - PropertyValue fillOpacity = ConstantFunction(shapeStyle.get().opacity); - paintProperties.set(PropertyKey::FillOpacity, fillOpacity); - - // fill color - PropertyValue fillColor = ConstantFunction(shapeStyle.get().fill_color); - paintProperties.set(PropertyKey::FillColor, fillColor); - - // stroke color - PropertyValue strokeColor = ConstantFunction(shapeStyle.get().stroke_color); - paintProperties.set(PropertyKey::FillOutlineColor, strokeColor); - } - - std::map shapePaints; - shapePaints.emplace(ClassID::Default, std::move(paintProperties)); - - // create shape layer - util::ptr shapeLayer = std::make_shared(shapeLayerID, std::move(shapePaints)); - shapeLayer->type = (shapeStyle.is() ? StyleLayerType::Line : StyleLayerType::Fill); - - // add to end of other shape layers just before (last) point layer - style->layers.emplace((style->layers.end() - 1), shapeLayer); - - // create shape bucket & connect to source - util::ptr shapeBucket = std::make_shared(shapeLayer->type); - shapeBucket->name = shapeLayer->id; - shapeBucket->source = shapeID; - shapeBucket->source_layer = shapeLayer->id; - - // apply line layout properties to bucket - if (shapeStyle.is()) { - shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction(JoinType::Round)); - } - - // connect layer to bucket - shapeLayer->bucket = shapeBucket; - } - } - - // invalidate annotations layer tiles - for (const auto &source : style->sources) { - if (source->info.type == SourceType::Annotations) { - source->invalidateTiles(ids); - } - } - + data.getAnnotationManager()->updateTiles(ids, style.get()); updateFlags |= Update::Classes; asyncUpdate->send(); - - annotationManager->resetStaleTiles(); } void MapContext::update() { @@ -419,7 +314,9 @@ void MapContext::onResourceLoadingFailed(std::exception_ptr error) { } void MapContext::onSpriteStoreLoaded() { - updateAnnotationTilesIfNeeded(); + data.getAnnotationManager()->updateTilesIfNeeded(style.get()); + updateFlags |= Update::Classes; + asyncUpdate->send(); } } diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index 449a0f1d1ce..f3ac636a40a 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -54,7 +54,6 @@ class MapContext : public Style::Observer { bool isLoaded() const; double getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol); - void updateAnnotationTilesIfNeeded(); void updateAnnotationTiles(const std::unordered_set&); void setSourceTileCacheSize(size_t size); From af392b9434fd176704e564dfc22ee74f3a50cdf6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 21 Sep 2015 13:15:58 -0700 Subject: [PATCH 8/9] Rewrite annotation invalidation strategy First, move style mutation code out of StyleParser and into AnnotationManager, coalescing it with the mutation code for shape layers. Second, allow AnnotationManager to keep track of stale tiles entirely internally. There's no reason to pass sets of TileIDs around. Third, correct the logic for invalidating the shape source. Since AnnotationManager does not track shape invalidations on a tile-by-tile basis, don't try to invalidate the shape source tile-by-tile. Fixes #1675 Fixes #2322 Fixes #2095 --- include/mbgl/map/update.hpp | 1 + src/mbgl/annotation/annotation_manager.cpp | 150 +++++++++------------ src/mbgl/annotation/annotation_manager.hpp | 27 ++-- src/mbgl/map/map.cpp | 12 +- src/mbgl/map/map_context.cpp | 19 +-- src/mbgl/map/map_context.hpp | 3 +- src/mbgl/map/source.cpp | 18 +-- src/mbgl/map/source.hpp | 1 + src/mbgl/style/style.cpp | 40 ++++-- src/mbgl/style/style.hpp | 6 +- src/mbgl/style/style_parser.cpp | 42 ------ test/api/annotations.cpp | 96 +++++++++---- test/style/resource_loading.cpp | 4 - 13 files changed, 197 insertions(+), 222 deletions(-) diff --git a/include/mbgl/map/update.hpp b/include/mbgl/map/update.hpp index fe9250efcff..83c2637559d 100644 --- a/include/mbgl/map/update.hpp +++ b/include/mbgl/map/update.hpp @@ -13,6 +13,7 @@ enum class Update : uint32_t { Zoom = 1 << 4, RenderStill = 1 << 5, Repaint = 1 << 6, + Annotations = 1 << 7, }; inline Update operator| (const Update& lhs, const Update& rhs) { diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index 31b50b1c51c..786ca424336 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -137,8 +137,13 @@ AnnotationManager::addShapeAnnotation(const ShapeAnnotation& shape, const uint8_ } uint32_t -AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_t maxZoom, - std::unordered_set& affectedTiles) { +AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_t maxZoom) { + // We pre-generate tiles to contain each annotation up to the map's max zoom. + // We do this for fast rendering without projection conversions on the fly, as well as + // to simplify bounding box queries of annotations later. Tiles get invalidated when + // annotations are added or removed in order to refresh the map render without + // touching the base map underneath. + const uint32_t annotationID = nextID(); // at render time we style the point according to its {sprite} field @@ -178,7 +183,7 @@ AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_ // check for annotation layer & create if necessary util::ptr layer; - std::string layerID = PointLayerID; + std::string layerID = PointSourceID; if (tile_pos.second || tile_pos.first->second.second->getMutableLayer(layerID) == nullptr) { layer = std::make_shared(); @@ -198,7 +203,7 @@ AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_ annotation->tilePointFeatures.emplace(featureTileID, std::weak_ptr(feature)); // track affected tile - affectedTiles.insert(featureTileID); + stalePointTileIDs.insert(featureTileID); } annotations.emplace(annotationID, std::move(annotation)); @@ -206,37 +211,22 @@ AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_ return annotationID; } -std::pair +AnnotationIDs AnnotationManager::addPointAnnotations(const std::vector& points, const uint8_t maxZoom) { - // We pre-generate tiles to contain each annotation up to the map's max zoom. - // We do this for fast rendering without projection conversions on the fly, as well as - // to simplify bounding box queries of annotations later. Tiles get invalidated when - // annotations are added or removed in order to refresh the map render without - // touching the base map underneath. - AnnotationIDs annotationIDs; annotationIDs.reserve(points.size()); - AffectedTiles affectedTiles; - for (const auto& point : points) { - annotationIDs.push_back(addPointAnnotation(point, maxZoom, affectedTiles)); + annotationIDs.push_back(addPointAnnotation(point, maxZoom)); } - // Tile:IDs that need refreshed and the annotation identifiers held onto by the client. - return std::make_pair(affectedTiles, annotationIDs); + return annotationIDs; } -std::pair +AnnotationIDs AnnotationManager::addShapeAnnotations(const std::vector& shapes, const uint8_t maxZoom) { - // We pre-generate tiles to contain each annotation up to the map's max zoom. - // We do this for fast rendering without projection conversions on the fly, as well as - // to simplify bounding box queries of annotations later. Tiles get invalidated when - // annotations are added or removed in order to refresh the map render without - // touching the base map underneath. - AnnotationIDs annotationIDs; annotationIDs.reserve(shapes.size()); @@ -244,16 +234,10 @@ AnnotationManager::addShapeAnnotations(const std::vector& shape annotationIDs.push_back(addShapeAnnotation(shape, maxZoom)); } - // Tile:IDs that need refreshed and the annotation identifiers held onto by the client. - // Shapes are tiled "on-the-fly", so we don't get any "affected tiles" and just expire - // all annotation tiles for shape adds. - return std::make_pair(AffectedTiles(), annotationIDs); + return annotationIDs; } -std::unordered_set AnnotationManager::removeAnnotations(const AnnotationIDs& ids, - const uint8_t maxZoom) { - std::unordered_set affectedTiles; - +void AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const uint8_t maxZoom) { std::vector z2s; const uint8_t zoomCount = maxZoom + 1; z2s.reserve(zoomCount); @@ -287,17 +271,16 @@ std::unordered_set AnnotationManager::removeAnnotations(co const auto& features_it = annotation->tilePointFeatures.find(tid); if (features_it != annotation->tilePointFeatures.end()) { // points share a layer; remove feature - auto layer = tiles[tid].second->getMutableLayer(PointLayerID); + auto layer = tiles[tid].second->getMutableLayer(PointSourceID); layer->removeFeature(features_it->second); - affectedTiles.insert(tid); + stalePointTileIDs.insert(tid); } } } else { // remove shape layer from tiles if relevant for (auto tile_it = tiles.begin(); tile_it != tiles.end(); ++tile_it) { if (tile_it->second.first.count(annotationID)) { - tile_it->second.second->removeLayer(ShapeLayerID + "." + util::toString(annotationID)); - affectedTiles.insert(tile_it->first); + tile_it->second.second->removeLayer(ShapeSourceID + "." + util::toString(annotationID)); } } @@ -312,9 +295,6 @@ std::unordered_set AnnotationManager::removeAnnotations(co annotations.erase(annotationID); } } - - // TileIDs for tiles that need refreshed. - return affectedTiles; } AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds, @@ -414,7 +394,7 @@ const LiveTile* AnnotationManager::getTile(const TileID& id) { // create shape tile layers from GeoJSONVT queries for (auto& tiler_it : shapeTilers) { const auto annotationID = tiler_it.first; - const std::string layerID = ShapeLayerID + "." + util::toString(annotationID); + const std::string layerID = ShapeSourceID + "." + util::toString(annotationID); // check for existing render layer auto renderLayer = renderTile->getMutableLayer(layerID); @@ -478,42 +458,48 @@ const LiveTile* AnnotationManager::getTile(const TileID& id) { return renderTile; } -void AnnotationManager::updateTilesIfNeeded(Style* style) { - if (!staleTiles.empty()) { - updateTiles(staleTiles, style); +void AnnotationManager::updateStyle(Style& style) { + // Create shape source + if (!style.getSource(ShapeSourceID)) { + std::unique_ptr shapeSource = std::make_unique(); + shapeSource->info.type = SourceType::Annotations; + shapeSource->info.source_id = ShapeSourceID; + shapeSource->enabled = true; + style.addSource(std::move(shapeSource)); } -} - -void AnnotationManager::updateTiles(const AffectedTiles& ids, Style* style) { - std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin())); - - if (!style) { - return; - } - - // grab existing, single shape annotations source - const auto& shapeID = AnnotationManager::ShapeLayerID; - Source* shapeAnnotationSource = style->getSource(shapeID); - // Style not parsed yet - if (!shapeAnnotationSource) { - return; + // Create point source and singular layer and bucket + if (!style.getSource(PointSourceID)) { + std::unique_ptr pointSource = std::make_unique(); + pointSource->info.type = SourceType::Annotations; + pointSource->info.source_id = PointSourceID; + pointSource->enabled = true; + style.addSource(std::move(pointSource)); + + std::map pointPaints; + pointPaints.emplace(ClassID::Default, ClassProperties()); + std::unique_ptr pointLayer = std::make_unique(PointSourceID, std::move(pointPaints)); + pointLayer->type = StyleLayerType::Symbol; + + util::ptr pointBucket = std::make_shared(pointLayer->type); + pointBucket->name = pointLayer->id; + pointBucket->source = PointSourceID; + pointBucket->source_layer = pointLayer->id; + pointBucket->layout.set(PropertyKey::IconImage, ConstantFunction("{sprite}")); + pointBucket->layout.set(PropertyKey::IconAllowOverlap, ConstantFunction(true)); + + pointLayer->bucket = pointBucket; + style.addLayer(std::move(pointLayer)); } - shapeAnnotationSource->enabled = true; - - const auto& layers = style->layers; - - // create (if necessary) layers and buckets for each shape - for (const auto& shapeAnnotationID : orderedShapeAnnotations) { - const std::string shapeLayerID = shapeID + "." + util::toString(shapeAnnotationID); - - if (std::find_if(layers.begin(), layers.end(), [&](auto l) { return l->id == shapeLayerID; }) != layers.end()) { + // Create new shape layers and buckets + for (const auto& shapeID : orderedShapeAnnotations) { + const std::string shapeLayerID = ShapeSourceID + "." + util::toString(shapeID); + if (style.getLayer(shapeLayerID)) { continue; } - // apply shape paint properties - const StyleProperties& shapeStyle = annotations.at(shapeAnnotationID)->styleProperties; + const StyleProperties& shapeStyle = annotations.at(shapeID)->styleProperties; ClassProperties paintProperties; if (shapeStyle.is()) { @@ -530,40 +516,28 @@ void AnnotationManager::updateTiles(const AffectedTiles& ids, Style* style) { std::map shapePaints; shapePaints.emplace(ClassID::Default, std::move(paintProperties)); - - // create shape layer - util::ptr shapeLayer = std::make_shared(shapeLayerID, std::move(shapePaints)); + std::unique_ptr shapeLayer = std::make_unique(shapeLayerID, std::move(shapePaints)); shapeLayer->type = (shapeStyle.is() ? StyleLayerType::Line : StyleLayerType::Fill); - // add to end of other shape layers just before (last) point layer - style->layers.emplace((style->layers.end() - 1), shapeLayer); - - // create shape bucket & connect to source util::ptr shapeBucket = std::make_shared(shapeLayer->type); shapeBucket->name = shapeLayer->id; - shapeBucket->source = shapeID; + shapeBucket->source = ShapeSourceID; shapeBucket->source_layer = shapeLayer->id; - - // apply line layout properties to bucket if (shapeStyle.is()) { shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction(JoinType::Round)); } - // connect layer to bucket shapeLayer->bucket = shapeBucket; + style.addLayer(std::move(shapeLayer), PointSourceID); } - // invalidate annotations layer tiles - for (const auto &source : style->sources) { - if (source->info.type == SourceType::Annotations) { - source->invalidateTiles(ids); - } - } + style.getSource(PointSourceID)->invalidateTiles(stalePointTileIDs); + style.getSource(ShapeSourceID)->invalidateTiles(); - staleTiles.clear(); + stalePointTileIDs.clear(); } -const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points"; -const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape"; +const std::string AnnotationManager::PointSourceID = "com.mapbox.annotations.points"; +const std::string AnnotationManager::ShapeSourceID = "com.mapbox.annotations.shape"; } diff --git a/src/mbgl/annotation/annotation_manager.hpp b/src/mbgl/annotation/annotation_manager.hpp index cf8d8e3e771..248fb798d60 100644 --- a/src/mbgl/annotation/annotation_manager.hpp +++ b/src/mbgl/annotation/annotation_manager.hpp @@ -29,48 +29,37 @@ using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT; class AnnotationManager : private util::noncopyable { public: - typedef std::unordered_set AffectedTiles; - AnnotationManager(); ~AnnotationManager(); void setDefaultPointAnnotationSymbol(const std::string& symbol); - std::pair - addPointAnnotations(const std::vector&, const uint8_t maxZoom); - - std::pair - addShapeAnnotations(const std::vector&, const uint8_t maxZoom); - - AffectedTiles - removeAnnotations(const AnnotationIDs&, const uint8_t maxZoom); - - void updateTilesIfNeeded(Style*); - void updateTiles(const AffectedTiles&, Style*); + AnnotationIDs addPointAnnotations(const std::vector&, const uint8_t maxZoom); + AnnotationIDs addShapeAnnotations(const std::vector&, const uint8_t maxZoom); + void removeAnnotations(const AnnotationIDs&, const uint8_t maxZoom); AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const uint8_t maxZoom, const AnnotationType& = AnnotationType::Any) const; LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const; + void updateStyle(Style&); const LiveTile* getTile(const TileID& id); - static const std::string PointLayerID; - static const std::string ShapeLayerID; + static const std::string PointSourceID; + static const std::string ShapeSourceID; private: inline uint32_t nextID(); static vec2 projectPoint(const LatLng& point); uint32_t addShapeAnnotation(const ShapeAnnotation&, const uint8_t maxZoom); - uint32_t addPointAnnotation(const PointAnnotation&, const uint8_t maxZoom, AffectedTiles&); - - const StyleProperties getAnnotationStyleProperties(uint32_t) const; + uint32_t addPointAnnotation(const PointAnnotation&, const uint8_t maxZoom); std::string defaultPointAnnotationSymbol; std::unordered_map> annotations; std::vector orderedShapeAnnotations; std::unordered_map, std::unique_ptr>, TileID::Hash> tiles; std::unordered_map> shapeTilers; - std::unordered_set staleTiles; + std::unordered_set stalePointTileIDs; uint32_t nextID_ = 0; }; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 3e7a61a4e84..4ba8d6cd8f7 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -348,8 +348,8 @@ uint32_t Map::addPointAnnotation(const PointAnnotation& annotation) { AnnotationIDs Map::addPointAnnotations(const std::vector& annotations) { auto result = data->getAnnotationManager()->addPointAnnotations(annotations, getMaxZoom()); - context->invoke(&MapContext::updateAnnotationTiles, result.first); - return result.second; + update(Update::Annotations); + return result; } uint32_t Map::addShapeAnnotation(const ShapeAnnotation& annotation) { @@ -358,8 +358,8 @@ uint32_t Map::addShapeAnnotation(const ShapeAnnotation& annotation) { AnnotationIDs Map::addShapeAnnotations(const std::vector& annotations) { auto result = data->getAnnotationManager()->addShapeAnnotations(annotations, getMaxZoom()); - context->invoke(&MapContext::updateAnnotationTiles, result.first); - return result.second; + update(Update::Annotations); + return result; } void Map::removeAnnotation(uint32_t annotation) { @@ -367,8 +367,8 @@ void Map::removeAnnotation(uint32_t annotation) { } void Map::removeAnnotations(const std::vector& annotations) { - auto result = data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom()); - context->invoke(&MapContext::updateAnnotationTiles, result); + data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom()); + update(Update::Annotations); } std::vector Map::getAnnotationsInBounds(const LatLngBounds& bounds, const AnnotationType& type) { diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 1c0c98b5994..c2b5900b16d 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -137,13 +137,7 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base) // force style cascade, causing all pending transitions to complete. style->cascade(); - updateFlags |= Update::DefaultTransition | Update::Classes | Update::Zoom; - asyncUpdate->send(); -} - -void MapContext::updateAnnotationTiles(const std::unordered_set& ids) { - data.getAnnotationManager()->updateTiles(ids, style.get()); - updateFlags |= Update::Classes; + updateFlags |= Update::DefaultTransition | Update::Classes | Update::Zoom | Update::Annotations; asyncUpdate->send(); } @@ -160,6 +154,11 @@ void MapContext::update() { data.setAnimationTime(Clock::now()); + if (style->sprite && updateFlags & Update::Annotations) { + data.getAnnotationManager()->updateStyle(*style); + updateFlags |= Update::Classes; + } + if (updateFlags & Update::Classes) { style->cascade(); } @@ -313,10 +312,4 @@ void MapContext::onResourceLoadingFailed(std::exception_ptr error) { } } -void MapContext::onSpriteStoreLoaded() { - data.getAnnotationManager()->updateTilesIfNeeded(style.get()); - updateFlags |= Update::Classes; - asyncUpdate->send(); -} - } diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index f3ac636a40a..b78d3283a76 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -54,7 +54,7 @@ class MapContext : public Style::Observer { bool isLoaded() const; double getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol); - void updateAnnotationTiles(const std::unordered_set&); + void updateAnnotations(); void setSourceTileCacheSize(size_t size); void onLowMemory(); @@ -66,7 +66,6 @@ class MapContext : public Style::Observer { // Style::Observer implementation. void onTileDataChanged() override; void onResourceLoadingFailed(std::exception_ptr error) override; - void onSpriteStoreLoaded() override; private: // Update the state indicated by the accumulated Update flags, then render. diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 572973b79aa..989d2eb75d6 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -515,18 +515,20 @@ bool Source::update(MapData& data, void Source::invalidateTiles(const std::unordered_set& ids) { cache.clear(); - if (!ids.empty()) { - for (auto& id : ids) { - tiles.erase(id); - tile_data.erase(id); - } - } else { - tiles.clear(); - tile_data.clear(); + for (const auto& id : ids) { + tiles.erase(id); + tile_data.erase(id); } updateTilePtrs(); } +void Source::invalidateTiles() { + cache.clear(); + tiles.clear(); + tile_data.clear(); + updateTilePtrs(); +} + void Source::updateTilePtrs() { tilePtrs.clear(); for (const auto& pair : tiles) { diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 336caa5b93d..421f8196405 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -78,6 +78,7 @@ class Source : private util::noncopyable { bool shouldReparsePartialTiles); void invalidateTiles(const std::unordered_set&); + void invalidateTiles(); void updateMatrices(const mat4 &projMatrix, const TransformState &transform); void drawClippingMasks(Painter &painter); diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index d4c9bd4e560..7945a45b1d8 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -44,18 +44,18 @@ void Style::setJSON(const std::string& json, const std::string&) { StyleParser parser(data); parser.parse(doc); - sources = parser.getSources(); - layers = parser.getLayers(); + for (auto& source : parser.getSources()) { + addSource(std::move(source)); + } + + for (auto& layer : parser.getLayers()) { + addLayer(std::move(layer)); + } sprite = std::make_unique(parser.getSprite(), data.pixelRatio); sprite->setObserver(this); glyphStore->setURL(parser.getGlyphURL()); - - for (const auto& source : sources) { - source->setObserver(this); - source->load(); - } } Style::~Style() { @@ -70,6 +70,20 @@ Style::~Style() { } } +void Style::addSource(std::unique_ptr source) { + source->setObserver(this); + source->load(); + sources.emplace_back(std::move(source)); +} + +void Style::addLayer(util::ptr layer) { + layers.emplace_back(std::move(layer)); +} + +void Style::addLayer(util::ptr layer, const std::string& before) { + layers.emplace(std::find_if(layers.begin(), layers.end(), [&](const auto& l) { return l->id == before; }), std::move(layer)); +} + void Style::update(const TransformState& transform, TexturePool& texturePool) { bool allTilesUpdated = true; @@ -126,6 +140,14 @@ Source* Style::getSource(const std::string& id) const { return it != sources.end() ? it->get() : nullptr; } +StyleLayer* Style::getLayer(const std::string& id) const { + const auto it = std::find_if(layers.begin(), layers.end(), [&](const auto& layer) { + return layer->id == id; + }); + + return it != layers.end() ? it->get() : nullptr; +} + bool Style::hasTransitions() const { for (const auto& layer : layers) { if (layer->hasTransitions()) { @@ -190,10 +212,6 @@ void Style::onSpriteLoaded(const Sprites& sprites) { // Add all sprite images to the SpriteStore object spriteStore->setSprites(sprites); - if (observer) { - observer->onSpriteStoreLoaded(); - } - shouldReparsePartialTiles = true; emitTileDataChanged(); } diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index 3f9696ffbf3..9c5091863ee 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -39,7 +39,6 @@ class Style : public GlyphStore::Observer, virtual ~Observer() = default; virtual void onTileDataChanged() = 0; - virtual void onSpriteStoreLoaded() = 0; virtual void onResourceLoadingFailed(std::exception_ptr error) = 0; }; @@ -63,6 +62,11 @@ class Style : public GlyphStore::Observer, } Source* getSource(const std::string& id) const; + StyleLayer* getLayer(const std::string& id) const; + + void addSource(std::unique_ptr); + void addLayer(util::ptr); + void addLayer(util::ptr, const std::string& beforeLayerID); MapData& data; std::unique_ptr glyphStore; diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index 2e298e9a6c3..04c3a455537 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -40,47 +39,6 @@ void StyleParser::parse(JSVal document) { if (document.HasMember("layers")) { parseLayers(document["layers"]); - - // create shape annotations source - const std::string& shapeID = AnnotationManager::ShapeLayerID; - - std::unique_ptr shapeAnnotationsSource = std::make_unique(); - shapeAnnotationsSource->info.type = SourceType::Annotations; - shapeAnnotationsSource->info.source_id = shapeID; - sourcesMap.emplace(shapeID, shapeAnnotationsSource.get()); - sources.emplace_back(std::move(shapeAnnotationsSource)); - - // create point annotations layer - const std::string& pointID = AnnotationManager::PointLayerID; - - std::map pointPaints; - util::ptr pointAnnotationsLayer = std::make_shared(pointID, std::move(pointPaints)); - pointAnnotationsLayer->type = StyleLayerType::Symbol; - layersMap.emplace(pointID, std::pair> { JSVal(pointID), pointAnnotationsLayer }); - layers.emplace_back(pointAnnotationsLayer); - - // create point annotations symbol bucket - util::ptr pointBucket = std::make_shared(pointAnnotationsLayer->type); - pointBucket->name = pointAnnotationsLayer->id; - pointBucket->source = pointID; - pointBucket->source_layer = pointAnnotationsLayer->id; - - // build up point annotations style - rapidjson::Document d; - rapidjson::Value iconImage(rapidjson::kObjectType); - iconImage.AddMember("icon-image", "{sprite}", d.GetAllocator()); - parseLayout(iconImage, pointBucket); - rapidjson::Value iconOverlap(rapidjson::kObjectType); - iconOverlap.AddMember("icon-allow-overlap", true, d.GetAllocator()); - parseLayout(iconOverlap, pointBucket); - - // create point annotations source & connect to bucket & layer - std::unique_ptr pointAnnotationsSource = std::make_unique(); - pointAnnotationsSource->info.type = SourceType::Annotations; - pointAnnotationsSource->info.source_id = pointID; - pointAnnotationsLayer->bucket = pointBucket; - sourcesMap.emplace(pointID, pointAnnotationsSource.get()); - sources.emplace_back(std::move(pointAnnotationsSource)); } if (document.HasMember("sprite")) { diff --git a/test/api/annotations.cpp b/test/api/annotations.cpp index a9ebb4ab014..a00a449a6d8 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -14,9 +14,19 @@ #include #include -TEST(Annotations, PointAnnotation) { - using namespace mbgl; +using namespace mbgl; + +std::string renderPNG(Map& map) { + std::promise> promise; + map.renderStill([&](std::exception_ptr, std::unique_ptr image) { + promise.set_value(std::move(image)); + }); + auto result = promise.get_future().get(); + return util::compress_png(result->width, result->height, result->pixels.get()); +} + +TEST(Annotations, PointAnnotation) { auto display = std::make_shared(); HeadlessView view(display, 1); DefaultFileSource fileSource(nullptr); @@ -25,19 +35,10 @@ TEST(Annotations, PointAnnotation) { map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); map.addPointAnnotation(PointAnnotation({ 0, 0 }, "default_marker")); - std::promise> promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { - promise.set_value(std::move(image)); - }); - - auto result = promise.get_future().get(); - const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); - util::write_file("test/output/point_annotation.png", png); + util::write_file("test/output/point_annotation.png", renderPNG(map)); } TEST(Annotations, LineAnnotation) { - using namespace mbgl; - auto display = std::make_shared(); HeadlessView view(display, 1); DefaultFileSource fileSource(nullptr); @@ -56,25 +57,55 @@ TEST(Annotations, LineAnnotation) { map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); - std::promise> promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { - promise.set_value(std::move(image)); - }); - - auto result = promise.get_future().get(); - const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); - util::write_file("test/output/line_annotation.png", png); + util::write_file("test/output/line_annotation.png", renderPNG(map)); } TEST(Annotations, FillAnnotation) { - using namespace mbgl; + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + + AnnotationSegments segments = {{ {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }}; + + FillProperties fillProperties; + fillProperties.fill_color = {{ 255, 0, 0, 1 }}; + + StyleProperties styleProperties; + styleProperties.set(fillProperties); + + map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); + + util::write_file("test/output/fill_annotation.png", renderPNG(map)); +} +TEST(Annotations, AddMultiple) { auto display = std::make_shared(); HeadlessView view(display, 1); DefaultFileSource fileSource(nullptr); Map map(view, fileSource, MapMode::Still); map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + map.addPointAnnotation(PointAnnotation({ 0, -20 }, "default_marker")); + + renderPNG(map); + + map.addPointAnnotation(PointAnnotation({ 0, 20 }, "default_marker")); + + util::write_file("test/output/add_multiple.png", renderPNG(map)); +} + +TEST(Annotations, NonImmediateAdd) { + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + + renderPNG(map); AnnotationSegments segments = {{ {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }}; @@ -86,12 +117,21 @@ TEST(Annotations, FillAnnotation) { map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); - std::promise> promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr image) { - promise.set_value(std::move(image)); - }); + util::write_file("test/output/non_immediate_add.png", renderPNG(map)); +} - auto result = promise.get_future().get(); - const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); - util::write_file("test/output/fill_annotation.png", png); +TEST(Annotations, SwitchStyle) { + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + map.addPointAnnotation(PointAnnotation({ 0, 0 }, "default_marker")); + + renderPNG(map); + + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + + util::write_file("test/output/switch_style.png", renderPNG(map)); } diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index 5ed09065a14..0562434586d 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -63,10 +63,6 @@ class MockMapContext : public Style::Observer { callback_(error); } - void onSpriteStoreLoaded() override { - // no-op - } - private: MapData data_; Transform transform_; From 0156ac7c8ce7d22b8a68cc37fcb03b1271ef7a4e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 28 Sep 2015 14:14:45 -0700 Subject: [PATCH 9/9] Add test for removeAnnotation --- test/api/annotations.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/api/annotations.cpp b/test/api/annotations.cpp index a00a449a6d8..84cf259b031 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -120,6 +120,22 @@ TEST(Annotations, NonImmediateAdd) { util::write_file("test/output/non_immediate_add.png", renderPNG(map)); } +TEST(Annotations, Remove) { + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + uint32_t point = map.addPointAnnotation(PointAnnotation({ 0, 0 }, "default_marker")); + + renderPNG(map); + + map.removeAnnotation(point); + + util::write_file("test/output/remove.png", renderPNG(map)); +} + TEST(Annotations, SwitchStyle) { auto display = std::make_shared(); HeadlessView view(display, 1);