Skip to content

Commit

Permalink
Rewrite annotation invalidation strategy
Browse files Browse the repository at this point in the history
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 mapbox#1675
Fixes mapbox#2322
Fixes mapbox#2095
  • Loading branch information
jfirebaugh committed Sep 28, 2015
1 parent af4f575 commit eb9fcd8
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 222 deletions.
1 change: 1 addition & 0 deletions include/mbgl/map/update.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
150 changes: 62 additions & 88 deletions src/mbgl/annotation/annotation_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TileID, TileID::Hash>& 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
Expand Down Expand Up @@ -178,7 +183,7 @@ AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_

// check for annotation layer & create if necessary
util::ptr<LiveTileLayer> 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<LiveTileLayer>();
Expand All @@ -198,62 +203,41 @@ AnnotationManager::addPointAnnotation(const PointAnnotation& point, const uint8_
annotation->tilePointFeatures.emplace(featureTileID, std::weak_ptr<const LiveTileFeature>(feature));

// track affected tile
affectedTiles.insert(featureTileID);
stalePointTileIDs.insert(featureTileID);
}

annotations.emplace(annotationID, std::move(annotation));

return annotationID;
}

std::pair<AnnotationManager::AffectedTiles, AnnotationIDs>
AnnotationIDs
AnnotationManager::addPointAnnotations(const std::vector<PointAnnotation>& 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<AnnotationManager::AffectedTiles, AnnotationIDs>
AnnotationIDs
AnnotationManager::addShapeAnnotations(const std::vector<ShapeAnnotation>& 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());

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.
// 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<TileID, TileID::Hash> AnnotationManager::removeAnnotations(const AnnotationIDs& ids,
const uint8_t maxZoom) {
std::unordered_set<TileID, TileID::Hash> affectedTiles;

void AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const uint8_t maxZoom) {
std::vector<uint32_t> z2s;
const uint8_t zoomCount = maxZoom + 1;
z2s.reserve(zoomCount);
Expand Down Expand Up @@ -287,17 +271,16 @@ std::unordered_set<TileID, TileID::Hash> 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));
}
}

Expand All @@ -312,9 +295,6 @@ std::unordered_set<TileID, TileID::Hash> AnnotationManager::removeAnnotations(co
annotations.erase(annotationID);
}
}

// TileIDs for tiles that need refreshed.
return affectedTiles;
}

AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Source> shapeSource = std::make_unique<Source>();
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<Source> pointSource = std::make_unique<Source>();
pointSource->info.type = SourceType::Annotations;
pointSource->info.source_id = PointSourceID;
pointSource->enabled = true;
style.addSource(std::move(pointSource));

std::map<ClassID, ClassProperties> pointPaints;
pointPaints.emplace(ClassID::Default, ClassProperties());
std::unique_ptr<StyleLayer> pointLayer = std::make_unique<StyleLayer>(PointSourceID, std::move(pointPaints));
pointLayer->type = StyleLayerType::Symbol;

util::ptr<StyleBucket> pointBucket = std::make_shared<StyleBucket>(pointLayer->type);
pointBucket->name = pointLayer->id;
pointBucket->source = PointSourceID;
pointBucket->source_layer = pointLayer->id;
pointBucket->layout.set(PropertyKey::IconImage, ConstantFunction<std::string>("{sprite}"));
pointBucket->layout.set(PropertyKey::IconAllowOverlap, ConstantFunction<bool>(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<LineProperties>()) {
Expand All @@ -530,40 +516,28 @@ void AnnotationManager::updateTiles(const AffectedTiles& ids, Style* style) {

std::map<ClassID, ClassProperties> shapePaints;
shapePaints.emplace(ClassID::Default, std::move(paintProperties));

// create shape layer
util::ptr<StyleLayer> shapeLayer = std::make_shared<StyleLayer>(shapeLayerID, std::move(shapePaints));
std::unique_ptr<StyleLayer> shapeLayer = std::make_unique<StyleLayer>(shapeLayerID, std::move(shapePaints));
shapeLayer->type = (shapeStyle.is<LineProperties>() ? 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<StyleBucket> shapeBucket = std::make_shared<StyleBucket>(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<LineProperties>()) {
shapeBucket->layout.set(PropertyKey::LineJoin, ConstantFunction<JoinType>(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";

}
27 changes: 8 additions & 19 deletions src/mbgl/annotation/annotation_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,37 @@ using GeoJSONVT = mapbox::util::geojsonvt::GeoJSONVT;

class AnnotationManager : private util::noncopyable {
public:
typedef std::unordered_set<TileID, TileID::Hash> AffectedTiles;

AnnotationManager();
~AnnotationManager();

void setDefaultPointAnnotationSymbol(const std::string& symbol);

std::pair<AffectedTiles, AnnotationIDs>
addPointAnnotations(const std::vector<PointAnnotation>&, const uint8_t maxZoom);

std::pair<AffectedTiles, AnnotationIDs>
addShapeAnnotations(const std::vector<ShapeAnnotation>&, 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<PointAnnotation>&, const uint8_t maxZoom);
AnnotationIDs addShapeAnnotations(const std::vector<ShapeAnnotation>&, 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<double> 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<uint32_t, std::unique_ptr<Annotation>> annotations;
std::vector<uint32_t> orderedShapeAnnotations;
std::unordered_map<TileID, std::pair<std::unordered_set<uint32_t>, std::unique_ptr<LiveTile>>, TileID::Hash> tiles;
std::unordered_map<uint32_t, std::unique_ptr<GeoJSONVT>> shapeTilers;
std::unordered_set<TileID, TileID::Hash> staleTiles;
std::unordered_set<TileID, TileID::Hash> stalePointTileIDs;
uint32_t nextID_ = 0;
};

Expand Down
12 changes: 6 additions & 6 deletions src/mbgl/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ uint32_t Map::addPointAnnotation(const PointAnnotation& annotation) {

AnnotationIDs Map::addPointAnnotations(const std::vector<PointAnnotation>& 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) {
Expand All @@ -358,17 +358,17 @@ uint32_t Map::addShapeAnnotation(const ShapeAnnotation& annotation) {

AnnotationIDs Map::addShapeAnnotations(const std::vector<ShapeAnnotation>& 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) {
removeAnnotations({ annotation });
}

void Map::removeAnnotations(const std::vector<uint32_t>& annotations) {
auto result = data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom());
context->invoke(&MapContext::updateAnnotationTiles, result);
data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom());
update(Update::Annotations);
}

std::vector<uint32_t> Map::getAnnotationsInBounds(const LatLngBounds& bounds, const AnnotationType& type) {
Expand Down
19 changes: 6 additions & 13 deletions src/mbgl/map/map_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TileID, TileID::Hash>& ids) {
data.getAnnotationManager()->updateTiles(ids, style.get());
updateFlags |= Update::Classes;
updateFlags |= Update::DefaultTransition | Update::Classes | Update::Zoom | Update::Annotations;
asyncUpdate->send();
}

Expand All @@ -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();
}
Expand Down Expand Up @@ -313,10 +312,4 @@ void MapContext::onResourceLoadingFailed(std::exception_ptr error) {
}
}

void MapContext::onSpriteStoreLoaded() {
data.getAnnotationManager()->updateTilesIfNeeded(style.get());
updateFlags |= Update::Classes;
asyncUpdate->send();
}

}
Loading

0 comments on commit eb9fcd8

Please sign in to comment.