diff --git a/bin/style.js b/bin/style.js index 9d2477544..a10ca6acf 100644 --- a/bin/style.js +++ b/bin/style.js @@ -222,6 +222,7 @@ module.exports = { "opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ] }, "country_label": { + "type": "text", "stroke": [ 1, 1, 1, 0.7 ], "color": "text" }, diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index f9ad8b3a4..1402cb074 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -90,7 +90,6 @@ public: std::shared_ptr triangleElementsBuffer; std::shared_ptr lineElementsBuffer; std::shared_ptr pointElementsBuffer; - std::shared_ptr textElementsBuffer; // Holds the buckets of this tile. // They contain the location offsets in the buffers stored above diff --git a/include/llmr/renderer/text_bucket.hpp b/include/llmr/renderer/text_bucket.hpp index 60b0668a1..5a19ebeda 100644 --- a/include/llmr/renderer/text_bucket.hpp +++ b/include/llmr/renderer/text_bucket.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace llmr { @@ -14,6 +15,9 @@ class Style; class TextVertexBuffer; class TriangleElementsBuffer; class TextShader; +class VectorTileFeature; +class VectorTileFace; +class VectorTilePlacement; struct Coordinate; struct pbf; @@ -26,7 +30,13 @@ public: virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); - void addGeometry(pbf& data); + void addFeature(const VectorTileFeature& feature, + const std::vector& faces, + const std::map>& shaping); + + void addLabel(const std::vector& line, + const std::vector& faces, + const std::vector& placements); bool empty() const; diff --git a/include/llmr/style/bucket_description.hpp b/include/llmr/style/bucket_description.hpp index c935d5061..0a750e113 100644 --- a/include/llmr/style/bucket_description.hpp +++ b/include/llmr/style/bucket_description.hpp @@ -35,6 +35,7 @@ inline BucketType bucketType(const std::string& type) { if (type == "fill") return BucketType::Fill; else if (type == "line") return BucketType::Line; else if (type == "point") return BucketType::Point; + else if (type == "text") return BucketType::Text; else return BucketType::None; } @@ -58,6 +59,7 @@ public: CapType cap = CapType::None; JoinType join = JoinType::None; std::string font; + std::string text_field; float font_size = 0.0f; float miter_limit = 2.0f; float round_limit = 1.0f; @@ -65,6 +67,7 @@ public: class BucketDescription { public: + BucketType feature_type = BucketType::None; BucketType type = BucketType::None; // Specify what data to pull into this bucket diff --git a/include/llmr/style/class_description.hpp b/include/llmr/style/class_description.hpp index efb46fd39..e98aee0c5 100644 --- a/include/llmr/style/class_description.hpp +++ b/include/llmr/style/class_description.hpp @@ -14,6 +14,7 @@ public: std::map fill; std::map line; std::map point; + std::map text; }; diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index de7b3404b..f3bc1054c 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -112,6 +112,14 @@ struct FillProperties { std::string image; }; +struct TextClass { + Color color = {{ 1, 1, 1, 1 }}; +}; + +struct TextProperties { + Color color = {{ 1, 1, 1, 1 }}; +}; + struct BackgroundClass { Color color = {{ 1, 1, 1, 1 }}; }; diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 1b57521f8..2323228b0 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -56,6 +56,7 @@ public: std::map fills; std::map lines; std::map points; + std::map texts; } computed; }; diff --git a/include/llmr/style/style_parser.hpp b/include/llmr/style/style_parser.hpp index 3c53037aa..93cd373b6 100644 --- a/include/llmr/style/style_parser.hpp +++ b/include/llmr/style/style_parser.hpp @@ -23,6 +23,7 @@ private: FillClass parseFillClass(JSVal value); LineClass parseLineClass(JSVal value); PointClass parsePointClass(JSVal value); + TextClass parseTextClass(JSVal value); BackgroundClass parseBackgroundClass(JSVal value); bool parseBoolean(JSVal value); diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 5ca0e817a..7110297ea 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ TileData::TileData(Tile::ID id, const Style& style) fillVertexBuffer(std::make_shared()), lineVertexBuffer(std::make_shared()), pointVertexBuffer(std::make_shared()), + textVertexBuffer(std::make_shared()), triangleElementsBuffer(std::make_shared()), lineElementsBuffer(std::make_shared()), pointElementsBuffer(std::make_shared()), @@ -157,7 +159,7 @@ std::shared_ptr TileData::createBucket(const VectorTile& tile, const Buc } else if (bucket_desc.type == BucketType::Text) { return createTextBucket(tile, layer, bucket_desc); } else { - // TODO: create other bucket types. + throw std::runtime_error("unknown bucket type"); } } else { // The layer specified in the bucket does not exist. Do nothing. @@ -206,7 +208,7 @@ std::shared_ptr TileData::createTextBucket(const VectorTile& tile, const } // TODO: currently hardcoded to use the first font stack. - const std::map>& stack = layer.shaping.begin()->second; + const std::map>& shaping = layer.shaping.begin()->second; std::vector faces; for (const std::string& face : layer.faces) { @@ -221,11 +223,9 @@ std::shared_ptr TileData::createTextBucket(const VectorTile& tile, const std::shared_ptr bucket = std::make_shared(textVertexBuffer, triangleElementsBuffer, bucket_desc); FilteredVectorTileLayer filtered_layer(layer, bucket_desc); - for (pbf feature_pbf : filtered_layer) { + for (const pbf& feature_pbf : filtered_layer) { if (state == State::obsolete) return nullptr; - - const VectorTileFeature feature(feature_pbf, layer); - std::cerr << feature; + bucket->addFeature({ feature_pbf, layer }, faces, shaping); } return bucket; diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index ebe65184a..e2d6d828c 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,4 +1,4 @@ #include -const char *llmr::kTileURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; -const char *llmr::kSpriteURL = "https://dl.dropboxusercontent.com/u/575564/sprite"; +const char *llmr::kTileURL = "http://localhost:3333/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; +const char *llmr::kSpriteURL = "http://localhost:3333/images/sprite"; diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index dd646794b..0b9b865b8 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -11,12 +11,12 @@ #include #include +#include + #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) #include -struct geometry_too_long_exception : std::exception {}; - using namespace llmr; TextBucket::TextBucket(const std::shared_ptr& vertexBuffer, @@ -26,12 +26,55 @@ TextBucket::TextBucket(const std::shared_ptr& vertexBuffer, vertexBuffer(vertexBuffer), triangleElementsBuffer(triangleElementsBuffer), vertex_start(vertexBuffer->index()), - triangle_elements_start(triangleElementsBuffer->index()) -{ + triangle_elements_start(triangleElementsBuffer->index()) { } -void TextBucket::addGeometry(pbf& geom) { - // noop +void TextBucket::addFeature(const VectorTileFeature& feature, + const std::vector& faces, + const std::map>& shaping) { + auto it_prop = feature.properties.find(geometry.text_field); + if (it_prop == feature.properties.end()) { + // feature does not have the correct property + return; + } + const Value& value = it_prop->second; + + + auto it_shaping = shaping.find(value); + if (it_shaping == shaping.end()) { + // we lack shaping information for this label + return; + } + const std::vector& placements = it_shaping->second; + + // std::cerr << "we have shaping for " << value << std::endl; + + // Decode all lines. + std::vector line; + Geometry::command cmd; + + Coordinate coord; + pbf geom = feature.geometry; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (!line.empty()) { + addLabel(line, faces, placements); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addLabel(line, faces, placements); + } +} + +void TextBucket::addLabel(const std::vector& line, + const std::vector& faces, + const std::vector& placements) { + // TODO } void TextBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) { diff --git a/src/style/resources.cpp b/src/style/resources.cpp index 58cf8265f..9e06afc6d 100644 --- a/src/style/resources.cpp +++ b/src/style/resources.cpp @@ -3,5 +3,5 @@ using namespace llmr; -const unsigned char resources::style[] = R"JSON({"buckets":{"water":{"source":"streets","layer":"water","type":"fill"},"road_large":{"source":"streets","layer":"road","field":"class","value":["motorway","main"],"type":"line","cap":"round","join":"round"},"road_regular":{"source":"streets","layer":"road","field":"class","value":"street","type":"line","cap":"round","join":"round"},"road_limited":{"source":"streets","layer":"road","field":"class","value":"street_limited","type":"line","cap":"round","join":"round"},"park":{"source":"streets","layer":"landuse","field":"class","value":"park","type":"fill"},"wood":{"source":"streets","layer":"landuse","field":"class","value":"wood","type":"fill"},"school":{"source":"streets","layer":"landuse","field":"class","value":"school","type":"fill"},"cemetery":{"source":"streets","layer":"landuse","field":"class","value":"cemetery","type":"fill"},"industrial":{"source":"streets","layer":"landuse","field":"class","value":"industrial","type":"fill"},"building":{"source":"streets","layer":"building","type":"fill"},"alcohol_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Alcohol"],"type":"point"},"cafe_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Cafe"],"type":"point"},"embassy_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Embassy"],"type":"point"},"park_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Park"],"type":"point"},"restaurant_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Restaurant"],"type":"point"}},"sprite":"img/maki-sprite","constants":{"park":"#c8df9f"},"structure":[{"name":"park","bucket":"park"},{"name":"wood","bucket":"wood"},{"name":"water","bucket":"water"},{"name":"building","bucket":"building"},{"name":"road_limited","bucket":"road_limited"},{"name":"road_regular","bucket":"road_regular"},{"name":"road_large","bucket":"road_large"},{"name":"alcohol_poi","bucket":"alcohol_poi"},{"name":"cafe_poi","bucket":"cafe_poi"},{"name":"embassy_poi","bucket":"embassy_poi"},{"name":"park_poi","bucket":"park_poi"},{"name":"restaurant_poi","bucket":"restaurant_poi"}],"classes":[{"name":"default","layers":{"background":{"type":"background","color":"#FFFFFF"},"park":{"type":"fill","color":"park","antialias":true,"image":"park"},"wood":{"type":"fill","color":"#33AA66","antialias":true,"opacity":0.1},"water":{"type":"fill","color":"#bae0e9","stroke":"#73b6e6","antialias":true,"image":"water_etsy"},"building":{"type":"fill","color":"#000000","antialias":true,"opacity":["linear",13,0,0.1,0,0.1]},"road_limited":{"type":"line","color":"#BBBBBB","width":["stops",{"z":0,"val":1},{"z":30,"val":1}]},"road_regular":{"type":"line","color":"#999999","width":["stops",{"z":0,"val":0.5},{"z":13,"val":0.5},{"z":16,"val":2},{"z":20,"val":32},{"z":30,"val":32}]},"road_large":{"type":"line","color":"#666666","width":["stops",{"z":0,"val":0.5},{"z":11,"val":0.5},{"z":13,"val":1},{"z":16,"val":4},{"z":20,"val":64},{"z":30,"val":64}]},"alcohol_poi":{"type":"point","color":"#cccccc","size":18,"image":"alcohol-shop","opacity":["linear",15,0,1,0,0.75]},"cafe_poi":{"type":"point","color":"#cccccc","size":18,"image":"cafe","opacity":["linear",15,0,1,0,0.75]},"embassy_poi":{"type":"point","color":"#cccccc","size":18,"image":"embassy","opacity":["linear",15,0,1,0,0.75]},"park_poi":{"type":"point","color":"#cccccc","size":18,"image":"park","opacity":["linear",15,0,1,0,0.75]},"restaurant_poi":{"type":"point","color":"#cccccc","size":18,"image":"restaurant","opacity":["linear",15,0,1,0,0.75]}}}]})JSON"; +const unsigned char resources::style[] = R"JSON({"buckets":{"water":{"source":"streets","layer":"water","type":"fill"},"road_large":{"source":"streets","layer":"road","field":"class","value":["motorway","main"],"type":"line","cap":"round","join":"round"},"road_regular":{"source":"streets","layer":"road","field":"class","value":"street","type":"line","cap":"round","join":"round"},"road_limited":{"source":"streets","layer":"road","field":"class","value":"street_limited","type":"line","cap":"round","join":"round"},"park":{"source":"streets","layer":"landuse","field":"class","value":"park","type":"fill"},"wood":{"source":"streets","layer":"landuse","field":"class","value":"wood","type":"fill"},"school":{"source":"streets","layer":"landuse","field":"class","value":"school","type":"fill"},"cemetery":{"source":"streets","layer":"landuse","field":"class","value":"cemetery","type":"fill"},"industrial":{"source":"streets","layer":"landuse","field":"class","value":"industrial","type":"fill"},"building":{"source":"streets","layer":"building","type":"fill"},"alcohol_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Alcohol"],"type":"point"},"cafe_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Cafe"],"type":"point"},"embassy_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Embassy"],"type":"point"},"park_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Park"],"type":"point"},"restaurant_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Restaurant"],"type":"point"},"country_label":{"source":"streets","type":"text","layer":"country_label","feature_type":"point","padding":10,"text_field":"name","path":"horizontal","font":"Open Sans, Jomolhari, Siyam Rupali, Alef, Arial Unicode MS","fontSize":16}},"sprite":"img/maki-sprite","constants":{"park":"#c8df9f","text":"#000000"},"structure":[{"name":"park","bucket":"park"},{"name":"wood","bucket":"wood"},{"name":"water","bucket":"water"},{"name":"building","bucket":"building"},{"name":"road_limited","bucket":"road_limited"},{"name":"road_regular","bucket":"road_regular"},{"name":"road_large","bucket":"road_large"},{"name":"alcohol_poi","bucket":"alcohol_poi"},{"name":"cafe_poi","bucket":"cafe_poi"},{"name":"embassy_poi","bucket":"embassy_poi"},{"name":"park_poi","bucket":"park_poi"},{"name":"restaurant_poi","bucket":"restaurant_poi"},{"name":"country_label","bucket":"country_label"}],"classes":[{"name":"default","layers":{"background":{"type":"background","color":"#FFFFFF"},"park":{"type":"fill","color":"park","antialias":true,"image":"park"},"wood":{"type":"fill","color":"#33AA66","antialias":true,"opacity":0.1},"water":{"type":"fill","color":"#bae0e9","stroke":"#73b6e6","antialias":true,"image":"water_etsy"},"building":{"type":"fill","color":"#000000","antialias":true,"opacity":["linear",13,0,0.1,0,0.1]},"road_limited":{"type":"line","color":"#BBBBBB","width":["stops",{"z":0,"val":1},{"z":30,"val":1}]},"road_regular":{"type":"line","color":"#999999","width":["stops",{"z":0,"val":0.5},{"z":13,"val":0.5},{"z":16,"val":2},{"z":20,"val":32},{"z":30,"val":32}]},"road_large":{"type":"line","color":"#666666","width":["stops",{"z":0,"val":0.5},{"z":11,"val":0.5},{"z":13,"val":1},{"z":16,"val":4},{"z":20,"val":64},{"z":30,"val":64}]},"alcohol_poi":{"type":"point","color":"#cccccc","size":18,"image":"alcohol-shop","opacity":["linear",15,0,1,0,0.75]},"cafe_poi":{"type":"point","color":"#cccccc","size":18,"image":"cafe","opacity":["linear",15,0,1,0,0.75]},"embassy_poi":{"type":"point","color":"#cccccc","size":18,"image":"embassy","opacity":["linear",15,0,1,0,0.75]},"park_poi":{"type":"point","color":"#cccccc","size":18,"image":"park","opacity":["linear",15,0,1,0,0.75]},"restaurant_poi":{"type":"point","color":"#cccccc","size":18,"image":"restaurant","opacity":["linear",15,0,1,0,0.75]},"country_label":{"type":"text","stroke":[1,1,1,0.7],"color":"text"}}}]})JSON"; const unsigned long resources::style_size = sizeof(resources::style); diff --git a/src/style/style.cpp b/src/style/style.cpp index 03656e8f4..9ecf59e70 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -76,6 +76,16 @@ void Style::cascade(float z) { point.image = layer.image; } + for (const auto& text_pair : sheetClass.text) { + const std::string& layer_name = text_pair.first; + const llmr::TextClass& layer = text_pair.second; + + // TODO: This should be restricted to point styles that have actual + // values so as to not override with default values. + llmr::TextProperties& text = computed.texts[layer_name]; + text.color = layer.color; + } + // Cascade background computed.background.color = sheetClass.background.color; } diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 82e2b5153..f9e294f97 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -233,6 +233,8 @@ void StyleParser::parseClass(const std::string& name, JSVal value, ClassDescript class_desc.line.insert({ name, std::forward(parseLineClass(value)) }); } else if (type_name == "point") { class_desc.point.insert({ name, std::forward(parsePointClass(value)) }); + } else if (type_name == "text") { + class_desc.text.insert({ name, std::forward(parseTextClass(value)) }); } else if (type_name == "background") { class_desc.background = parseBackgroundClass(value); } else { @@ -471,6 +473,17 @@ PointClass StyleParser::parsePointClass(JSVal value) { return klass; } + +TextClass StyleParser::parseTextClass(JSVal value) { + TextClass klass; + + if (value.HasMember("color")) { + klass.color = parseColor(value["color"]); + } + + return klass; +} + BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { BackgroundClass klass;