-
Notifications
You must be signed in to change notification settings - Fork 1.3k
refs #893 #992: point annotations API #1018
Changes from 10 commits
79fbd54
b59e13f
b883d4f
980be6c
cb47993
543b657
a878247
1f761ac
d909e81
d34caf6
98be4b1
78cbab9
0462b63
5551f0a
6eea1ac
efdeb80
7d593a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#ifndef MBGL_MAP_ANNOTATIONS | ||
#define MBGL_MAP_ANNOTATIONS | ||
|
||
#include <mbgl/map/map.hpp> | ||
#include <mbgl/map/tile.hpp> | ||
#include <mbgl/map/live_tile.hpp> | ||
#include <mbgl/util/geo.hpp> | ||
#include <mbgl/util/noncopyable.hpp> | ||
#include <mbgl/util/std.hpp> | ||
#include <mbgl/util/vec.hpp> | ||
|
||
#include <string> | ||
#include <vector> | ||
#include <map> | ||
#include <mutex> | ||
#include <memory> | ||
|
||
namespace mbgl { | ||
|
||
class Annotation; | ||
|
||
enum class AnnotationType : uint8_t { | ||
Point, | ||
Shape | ||
}; | ||
|
||
class AnnotationManager : private util::noncopyable { | ||
public: | ||
AnnotationManager(Map&); | ||
|
||
void setDefaultPointAnnotationSymbol(std::string& symbol) { defaultPointAnnotationSymbol = symbol; } | ||
std::pair<std::vector<Tile::ID>, std::vector<uint32_t>> addPointAnnotations(std::vector<LatLng>, std::vector<std::string>& symbols); | ||
std::vector<Tile::ID> removeAnnotations(std::vector<uint32_t>); | ||
std::vector<uint32_t> getAnnotationsInBoundingBox(BoundingBox) const; | ||
BoundingBox getBoundingBoxForAnnotations(std::vector<uint32_t>) const; | ||
|
||
const std::unique_ptr<LiveTile>& getTile(Tile::ID const& id); | ||
|
||
private: | ||
uint32_t nextID() { return nextID_++; } | ||
static vec2<double> projectPoint(LatLng& point); | ||
|
||
private: | ||
std::mutex mtx; | ||
Map& map; | ||
std::string defaultPointAnnotationSymbol; | ||
std::map<uint32_t, std::unique_ptr<Annotation>> annotations; | ||
std::map<Tile::ID, std::pair<std::vector<uint32_t>, std::unique_ptr<LiveTile>>> annotationTiles; | ||
std::unique_ptr<LiveTile> nullTile; | ||
uint32_t nextID_ = 0; | ||
}; | ||
|
||
class Annotation : private util::noncopyable { | ||
public: | ||
Annotation(AnnotationType, std::vector<AnnotationSegment>); | ||
|
||
LatLng getPoint() const { return geometry[0][0]; } | ||
BoundingBox getBoundingBox() const { return bbox; } | ||
|
||
public: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these need to be public? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const AnnotationType type = AnnotationType::Point; | ||
const std::vector<AnnotationSegment> geometry; | ||
std::map<Tile::ID, std::vector<std::weak_ptr<const LiveTileFeature>>> tileFeatures; | ||
|
||
private: | ||
BoundingBox bbox; | ||
}; | ||
|
||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,9 @@ class GlyphAtlas; | |
class SpriteAtlas; | ||
class LineAtlas; | ||
class Environment; | ||
class AnnotationManager; | ||
|
||
typedef std::vector<LatLng> AnnotationSegment; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't appear to be used by this header; should move to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reasoning here is because the shapes API will have it; you pass vectors of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
class Map : private util::noncopyable { | ||
friend class View; | ||
|
@@ -140,13 +143,23 @@ class Map : private util::noncopyable { | |
inline const vec2<double> pixelForLatLng(const LatLng latLng) const { return state.pixelForLatLng(latLng); } | ||
inline const LatLng latLngForPixel(const vec2<double> pixel) const { return state.latLngForPixel(pixel); } | ||
|
||
// Annotations | ||
void setDefaultPointAnnotationSymbol(std::string&); | ||
uint32_t addPointAnnotation(LatLng, std::string& symbol); | ||
std::vector<uint32_t> addPointAnnotations(std::vector<LatLng>, std::vector<std::string>& symbols); | ||
void removeAnnotation(uint32_t); | ||
void removeAnnotations(std::vector<uint32_t>); | ||
std::vector<uint32_t> getAnnotationsInBoundingBox(BoundingBox) const; | ||
BoundingBox getBoundingBoxForAnnotations(std::vector<uint32_t>) const; | ||
|
||
// Debug | ||
void setDebug(bool value); | ||
void toggleDebug(); | ||
bool getDebug() const; | ||
|
||
inline const TransformState &getState() const { return state; } | ||
inline std::chrono::steady_clock::time_point getTime() const { return animationTime; } | ||
inline util::ptr<AnnotationManager> getAnnotationManager() const { return annotationManager; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't look like shared ownership should be necessary here; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That requires the member variable to move from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have to change the member variable:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohhhh, perfect. Right. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
private: | ||
// This may only be called by the View object. | ||
|
@@ -170,6 +183,8 @@ class Map : private util::noncopyable { | |
// the stylesheet. | ||
void prepare(); | ||
|
||
void updateAnnotationTiles(std::vector<Tile::ID>&); | ||
|
||
enum class Mode : uint8_t { | ||
None, // we're not doing any processing | ||
Continuous, // continually updating map | ||
|
@@ -219,8 +234,8 @@ class Map : private util::noncopyable { | |
util::ptr<Sprite> sprite; | ||
const std::unique_ptr<LineAtlas> lineAtlas; | ||
util::ptr<TexturePool> texturePool; | ||
|
||
const std::unique_ptr<Painter> painter; | ||
util::ptr<AnnotationManager> annotationManager; | ||
|
||
std::string styleURL; | ||
std::string styleJSON = ""; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,14 @@ struct ProjectedMeters { | |
: northing(n), easting(e) {} | ||
}; | ||
|
||
struct BoundingBox { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
LatLng sw = {90, 180}; | ||
LatLng ne = {-90, -180}; | ||
|
||
inline BoundingBox(LatLng sw_ = {90, 180}, LatLng ne_ = {-90, -180}) | ||
: sw(sw_), ne(ne_) {} | ||
}; | ||
|
||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
#include <mbgl/map/annotation.hpp> | ||
#include <mbgl/util/ptr.hpp> | ||
|
||
#include <algorithm> | ||
#include <memory> | ||
|
||
using namespace mbgl; | ||
|
||
Annotation::Annotation(AnnotationType type_, std::vector<AnnotationSegment> geometry_) | ||
: type(type_), | ||
geometry(geometry_) { | ||
if (type == AnnotationType::Point) { | ||
bbox = BoundingBox(getPoint(), getPoint()); | ||
} else { | ||
LatLng sw, ne; | ||
for (auto segment : geometry) { | ||
for (auto point : segment) { | ||
if (point.latitude < sw.latitude) sw.latitude = point.latitude; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extract this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (point.latitude > ne.latitude) ne.latitude = point.latitude; | ||
if (point.longitude < sw.longitude) sw.longitude = point.longitude; | ||
if (point.longitude > ne.longitude) ne.longitude = point.longitude; | ||
} | ||
} | ||
bbox = BoundingBox(sw, ne); | ||
} | ||
} | ||
|
||
AnnotationManager::AnnotationManager(Map& map_) | ||
: map(map_), | ||
nullTile(util::make_unique<LiveTile>()) {} | ||
|
||
vec2<double> AnnotationManager::projectPoint(LatLng& point) { | ||
double sine = std::sin(point.latitude * M_PI / 180); | ||
double x = point.longitude / 360 + 0.5; | ||
double y = 0.5 - 0.25 * std::log((1 + sine) / (1 - sine)) / M_PI; | ||
return vec2<double>(x, y); | ||
} | ||
|
||
std::pair<std::vector<Tile::ID>, std::vector<uint32_t>> AnnotationManager::addPointAnnotations(std::vector<LatLng> points, std::vector<std::string>& symbols) { | ||
|
||
uint16_t extent = 4096; | ||
|
||
std::vector<uint32_t> annotationIDs(points.size()); | ||
std::vector<Tile::ID> affectedTiles; | ||
|
||
for (uint32_t i = 0; i < points.size(); ++i) { | ||
uint32_t annotationID = nextID(); | ||
|
||
auto anno_it = annotations.emplace(annotationID, util::make_unique<Annotation>(AnnotationType::Point, std::vector<AnnotationSegment>({{ points[i] }}))); | ||
|
||
uint8_t maxZoom = map.getMaxZoom(); | ||
|
||
uint32_t z2 = 1 << maxZoom; | ||
|
||
vec2<double> p = projectPoint(points[i]); | ||
|
||
uint32_t x = p.x * z2; | ||
uint32_t y = p.y * z2; | ||
|
||
for (int8_t z = maxZoom; z >= 0; z--) { | ||
affectedTiles.emplace_back(z, x, y); | ||
Tile::ID tileID = affectedTiles.back(); | ||
|
||
Coordinate coordinate(extent * (p.x * z2 - x), extent * (p.y * z2 - y)); | ||
|
||
GeometryCollection geometries({{ {{ coordinate }} }}); | ||
|
||
std::map<std::string, std::string> properties = {{ "sprite", (symbols[i].length() ? symbols[i] : defaultPointAnnotationSymbol) }}; | ||
|
||
auto feature = std::make_shared<const LiveTileFeature>(FeatureType::Point, | ||
geometries, | ||
properties); | ||
|
||
auto tile_it = annotationTiles.find(tileID); | ||
if (tile_it != annotationTiles.end()) { | ||
// get point layer & add feature | ||
auto layer = tile_it->second.second->getMutableLayer(util::ANNOTATIONS_POINTS_LAYER_ID); | ||
layer->addFeature(feature); | ||
// record annotation association with tile | ||
tile_it->second.first.push_back(annotationID); | ||
} else { | ||
// create point layer & add feature | ||
util::ptr<LiveTileLayer> layer = std::make_shared<LiveTileLayer>(); | ||
layer->addFeature(feature); | ||
// create tile & record annotation association | ||
auto tile_pos = annotationTiles.emplace(tileID, std::make_pair(std::vector<uint32_t>({ annotationID }), util::make_unique<LiveTile>())); | ||
// add point layer to tile | ||
tile_pos.first->second.second->addLayer(util::ANNOTATIONS_POINTS_LAYER_ID, layer); | ||
} | ||
|
||
// record annotation association with tile feature | ||
anno_it.first->second->tileFeatures.emplace(tileID, std::vector<std::weak_ptr<const LiveTileFeature>>({ feature })); | ||
|
||
z2 /= 2; | ||
x /= 2; | ||
y /= 2; | ||
} | ||
|
||
annotationIDs.push_back(annotationID); | ||
} | ||
|
||
return std::make_pair(affectedTiles, annotationIDs); | ||
} | ||
|
||
std::vector<Tile::ID> AnnotationManager::removeAnnotations(std::vector<uint32_t> ids) { | ||
std::vector<Tile::ID> affectedTiles; | ||
|
||
for (auto& annotationID : ids) { | ||
auto annotation_it = annotations.find(annotationID); | ||
if (annotation_it != annotations.end()) { | ||
auto& annotation = annotation_it->second; | ||
for (auto& tile_it : annotationTiles) { | ||
auto features_it = annotation->tileFeatures.find(tile_it.first); | ||
if (features_it != annotation->tileFeatures.end()) { | ||
auto layer = tile_it.second.second->getMutableLayer(util::ANNOTATIONS_POINTS_LAYER_ID); | ||
auto& features = features_it->second; | ||
layer->removeFeature(features[0]); | ||
affectedTiles.push_back(tile_it.first); | ||
} | ||
} | ||
annotations.erase(annotationID); | ||
} | ||
} | ||
|
||
return affectedTiles; | ||
} | ||
|
||
std::vector<uint32_t> AnnotationManager::getAnnotationsInBoundingBox(BoundingBox queryBbox) const { | ||
uint8_t z = map.getMaxZoom(); | ||
uint32_t z2 = 1 << z; | ||
vec2<double> swPoint = projectPoint(queryBbox.sw); | ||
vec2<double> nePoint = projectPoint(queryBbox.ne); | ||
|
||
// tiles number y from top down | ||
Tile::ID nwTile(z, swPoint.x * z2, nePoint.y * z2); | ||
Tile::ID seTile(z, nePoint.x * z2, swPoint.y * z2); | ||
|
||
std::vector<uint32_t> matchingAnnotations; | ||
|
||
for (auto& tile : annotationTiles) { | ||
Tile::ID id = tile.first; | ||
if (id.z == z) { | ||
if (id.x >= nwTile.x && id.x <= seTile.x && id.y >= nwTile.y && id.y <= seTile.y) { | ||
if (id.x > nwTile.x && id.x < seTile.x && id.y > nwTile.y && id.y < seTile.y) { | ||
// trivial accept; grab all of the tile's annotations | ||
std::copy(tile.second.first.begin(), tile.second.first.end(), std::back_inserter(matchingAnnotations)); | ||
} else { | ||
// check tile's annotations' bounding boxes | ||
std::copy_if(tile.second.first.begin(), tile.second.first.end(), | ||
std::back_inserter(matchingAnnotations), [&](const uint32_t annotationID) -> bool { | ||
printf("checking annotation %i\n", annotationID); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this debug output. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
BoundingBox annoBbox = this->annotations.find(annotationID)->second->getBoundingBox(); | ||
return (annoBbox.sw.latitude >= queryBbox.sw.latitude && | ||
annoBbox.ne.latitude <= queryBbox.ne.latitude && | ||
annoBbox.sw.longitude >= queryBbox.sw.longitude && | ||
annoBbox.ne.longitude <= queryBbox.ne.longitude); | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return matchingAnnotations; | ||
} | ||
|
||
BoundingBox AnnotationManager::getBoundingBoxForAnnotations(std::vector<uint32_t> ids) const { | ||
LatLng sw, ne; | ||
for (auto id : ids) { | ||
auto annotation_it = annotations.find(id); | ||
if (annotation_it != annotations.end()) { | ||
auto bbox = annotation_it->second->getBoundingBox(); | ||
if (bbox.sw.latitude < sw.latitude) sw.latitude = bbox.sw.latitude; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reuse There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (bbox.ne.latitude > ne.latitude) ne.latitude = bbox.ne.latitude; | ||
if (bbox.sw.longitude < sw.longitude) sw.longitude = bbox.sw.longitude; | ||
if (bbox.ne.longitude > ne.longitude) ne.longitude = bbox.ne.longitude; | ||
} | ||
} | ||
|
||
return BoundingBox(sw, ne); | ||
} | ||
|
||
const std::unique_ptr<LiveTile>& AnnotationManager::getTile(Tile::ID const& id) { | ||
std::lock_guard<std::mutex> lock(mtx); | ||
|
||
auto tile_it = annotationTiles.find(id); | ||
if (tile_it != annotationTiles.end()) { | ||
return tile_it->second.second; | ||
} | ||
return nullTile; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be a forward declaration instead of an include?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leads to not seeing
AnnotationSegment
, and that can't be forward declared because it is used ingetPoint()
. That method can't be moved inside ofannotation.cpp
because then you get a redefinition error between the forward declaration ofAnnotationSegment
and the one brought intoannotation.cpp
with#include <mbgl/map/map.hpp>
. This went really deep and doing it like this seemed the only way to keep things happy.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The underlying problem is the cyclic dependency between
AnnotationManager
andMap
. It looks like you can break the cycle by removingMap& map
fromAnnotationManager
and passing the max zoom toAnnotationManager::getAnnotationsInBounds
andAnnotationManager::addPointAnnotations
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is weird from an API point of view — clients like Cocoa would have to query, then pass the max zoom, when it doesn't appear to need to be an argument of querying bounds for annotations (they are independent of zoom and an implementation detail).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought clients would use the API provided by
Map
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhh, got it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7d593a2