[core] Style-sourced shape annotation properties
This introduces the possibility to source the type and style properties of a shape annotation from a designated layer in the style.
This commit is contained in:
@@ -15,14 +15,17 @@ using AnnotationSegments = std::vector<AnnotationSegment>;
|
||||
|
||||
class ShapeAnnotation {
|
||||
public:
|
||||
using Properties = mapbox::util::variant<FillPaintProperties, LinePaintProperties>;
|
||||
using Properties = mapbox::util::variant<
|
||||
FillPaintProperties, // creates a fill annotation
|
||||
LinePaintProperties, // creates a line annotation
|
||||
std::string>; // creates an annotation whose type and properties are sourced from a style layer
|
||||
|
||||
inline ShapeAnnotation(const AnnotationSegments& segments_, const Properties& styleProperties_)
|
||||
: segments(segments_), styleProperties(styleProperties_) {
|
||||
ShapeAnnotation(const AnnotationSegments& segments_, const Properties& properties_)
|
||||
: segments(segments_), properties(properties_) {
|
||||
}
|
||||
|
||||
const AnnotationSegments segments;
|
||||
const Properties styleProperties;
|
||||
const Properties properties;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,92 +15,115 @@ using namespace mapbox::util::geojsonvt;
|
||||
|
||||
ShapeAnnotationImpl::ShapeAnnotationImpl(const AnnotationID id_,
|
||||
const ShapeAnnotation& shape_,
|
||||
const uint8_t maxZoom)
|
||||
const uint8_t maxZoom_)
|
||||
: id(id_),
|
||||
layerID("com.mapbox.annotations.shape." + util::toString(id)),
|
||||
shape(shape_),
|
||||
shapeTiler(([&] {
|
||||
const double baseTolerance = 3;
|
||||
const uint16_t extent = 4096;
|
||||
const double tolerance = baseTolerance / ((1 << maxZoom) * extent);
|
||||
|
||||
ProjectedGeometryContainer rings;
|
||||
std::vector<LonLat> points;
|
||||
|
||||
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 (shape.styleProperties.is<FillPaintProperties>()) {
|
||||
featureType = ProjectedFeatureType::Polygon;
|
||||
|
||||
if (points.front().lon != points.back().lon || points.front().lat != points.back().lat) {
|
||||
points.push_back(LonLat(points.front().lon, points.front().lat));
|
||||
}
|
||||
} else {
|
||||
featureType = ProjectedFeatureType::LineString;
|
||||
}
|
||||
|
||||
ProjectedGeometryContainer ring = Convert::project(points, tolerance);
|
||||
rings.members.push_back(ring);
|
||||
|
||||
std::vector<ProjectedFeature> features;
|
||||
features.push_back(Convert::create(Tags(), featureType, rings));
|
||||
return features;
|
||||
})(), maxZoom, 4, 100, 10) {}
|
||||
maxZoom(maxZoom_) {
|
||||
}
|
||||
|
||||
void ShapeAnnotationImpl::updateStyle(Style& style) {
|
||||
if (style.getLayer(layerID))
|
||||
return;
|
||||
|
||||
if (shape.styleProperties.is<LinePaintProperties>()) {
|
||||
const LinePaintProperties& properties = shape.styleProperties.get<LinePaintProperties>();
|
||||
std::unique_ptr<StyleLayer> layer;
|
||||
|
||||
std::unique_ptr<LineLayer> layer = std::make_unique<LineLayer>();
|
||||
layer->id = layerID;
|
||||
layer->type = StyleLayerType::Line;
|
||||
if (shape.properties.is<LinePaintProperties>()) {
|
||||
layer = createLineLayer();
|
||||
|
||||
const LinePaintProperties& properties = shape.properties.get<LinePaintProperties>();
|
||||
ClassProperties paintProperties;
|
||||
paintProperties.set(PropertyKey::LineOpacity, ConstantFunction<float>(properties.opacity));
|
||||
paintProperties.set(PropertyKey::LineWidth, ConstantFunction<float>(properties.width));
|
||||
paintProperties.set(PropertyKey::LineColor, ConstantFunction<Color>(properties.color));
|
||||
layer->styles.emplace(ClassID::Default, std::move(paintProperties));
|
||||
|
||||
layer->bucket = std::make_shared<StyleBucket>(layer->type);
|
||||
layer->bucket->name = layer->id;
|
||||
layer->bucket->source = AnnotationManager::SourceID;
|
||||
layer->bucket->source_layer = layer->id;
|
||||
layer->bucket->layout.set(PropertyKey::LineJoin, ConstantFunction<JoinType>(JoinType::Round));
|
||||
|
||||
style.addLayer(std::move(layer), AnnotationManager::PointLayerID);
|
||||
|
||||
} else if (shape.styleProperties.is<FillPaintProperties>()) {
|
||||
const FillPaintProperties& properties = shape.styleProperties.get<FillPaintProperties>();
|
||||
|
||||
std::unique_ptr<FillLayer> layer = std::make_unique<FillLayer>();
|
||||
layer->id = layerID;
|
||||
layer->type = StyleLayerType::Fill;
|
||||
} else if (shape.properties.is<FillPaintProperties>()) {
|
||||
layer = createFillLayer();
|
||||
|
||||
const FillPaintProperties& properties = shape.properties.get<FillPaintProperties>();
|
||||
ClassProperties paintProperties;
|
||||
paintProperties.set(PropertyKey::FillOpacity, ConstantFunction<float>(properties.opacity));
|
||||
paintProperties.set(PropertyKey::FillColor, ConstantFunction<Color>(properties.fill_color));
|
||||
paintProperties.set(PropertyKey::FillOutlineColor, ConstantFunction<Color>(properties.stroke_color));
|
||||
layer->styles.emplace(ClassID::Default, std::move(paintProperties));
|
||||
|
||||
layer->bucket = std::make_shared<StyleBucket>(layer->type);
|
||||
layer->bucket->name = layer->id;
|
||||
layer->bucket->source = AnnotationManager::SourceID;
|
||||
layer->bucket->source_layer = layer->id;
|
||||
} else {
|
||||
const StyleLayer* sourceLayer = style.getLayer(shape.properties.get<std::string>());
|
||||
if (!sourceLayer) return;
|
||||
|
||||
style.addLayer(std::move(layer), AnnotationManager::PointLayerID);
|
||||
switch (sourceLayer->type) {
|
||||
case StyleLayerType::Line:
|
||||
layer = createLineLayer();
|
||||
break;
|
||||
|
||||
case StyleLayerType::Fill:
|
||||
layer = createFillLayer();
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
layer->styles = sourceLayer->styles;
|
||||
layer->bucket->layout = sourceLayer->bucket->layout;
|
||||
}
|
||||
|
||||
layer->bucket->name = layer->id;
|
||||
layer->bucket->source = AnnotationManager::SourceID;
|
||||
layer->bucket->source_layer = layer->id;
|
||||
|
||||
style.addLayer(std::move(layer), AnnotationManager::PointLayerID);
|
||||
}
|
||||
|
||||
std::unique_ptr<StyleLayer> ShapeAnnotationImpl::createLineLayer() {
|
||||
type = ProjectedFeatureType::LineString;
|
||||
std::unique_ptr<LineLayer> layer = std::make_unique<LineLayer>();
|
||||
layer->id = layerID;
|
||||
layer->type = StyleLayerType::Line;
|
||||
layer->bucket = std::make_shared<StyleBucket>(layer->type);
|
||||
layer->bucket->layout.set(PropertyKey::LineJoin, ConstantFunction<JoinType>(JoinType::Round));
|
||||
return std::move(layer);
|
||||
}
|
||||
|
||||
std::unique_ptr<StyleLayer> ShapeAnnotationImpl::createFillLayer() {
|
||||
type = ProjectedFeatureType::Polygon;
|
||||
std::unique_ptr<FillLayer> layer = std::make_unique<FillLayer>();
|
||||
layer->id = layerID;
|
||||
layer->type = StyleLayerType::Fill;
|
||||
layer->bucket = std::make_shared<StyleBucket>(layer->type);
|
||||
return std::move(layer);
|
||||
}
|
||||
|
||||
void ShapeAnnotationImpl::updateTile(const TileID& tileID, AnnotationTile& tile) {
|
||||
const auto& shapeTile = shapeTiler.getTile(tileID.z, tileID.x, tileID.y);
|
||||
if (!shapeTiler) {
|
||||
const double baseTolerance = 3;
|
||||
const uint16_t extent = 4096;
|
||||
const double tolerance = baseTolerance / ((1 << maxZoom) * extent);
|
||||
|
||||
ProjectedGeometryContainer rings;
|
||||
std::vector<LonLat> points;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (type == ProjectedFeatureType::Polygon &&
|
||||
(points.front().lon != points.back().lon || points.front().lat != points.back().lat)) {
|
||||
points.push_back(LonLat(points.front().lon, points.front().lat));
|
||||
}
|
||||
|
||||
ProjectedGeometryContainer ring = Convert::project(points, tolerance);
|
||||
rings.members.push_back(ring);
|
||||
|
||||
std::vector<ProjectedFeature> features;
|
||||
features.push_back(Convert::create(Tags(), type, rings));
|
||||
|
||||
shapeTiler = std::make_unique<mapbox::util::geojsonvt::GeoJSONVT>(features, maxZoom, 4, 100, 10);
|
||||
}
|
||||
|
||||
const auto& shapeTile = shapeTiler->getTile(tileID.z, tileID.x, tileID.y);
|
||||
if (!shapeTile)
|
||||
return;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace mbgl {
|
||||
|
||||
class Style;
|
||||
class StyleLayer;
|
||||
class AnnotationTile;
|
||||
|
||||
class ShapeAnnotationImpl {
|
||||
@@ -30,7 +31,12 @@ public:
|
||||
const ShapeAnnotation shape;
|
||||
|
||||
private:
|
||||
mapbox::util::geojsonvt::GeoJSONVT shapeTiler;
|
||||
std::unique_ptr<StyleLayer> createLineLayer();
|
||||
std::unique_ptr<StyleLayer> createFillLayer();
|
||||
|
||||
const uint8_t maxZoom;
|
||||
mapbox::util::geojsonvt::ProjectedFeatureType type;
|
||||
std::unique_ptr<mapbox::util::geojsonvt::GeoJSONVT> shapeTiler;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ namespace mbgl {
|
||||
|
||||
class ClassProperties {
|
||||
public:
|
||||
inline ClassProperties() {}
|
||||
inline ClassProperties(ClassProperties &&properties_)
|
||||
: properties(std::move(properties_.properties)) {}
|
||||
|
||||
inline void set(PropertyKey key, const PropertyValue &value) {
|
||||
properties.emplace(key, value);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,21 @@ TEST(Annotations, FillAnnotation) {
|
||||
util::write_file("test/output/fill_annotation.png", renderPNG(map));
|
||||
}
|
||||
|
||||
TEST(Annotations, StyleSourcedShapeAnnotation) {
|
||||
auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
HeadlessView view(display, 1);
|
||||
DefaultFileSource fileSource(nullptr);
|
||||
|
||||
Map map(view, fileSource, MapMode::Still);
|
||||
map.setStyleJSON(util::read_file("test/fixtures/api/annotation.json"), "");
|
||||
|
||||
AnnotationSegments segments = {{ {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }};
|
||||
|
||||
map.addShapeAnnotation(ShapeAnnotation(segments, "annotation"));
|
||||
|
||||
util::write_file("test/output/style_sourced_shape_annotation.png", renderPNG(map));
|
||||
}
|
||||
|
||||
TEST(Annotations, AddMultiple) {
|
||||
auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
HeadlessView view(display, 1);
|
||||
|
||||
19
test/fixtures/api/annotation.json
vendored
Normal file
19
test/fixtures/api/annotation.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": 8,
|
||||
"sources": {
|
||||
"fake": {
|
||||
"type": "vector",
|
||||
"url": "asset://TEST_DATA/fixtures/tiles/streets.json"
|
||||
}
|
||||
},
|
||||
"layers": [{
|
||||
"id": "annotation",
|
||||
"type": "fill",
|
||||
"source": "fake",
|
||||
"source-layer": "fake",
|
||||
"paint": {
|
||||
"fill-color": "rgba(255,0,0,1)"
|
||||
}
|
||||
}],
|
||||
"sprite": "asset://TEST_DATA/fixtures/resources/sprite"
|
||||
}
|
||||
Reference in New Issue
Block a user