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/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/map/annotation.cpp b/src/mbgl/annotation/annotation_manager.cpp similarity index 68% rename from src/mbgl/map/annotation.cpp rename to src/mbgl/annotation/annotation_manager.cpp index 9cfb857bcbc..786ca424336 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -7,13 +7,29 @@ #include #include #include - +#include +#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_) @@ -50,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; } @@ -77,34 +85,14 @@ 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) { +uint32_t +AnnotationManager::addShapeAnnotation(const ShapeAnnotation& shape, const uint8_t maxZoom) { + const uint32_t annotationID = nextID(); - 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, - const StyleProperties& styleProperties, - const uint8_t maxZoom) { - // Currently unused. - std::unordered_set affectedTiles; + annotations.emplace(annotationID, std::make_unique( + AnnotationType::Shape, + shape.segments, + shape.styleProperties)); orderedShapeAnnotations.push_back(annotationID); @@ -120,14 +108,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) { @@ -145,21 +133,36 @@ 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) { + // 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(); - 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; @@ -180,7 +183,7 @@ AnnotationManager::addPointFeature(const uint32_t annotationID, // 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(); @@ -197,103 +200,44 @@ 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); + stalePointTileIDs.insert(featureTileID); } - return affectedTiles; + annotations.emplace(annotationID, std::move(annotation)); + + return annotationID; } -std::pair, AnnotationIDs> +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()); - 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(); - - // 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); - } - - // add individual point tile feature - auto featureAffectedTiles = addTileFeature( - pointAnnotationID, - AnnotationSegments({{ point.position }}), - std::vector>>({{ pp }}), - AnnotationType::Point, - {{ }}, - 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)); } - // 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> +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()); - 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 - - addTileFeature( - shapeAnnotationID, - shape.segments, - {{ }}, - AnnotationType::Shape, - 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); + 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); @@ -327,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)); } } @@ -352,16 +295,6 @@ std::unordered_set AnnotationManager::removeAnnotations(co annotations.erase(annotationID); } } - - // TileIDs for tiles that need refreshed. - 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, @@ -461,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); @@ -525,7 +458,86 @@ const LiveTile* AnnotationManager::getTile(const TileID& id) { return renderTile; } -const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points"; -const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape"; +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)); + } + + // 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)); + } + + // Create new shape layers and buckets + for (const auto& shapeID : orderedShapeAnnotations) { + const std::string shapeLayerID = ShapeSourceID + "." + util::toString(shapeID); + if (style.getLayer(shapeLayerID)) { + continue; + } + + const StyleProperties& shapeStyle = annotations.at(shapeID)->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)); + std::unique_ptr shapeLayer = std::make_unique(shapeLayerID, std::move(shapePaints)); + shapeLayer->type = (shapeStyle.is() ? StyleLayerType::Line : StyleLayerType::Fill); + + util::ptr shapeBucket = std::make_shared(shapeLayer->type); + shapeBucket->name = shapeLayer->id; + shapeBucket->source = ShapeSourceID; + shapeBucket->source_layer = shapeLayer->id; + if (shapeStyle.is()) { + shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction(JoinType::Round)); + } + + shapeLayer->bucket = shapeBucket; + style.addLayer(std::move(shapeLayer), PointSourceID); + } + + style.getSource(PointSourceID)->invalidateTiles(stalePointTileIDs); + style.getSource(ShapeSourceID)->invalidateTiles(); + + stalePointTileIDs.clear(); +} + +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 new file mode 100644 index 00000000000..248fb798d60 --- /dev/null +++ b/src/mbgl/annotation/annotation_manager.hpp @@ -0,0 +1,68 @@ +#ifndef MBGL_MAP_ANNOTATIONS +#define MBGL_MAP_ANNOTATIONS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mbgl { + +class Annotation; +class PointAnnotation; +class ShapeAnnotation; +class LiveTile; +class Style; + +using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT; + +class AnnotationManager : private util::noncopyable { +public: + AnnotationManager(); + ~AnnotationManager(); + + void setDefaultPointAnnotationSymbol(const std::string& symbol); + + 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 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); + + 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 stalePointTileIDs; + uint32_t nextID_ = 0; +}; + +} + +#endif diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp deleted file mode 100644 index 7691e4508e2..00000000000 --- a/src/mbgl/map/annotation.hpp +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef MBGL_MAP_ANNOTATIONS -#define MBGL_MAP_ANNOTATIONS - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace mbgl { - -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: - 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, AnnotationIDs> - addPointAnnotations(const std::vector&, - const uint8_t maxZoom); - - std::pair, AnnotationIDs> - addShapeAnnotations(const std::vector&, - 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; - - AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const uint8_t maxZoom, const AnnotationType& = AnnotationType::Any) const; - LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const; - - const LiveTile* getTile(const TileID& id); - - static const std::string PointLayerID; - static const std::string ShapeLayerID; - -private: - 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, - 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); - -private: - 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; - uint32_t nextID_ = 0; -}; - -} - -#endif diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 0b73b52f36b..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 @@ -13,7 +12,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 +24,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 +33,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 +55,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/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 0ebcd73f8b4..c2b5900b16d 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 @@ -16,8 +15,6 @@ #include #include -#include -#include #include #include @@ -140,119 +137,10 @@ 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; + updateFlags |= Update::DefaultTransition | Update::Classes | Update::Zoom | Update::Annotations; 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); - } - } - - updateFlags |= Update::Classes; - asyncUpdate->send(); - - annotationManager->resetStaleTiles(); -} - void MapContext::update() { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); @@ -266,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(); } @@ -419,8 +312,4 @@ void MapContext::onResourceLoadingFailed(std::exception_ptr error) { } } -void MapContext::onSpriteStoreLoaded() { - updateAnnotationTilesIfNeeded(); -} - } diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index 449a0f1d1ce..b78d3283a76 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -54,8 +54,7 @@ class MapContext : public Style::Observer { bool isLoaded() const; double getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol); - void updateAnnotationTilesIfNeeded(); - void updateAnnotationTiles(const std::unordered_set&); + void updateAnnotations(); void setSourceTileCacheSize(size_t size); void onLowMemory(); @@ -67,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/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/map/source.cpp b/src/mbgl/map/source.cpp index 5899fea1ecc..989d2eb75d6 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); }); } @@ -517,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/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/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 6dc92a8d3dc..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 33374d43864..84cf259b031 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -1,36 +1,153 @@ -#include "../fixtures/fixture_log_observer.hpp" -#include "../fixtures/mock_file_source.hpp" #include "../fixtures/util.hpp" #include +#include +#include #include #include #include #include +#include +#include +#include #include #include -TEST(API, 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); + + Map map(view, fileSource, MapMode::Still); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); + map.addPointAnnotation(PointAnnotation({ 0, 0 }, "default_marker")); + + util::write_file("test/output/point_annotation.png", renderPNG(map)); +} - MockFileSource fileSource(MockFileSource::Success, ""); +TEST(Annotations, LineAnnotation) { + auto display = std::make_shared(); + HeadlessView view(display, 1); + DefaultFileSource fileSource(nullptr); Map map(view, fileSource, MapMode::Still); - map.setStyleURL("test/fixtures/resources/style.json"); + map.setStyleJSON(util::read_file("test/fixtures/api/empty.json"), ""); - std::vector points; - points.emplace_back(PointAnnotation({ 50.0, 50.0 }, "default_marker")); + AnnotationSegments segments = {{ {{ { 0, 0 }, { 45, 45 } }} }}; - map.addPointAnnotations(points); + LineProperties lineProperties; + lineProperties.color = {{ 255, 0, 0, 1 }}; + lineProperties.width = 5; - std::promise promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr) { - promise.set_value(true); - }); + StyleProperties styleProperties; + styleProperties.set(lineProperties); + + map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); + + util::write_file("test/output/line_annotation.png", renderPNG(map)); +} + +TEST(Annotations, FillAnnotation) { + 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 } }} }}; + + FillProperties fillProperties; + fillProperties.fill_color = {{ 255, 0, 0, 1 }}; + + StyleProperties styleProperties; + styleProperties.set(fillProperties); + + map.addShapeAnnotation(ShapeAnnotation(segments, styleProperties)); + + 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); + 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"), ""); - promise.get_future().get(); + util::write_file("test/output/switch_style.png", renderPNG(map)); } 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 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_;