diff --git a/bin/style.js b/bin/style.js index 876212ad5..a50ca7bba 100644 --- a/bin/style.js +++ b/bin/style.js @@ -89,6 +89,9 @@ module.exports = { }, }, "sprite": "img/maki-sprite", + "constants": { + "park": "#c8df9f", + }, "structure": [ { "name": "park", "bucket": "park" }, { "name": "wood", "bucket": "wood" }, @@ -113,7 +116,7 @@ module.exports = { }, "park": { "type": "fill", - "color": "#c8df9f", + "color": "park", "antialias": true, "image": "park" }, diff --git a/include/llmr/style/style_parser.hpp b/include/llmr/style/style_parser.hpp new file mode 100644 index 000000000..3c53037aa --- /dev/null +++ b/include/llmr/style/style_parser.hpp @@ -0,0 +1,42 @@ +#ifndef LLMR_STYLE_STYLE_PARSER +#define LLMR_STYLE_STYLE_PARSER + +#include +#include + +namespace llmr { + +class StyleParser { +public: + using JSVal = const rapidjson::Value&; + + void parseBuckets(JSVal value, std::map& buckets); + void parseLayers(JSVal value, std::vector& layers); + void parseConstants(JSVal value); + void parseClasses(JSVal value, std::map& classes); + +private: + LayerDescription parseLayer(JSVal value); + BucketDescription parseBucket(JSVal value); + std::pair parseClassDescription(JSVal value); + void parseClass(const std::string& name, JSVal value, ClassDescription& class_desc); + FillClass parseFillClass(JSVal value); + LineClass parseLineClass(JSVal value); + PointClass parsePointClass(JSVal value); + BackgroundClass parseBackgroundClass(JSVal value); + + bool parseBoolean(JSVal value); + std::string parseString(JSVal value); + Color parseColor(JSVal value); + Value parseValue(JSVal value); + template typename FunctionProperty::fn parseFunctionType(JSVal type); + FunctionProperty parseFloatFunction(JSVal value); + FunctionProperty parseBoolFunction(JSVal value); + +private: + std::map constants; +}; + +} + +#endif diff --git a/src/style/resources.cpp b/src/style/resources.cpp index e87ae7fee..58cf8265f 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","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":"#c8df9f","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"}},"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 long resources::style_size = sizeof(resources::style); diff --git a/src/style/style.cpp b/src/style/style.cpp index 109c59c8c..643841f71 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include @@ -80,23 +80,8 @@ void Style::cascade(float z) { } } - - -void loadBucketsJSON(const rapidjson::Value& value, std::map& buckets); -BucketDescription loadBucketJSON(const rapidjson::Value& value); -void loadLayersJSON(const rapidjson::Value& value, std::vector& layers); -LayerDescription loadLayerJSON(const rapidjson::Value& value); -void loadClassesJSON(const rapidjson::Value& value, std::map& classes); -std::pair loadClassDescriptionJSON(const rapidjson::Value& value); -void loadClassJSON(const std::string& name, const rapidjson::Value& value, ClassDescription& class_desc); -FillClass loadFillClassJSON(const rapidjson::Value& value); -LineClass loadLineClassJSON(const rapidjson::Value& value); -PointClass loadPointClassJSON(const rapidjson::Value& value); -BackgroundClass loadBackgroundClassJSON(const rapidjson::Value& value); -Value loadValueJSON(const rapidjson::Value& value); - void Style::loadJSON(const uint8_t *const data, size_t bytes) { - rapidjson::Document document; + rapidjson::Document doc; if (bytes <= 0) { return; @@ -106,491 +91,34 @@ void Style::loadJSON(const uint8_t *const data, size_t bytes) { throw exception("style JSON string is not 0-terminated"); } - document.Parse<0>((const char *const)data); + doc.Parse<0>((const char *const)data); + + const rapidjson::Document& document = doc; buckets.clear(); layers.clear(); classes.clear(); + StyleParser parser; + if (document.IsObject()) { if (document.HasMember("buckets")) { - loadBucketsJSON(document["buckets"], buckets); + parser.parseBuckets(document["buckets"], buckets); } if (document.HasMember("structure")) { - loadLayersJSON(document["structure"], layers); + parser.parseLayers(document["structure"], layers); + } + + if (document.HasMember("constants")) { + parser.parseConstants(document["constants"]); } if (document.HasMember("classes")) { - loadClassesJSON(document["classes"], classes); + parser.parseClasses(document["classes"], classes); } } else { throw Style::exception("stylesheet root must be an object"); } } -void loadBucketsJSON(const rapidjson::Value& value, std::map& buckets) { - if (value.IsObject()) { - rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); - for (; itr != value.MemberEnd(); ++itr) { - buckets.emplace(itr->name.GetString(), std::forward(loadBucketJSON(itr->value))); - } - } else { - throw Style::exception("buckets must be an object"); - } -} - -BucketDescription loadBucketJSON(const rapidjson::Value& value) { - BucketDescription bucket; - - rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); - for (; itr != value.MemberEnd(); ++itr) { - const std::string name(itr->name.GetString(), itr->name.GetStringLength()); - const rapidjson::Value& value = itr->value; - - if (name == "type") { - if (value.IsString()) { - bucket.type = bucketType({ value.GetString(), value.GetStringLength() }); - } else { - throw Style::exception("bucket type must be a string"); - } - } else if (name == "source") { - if (value.IsString()) { - bucket.source_name = { value.GetString(), value.GetStringLength() }; - } else { - throw Style::exception("source name must be a string"); - } - } else if (name == "layer") { - if (value.IsString()) { - bucket.source_layer = { value.GetString(), value.GetStringLength() }; - } else { - throw Style::exception("layer name must be a string"); - } - } else if (name == "field") { - if (value.IsString()) { - bucket.source_field = { value.GetString(), value.GetStringLength() }; - } else { - throw Style::exception("field name must be a string"); - } - } else if (name == "value") { - if (value.IsArray()) { - for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - bucket.source_value.push_back(loadValueJSON(value[i])); - } - } else { - bucket.source_value.push_back(loadValueJSON(value)); - } - } else if (name == "cap") { - if (value.IsString()) { - bucket.geometry.cap = capType({ value.GetString(), value.GetStringLength() }); - } else { - throw Style::exception("cap type must be a string"); - } - } else if (name == "join") { - if (value.IsString()) { - bucket.geometry.join = joinType({ value.GetString(), value.GetStringLength() }); - } else { - throw Style::exception("join type must be a string"); - } - } else if (name == "font") { - if (value.IsString()) { - bucket.geometry.font = { value.GetString(), value.GetStringLength() }; - } else { - throw Style::exception("font stack must be a string"); - } - } else if (name == "fontSize") { - if (value.IsNumber()) { - bucket.geometry.font_size = value.GetDouble(); - } else { - throw Style::exception("font size must be a number"); - } - } else if (name == "miterLimit") { - if (value.IsNumber()) { - bucket.geometry.miter_limit = value.GetDouble(); - } else { - throw Style::exception("miter limit must be a number"); - } - } else if (name == "roundLimit") { - if (value.IsNumber()) { - bucket.geometry.round_limit = value.GetDouble(); - } else { - throw Style::exception("round limit must be a number"); - } - } - } - - return bucket; -} - -void loadLayersJSON(const rapidjson::Value& value, std::vector& layers) { - if (value.IsArray()) { - for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - layers.push_back(loadLayerJSON(value[i])); - } - } else { - throw Style::exception("structure must be an array"); - } -} - -LayerDescription loadLayerJSON(const rapidjson::Value& value) { - LayerDescription layer; - - if (value.IsObject()) { - if (value.HasMember("name")) { - const rapidjson::Value& name = value["name"]; - if (name.IsString()) { - layer.name = { name.GetString(), name.GetStringLength() }; - } else { - throw Style::exception("structure element name must be a string"); - } - } else { - throw Style::exception("structure element must have a name"); - } - - if (value.HasMember("bucket")) { - const rapidjson::Value& bucket = value["bucket"]; - if (bucket.IsString()) { - layer.bucket_name = { bucket.GetString(), bucket.GetStringLength() }; - } else { - throw Style::exception("structure element bucket must be a string"); - } - } else if (value.HasMember("layers")) { - loadLayersJSON(value["layers"], layer.child_layer); - } else { - throw Style::exception("structure element must have either a bucket name or child layers"); - } - } else { - throw Style::exception("structure element must be an object"); - } - - return layer; -} - -void loadClassesJSON(const rapidjson::Value& value, std::map& classes) { - if (value.IsArray()) { - for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - classes.insert(std::forward>(loadClassDescriptionJSON(value[i]))); - } - } else { - throw Style::exception("classes must be an array"); - } -} - -std::pair loadClassDescriptionJSON(const rapidjson::Value& value) { - ClassDescription klass; - std::string klass_name; - - if (value.IsObject()) { - if (value.HasMember("name")) { - const rapidjson::Value& name = value["name"]; - if (name.IsString()) { - klass_name = { name.GetString(), name.GetStringLength() }; - } else { - throw Style::exception("class name must be a string"); - } - } else { - throw Style::exception("class must have a name"); - } - - if (value.HasMember("layers")) { - const rapidjson::Value& layers = value["layers"]; - - if (layers.IsObject()) { - rapidjson::Value::ConstMemberIterator itr = layers.MemberBegin(); - for (; itr != layers.MemberEnd(); ++itr) { - const std::string name { - itr->name.GetString(), itr->name.GetStringLength() - }; - loadClassJSON(name, itr->value, klass); - } - } else { - throw Style::exception("class layer styles must be an object"); - } - } else { - throw Style::exception("class must have layer styles"); - } - } - - return { klass_name, std::forward(klass) }; -} - -void loadClassJSON(const std::string& name, const rapidjson::Value& value, ClassDescription& class_desc) { - if (value.IsObject()) { - if (value.HasMember("type")) { - const rapidjson::Value& type = value["type"]; - if (type.IsString()) { - std::string type_name = { type.GetString(), type.GetStringLength() }; - if (type_name == "fill") { - class_desc.fill.insert({ name, std::forward(loadFillClassJSON(value)) }); - } else if (type_name == "line") { - class_desc.line.insert({ name, std::forward(loadLineClassJSON(value)) }); - } else if (type_name == "point") { - class_desc.point.insert({ name, std::forward(loadPointClassJSON(value)) }); - } else if (type_name == "background") { - class_desc.background = loadBackgroundClassJSON(value); - } else { - throw Style::exception("unkonwn class type name"); - } - } else { - throw Style::exception("style class type must be a string"); - } - } else { - throw Style::exception("style class must specify a type"); - } - } else { - throw Style::exception("style class must be an object"); - } -} - -bool parseBoolean(const rapidjson::Value& value) { - if (!value.IsBool()) { - throw Style::exception("boolean value must be a boolean"); - } - - return value.GetBool(); -} - -std::string parseString(const rapidjson::Value& value) { - if (!value.IsString()) { - throw Style::exception("string value must be a string"); - } - - return { value.GetString(), value.GetStringLength() }; -} - -Color parseColor(const rapidjson::Value& value) { - if (!value.IsString()) { - throw Style::exception("color value must be a string"); - } - - CSSColorParser::Color css_color = CSSColorParser::parse({ - value.GetString(), - value.GetStringLength() - }); - - return {{ - (float)css_color.r / 255, - (float)css_color.g / 255, - (float)css_color.b / 255, - css_color.a - } - }; -} - -template -typename FunctionProperty::fn parseFunctionType(const rapidjson::Value& type) { - if (type.IsString()) { - std::string t { type.GetString(), type.GetStringLength() }; - if (t == "constant") { - return &functions::constant; - } else if (t == "linear") { - return &functions::linear; - } else if (t == "stops") { - return &functions::stops; - } else { - throw Style::exception("unknown function type"); - } - } else { - throw Style::exception("function type must be a string"); - } -} - -FunctionProperty parseFloatFunction(const rapidjson::Value& value) { - FunctionProperty property; - - if (value.IsArray()) { - if (value.Size() < 1) { - throw Style::exception("value function does not have arguments"); - } - - property.function = parseFunctionType(value[(rapidjson::SizeType)0]); - for (rapidjson::SizeType i = 1; i < value.Size(); ++i) { - const rapidjson::Value& stop = value[i]; - if (stop.IsObject()) { - if (!stop.HasMember("z")) { - throw Style::exception("stop must have zoom level specification"); - } - const rapidjson::Value& z = stop["z"]; - if (!z.IsNumber()) { - throw Style::exception("zoom level in stops must be a number"); - } - property.values.push_back(z.GetDouble()); - - if (!stop.HasMember("val")) { - throw Style::exception("stop must have value specification"); - } - const rapidjson::Value& val = stop["val"]; - if (!val.IsNumber()) { - throw Style::exception("value in stops must be a number"); - } - property.values.push_back(val.GetDouble()); - } else if (stop.IsNumber()) { - property.values.push_back(stop.GetDouble()); - } else { - throw Style::exception("stop must be a number"); - } - } - } else if (value.IsNumber()) { - property.function = &functions::constant; - property.values.push_back(value.GetDouble()); - } - - return property; -} - -FunctionProperty parseBoolFunction(const rapidjson::Value& value) { - FunctionProperty property; - - if (value.IsArray()) { - if (value.Size() < 1) { - throw Style::exception("value function does not have arguments"); - } - - property.function = parseFunctionType(value[(rapidjson::SizeType)0]); - for (rapidjson::SizeType i = 1; i < value.Size(); ++i) { - const rapidjson::Value& stop = value[i]; - if (stop.IsObject()) { - if (!stop.HasMember("z")) { - throw Style::exception("stop must have zoom level specification"); - } - const rapidjson::Value& z = stop["z"]; - if (!z.IsBool()) { - throw Style::exception("zoom level in stops must be a number"); - } - property.values.push_back(z.GetBool()); - - if (!stop.HasMember("val")) { - throw Style::exception("stop must have value specification"); - } - const rapidjson::Value& val = stop["val"]; - if (!val.IsBool()) { - throw Style::exception("value in stops must be a number"); - } - property.values.push_back(val.GetBool()); - } else if (stop.IsBool()) { - property.values.push_back(stop.GetBool()); - } else { - throw Style::exception("stop must be a number"); - } - } - } else if (value.IsBool()) { - property.function = &functions::constant; - property.values.push_back(value.GetBool()); - } - - return property; -} - -FillClass loadFillClassJSON(const rapidjson::Value& value) { - FillClass klass; - - if (value.HasMember("hidden")) { - klass.hidden = parseBoolFunction(value["hidden"]); - } - - if (value.HasMember("color")) { - klass.fill_color = parseColor(value["color"]); - } - - if (value.HasMember("stroke")) { - klass.stroke_color = parseColor(value["stroke"]); - } else { - klass.stroke_color = klass.fill_color; - } - - if (value.HasMember("antialias")) { - klass.antialias = parseBoolean(value["antialias"]); - } - - if (value.HasMember("image")) { - klass.image = parseString(value["image"]); - } - - if (value.HasMember("opacity")) { - klass.opacity = parseFloatFunction(value["opacity"]); - } - - return klass; -} - -LineClass loadLineClassJSON(const rapidjson::Value& value) { - LineClass klass; - - if (value.HasMember("hidden")) { - klass.hidden = parseBoolFunction(value["hidden"]); - } - - if (value.HasMember("color")) { - klass.color = parseColor(value["color"]); - } - - if (value.HasMember("width")) { - klass.width = parseFloatFunction(value["width"]); - } - - if (value.HasMember("opacity")) { - klass.opacity = parseFloatFunction(value["opacity"]); - } - - return klass; -} - -PointClass loadPointClassJSON(const rapidjson::Value& value) { - PointClass klass; - - if (value.HasMember("hidden")) { - klass.hidden = parseBoolFunction(value["hidden"]); - } - - if (value.HasMember("color")) { - klass.color = parseColor(value["color"]); - } - - if (value.HasMember("opacity")) { - klass.opacity = parseFloatFunction(value["opacity"]); - } - - if (value.HasMember("image")) { - klass.image = parseString(value["image"]); - } - - if (value.HasMember("size")) { - klass.size = parseFloatFunction(value["size"]); - } - - return klass; -} - -BackgroundClass loadBackgroundClassJSON(const rapidjson::Value& value) { - BackgroundClass klass; - - if (value.HasMember("color")) { - klass.color = parseColor(value["color"]); - } - - return klass; -} - -Value loadValueJSON(const rapidjson::Value& value) { - switch (value.GetType()) { - case rapidjson::kNullType: - case rapidjson::kFalseType: - return false; - - case rapidjson::kTrueType: - return true; - - case rapidjson::kStringType: - return std::string { value.GetString(), value.GetStringLength() }; - - case rapidjson::kNumberType: - if (value.IsUint64()) return value.GetUint64(); - if (value.IsInt64()) return value.GetInt64(); - return value.GetDouble(); - - case rapidjson::kObjectType: - case rapidjson::kArrayType: - throw Style::exception("value cannot be an object or array"); - return false; - } -} diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp new file mode 100644 index 000000000..7d0b46ffa --- /dev/null +++ b/src/style/style_parser.cpp @@ -0,0 +1,490 @@ +#include +#include + +using namespace llmr; + +void StyleParser::parseBuckets(JSVal value, std::map& buckets) { + if (value.IsObject()) { + rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); + for (; itr != value.MemberEnd(); ++itr) { + buckets.emplace( + std::forward({ itr->name.GetString(), itr->name.GetStringLength() }), + std::forward(parseBucket(itr->value)) + ); + } + } else { + throw Style::exception("buckets must be an object"); + } +} + +BucketDescription StyleParser::parseBucket(JSVal value) { + BucketDescription bucket; + + rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); + for (; itr != value.MemberEnd(); ++itr) { + const std::string name(itr->name.GetString(), itr->name.GetStringLength()); + JSVal value = itr->value; + + if (name == "type") { + if (value.IsString()) { + bucket.type = bucketType({ value.GetString(), value.GetStringLength() }); + } else { + throw Style::exception("bucket type must be a string"); + } + } else if (name == "source") { + if (value.IsString()) { + bucket.source_name = { value.GetString(), value.GetStringLength() }; + } else { + throw Style::exception("source name must be a string"); + } + } else if (name == "layer") { + if (value.IsString()) { + bucket.source_layer = { value.GetString(), value.GetStringLength() }; + } else { + throw Style::exception("layer name must be a string"); + } + } else if (name == "field") { + if (value.IsString()) { + bucket.source_field = { value.GetString(), value.GetStringLength() }; + } else { + throw Style::exception("field name must be a string"); + } + } else if (name == "value") { + if (value.IsArray()) { + for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { + bucket.source_value.push_back(parseValue(value[i])); + } + } else { + bucket.source_value.push_back(parseValue(value)); + } + } else if (name == "cap") { + if (value.IsString()) { + bucket.geometry.cap = capType({ value.GetString(), value.GetStringLength() }); + } else { + throw Style::exception("cap type must be a string"); + } + } else if (name == "join") { + if (value.IsString()) { + bucket.geometry.join = joinType({ value.GetString(), value.GetStringLength() }); + } else { + throw Style::exception("join type must be a string"); + } + } else if (name == "font") { + if (value.IsString()) { + bucket.geometry.font = { value.GetString(), value.GetStringLength() }; + } else { + throw Style::exception("font stack must be a string"); + } + } else if (name == "fontSize") { + if (value.IsNumber()) { + bucket.geometry.font_size = value.GetDouble(); + } else { + throw Style::exception("font size must be a number"); + } + } else if (name == "miterLimit") { + if (value.IsNumber()) { + bucket.geometry.miter_limit = value.GetDouble(); + } else { + throw Style::exception("miter limit must be a number"); + } + } else if (name == "roundLimit") { + if (value.IsNumber()) { + bucket.geometry.round_limit = value.GetDouble(); + } else { + throw Style::exception("round limit must be a number"); + } + } + } + + return bucket; +} + +void StyleParser::parseLayers(JSVal value, std::vector& layers) { + if (value.IsArray()) { + for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { + layers.push_back(parseLayer(value[i])); + } + } else { + throw Style::exception("structure must be an array"); + } +} + +LayerDescription StyleParser::parseLayer(JSVal value) { + LayerDescription layer; + + if (value.IsObject()) { + if (value.HasMember("name")) { + JSVal name = value["name"]; + if (name.IsString()) { + layer.name = { name.GetString(), name.GetStringLength() }; + } else { + throw Style::exception("structure element name must be a string"); + } + } else { + throw Style::exception("structure element must have a name"); + } + + if (value.HasMember("bucket")) { + JSVal bucket = value["bucket"]; + if (bucket.IsString()) { + layer.bucket_name = { bucket.GetString(), bucket.GetStringLength() }; + } else { + throw Style::exception("structure element bucket must be a string"); + } + } else if (value.HasMember("layers")) { + parseLayers(value["layers"], layer.child_layer); + } else { + throw Style::exception("structure element must have either a bucket name or child layers"); + } + } else { + throw Style::exception("structure element must be an object"); + } + + return layer; +} + +void StyleParser::parseConstants(JSVal value) { + if (value.IsObject()) { + rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); + for (; itr != value.MemberEnd(); ++itr) { + constants.emplace( + std::forward({ itr->name.GetString(), itr->name.GetStringLength() }), + &itr->value + ); + } + } else { + throw Style::exception("constants must be an object"); + } +} + +void StyleParser::parseClasses(JSVal value, std::map& classes) { + if (value.IsArray()) { + for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { + classes.insert(std::forward>(parseClassDescription(value[i]))); + } + } else { + throw Style::exception("classes must be an array"); + } +} + +std::pair StyleParser::parseClassDescription(JSVal value) { + ClassDescription klass; + std::string klass_name; + + if (value.IsObject()) { + if (value.HasMember("name")) { + JSVal name = value["name"]; + if (name.IsString()) { + klass_name = { name.GetString(), name.GetStringLength() }; + } else { + throw Style::exception("class name must be a string"); + } + } else { + throw Style::exception("class must have a name"); + } + + if (value.HasMember("layers")) { + JSVal layers = value["layers"]; + + if (layers.IsObject()) { + rapidjson::Value::ConstMemberIterator itr = layers.MemberBegin(); + for (; itr != layers.MemberEnd(); ++itr) { + const std::string name { + itr->name.GetString(), itr->name.GetStringLength() + }; + parseClass(name, itr->value, klass); + } + } else { + throw Style::exception("class layer styles must be an object"); + } + } else { + throw Style::exception("class must have layer styles"); + } + } + + return { klass_name, std::forward(klass) }; +} + +void StyleParser::parseClass(const std::string& name, JSVal value, ClassDescription& class_desc) { + if (value.IsObject()) { + if (value.HasMember("type")) { + JSVal type = value["type"]; + if (type.IsString()) { + std::string type_name = { type.GetString(), type.GetStringLength() }; + if (type_name == "fill") { + class_desc.fill.insert({ name, std::forward(parseFillClass(value)) }); + } else if (type_name == "line") { + 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 == "background") { + class_desc.background = parseBackgroundClass(value); + } else { + throw Style::exception("unkonwn class type name"); + } + } else { + throw Style::exception("style class type must be a string"); + } + } else { + throw Style::exception("style class must specify a type"); + } + } else { + throw Style::exception("style class must be an object"); + } +} + +bool StyleParser::parseBoolean(JSVal value) { + if (!value.IsBool()) { + throw Style::exception("boolean value must be a boolean"); + } + + return value.GetBool(); +} + +std::string StyleParser::parseString(JSVal value) { + if (!value.IsString()) { + throw Style::exception("string value must be a string"); + } + + return { value.GetString(), value.GetStringLength() }; +} + +Color StyleParser::parseColor(JSVal value) { + if (!value.IsString()) { + throw Style::exception("color value must be a string"); + } + + const std::string str { value.GetString(), value.GetStringLength() }; + + auto it = constants.find(str); + if (it != constants.end()) { + return parseColor(*it->second); + } else { + CSSColorParser::Color css_color = CSSColorParser::parse(str); + return {{ + (float)css_color.r / 255, + (float)css_color.g / 255, + (float)css_color.b / 255, + css_color.a + } + }; + } +} + +template +typename FunctionProperty::fn StyleParser::parseFunctionType(JSVal type) { + if (type.IsString()) { + std::string t { type.GetString(), type.GetStringLength() }; + if (t == "constant") { + return &functions::constant; + } else if (t == "linear") { + return &functions::linear; + } else if (t == "stops") { + return &functions::stops; + } else { + throw Style::exception("unknown function type"); + } + } else { + throw Style::exception("function type must be a string"); + } +} + +FunctionProperty StyleParser::parseFloatFunction(JSVal value) { + FunctionProperty property; + + if (value.IsArray()) { + if (value.Size() < 1) { + throw Style::exception("value function does not have arguments"); + } + + property.function = parseFunctionType(value[(rapidjson::SizeType)0]); + for (rapidjson::SizeType i = 1; i < value.Size(); ++i) { + JSVal stop = value[i]; + if (stop.IsObject()) { + if (!stop.HasMember("z")) { + throw Style::exception("stop must have zoom level specification"); + } + JSVal z = stop["z"]; + if (!z.IsNumber()) { + throw Style::exception("zoom level in stops must be a number"); + } + property.values.push_back(z.GetDouble()); + + if (!stop.HasMember("val")) { + throw Style::exception("stop must have value specification"); + } + JSVal val = stop["val"]; + if (!val.IsNumber()) { + throw Style::exception("value in stops must be a number"); + } + property.values.push_back(val.GetDouble()); + } else if (stop.IsNumber()) { + property.values.push_back(stop.GetDouble()); + } else { + throw Style::exception("stop must be a number"); + } + } + } else if (value.IsNumber()) { + property.function = &functions::constant; + property.values.push_back(value.GetDouble()); + } + + return property; +} + +FunctionProperty StyleParser::parseBoolFunction(JSVal value) { + FunctionProperty property; + + if (value.IsArray()) { + if (value.Size() < 1) { + throw Style::exception("value function does not have arguments"); + } + + property.function = parseFunctionType(value[(rapidjson::SizeType)0]); + for (rapidjson::SizeType i = 1; i < value.Size(); ++i) { + JSVal stop = value[i]; + if (stop.IsObject()) { + if (!stop.HasMember("z")) { + throw Style::exception("stop must have zoom level specification"); + } + JSVal z = stop["z"]; + if (!z.IsBool()) { + throw Style::exception("zoom level in stops must be a number"); + } + property.values.push_back(z.GetBool()); + + if (!stop.HasMember("val")) { + throw Style::exception("stop must have value specification"); + } + JSVal val = stop["val"]; + if (!val.IsBool()) { + throw Style::exception("value in stops must be a number"); + } + property.values.push_back(val.GetBool()); + } else if (stop.IsBool()) { + property.values.push_back(stop.GetBool()); + } else { + throw Style::exception("stop must be a number"); + } + } + } else if (value.IsBool()) { + property.function = &functions::constant; + property.values.push_back(value.GetBool()); + } + + return property; +} + +FillClass StyleParser::parseFillClass(JSVal value) { + FillClass klass; + + if (value.HasMember("hidden")) { + klass.hidden = parseBoolFunction(value["hidden"]); + } + + if (value.HasMember("color")) { + klass.fill_color = parseColor(value["color"]); + } + + if (value.HasMember("stroke")) { + klass.stroke_color = parseColor(value["stroke"]); + } else { + klass.stroke_color = klass.fill_color; + } + + if (value.HasMember("antialias")) { + klass.antialias = parseBoolean(value["antialias"]); + } + + if (value.HasMember("image")) { + klass.image = parseString(value["image"]); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFloatFunction(value["opacity"]); + } + + return klass; +} + +LineClass StyleParser::parseLineClass(JSVal value) { + LineClass klass; + + if (value.HasMember("hidden")) { + klass.hidden = parseBoolFunction(value["hidden"]); + } + + if (value.HasMember("color")) { + klass.color = parseColor(value["color"]); + } + + if (value.HasMember("width")) { + klass.width = parseFloatFunction(value["width"]); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFloatFunction(value["opacity"]); + } + + return klass; +} + +PointClass StyleParser::parsePointClass(JSVal value) { + PointClass klass; + + if (value.HasMember("hidden")) { + klass.hidden = parseBoolFunction(value["hidden"]); + } + + if (value.HasMember("color")) { + klass.color = parseColor(value["color"]); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFloatFunction(value["opacity"]); + } + + if (value.HasMember("image")) { + klass.image = parseString(value["image"]); + } + + if (value.HasMember("size")) { + klass.size = parseFloatFunction(value["size"]); + } + + return klass; +} + +BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { + BackgroundClass klass; + + if (value.HasMember("color")) { + klass.color = parseColor(value["color"]); + } + + return klass; +} + +Value StyleParser::parseValue(JSVal value) { + switch (value.GetType()) { + case rapidjson::kNullType: + case rapidjson::kFalseType: + return false; + + case rapidjson::kTrueType: + return true; + + case rapidjson::kStringType: + return std::string { value.GetString(), value.GetStringLength() }; + + case rapidjson::kNumberType: + if (value.IsUint64()) return value.GetUint64(); + if (value.IsInt64()) return value.GetInt64(); + return value.GetDouble(); + + case rapidjson::kObjectType: + case rapidjson::kArrayType: + throw Style::exception("value cannot be an object or array"); + return false; + } +}