diff options
Diffstat (limited to 'src/mbgl/style')
81 files changed, 6239 insertions, 267 deletions
diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp new file mode 100644 index 0000000000..e837c4e70b --- /dev/null +++ b/src/mbgl/style/conversion/constant.cpp @@ -0,0 +1,94 @@ +#include <mbgl/style/conversion/constant.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<bool> Converter<bool>::operator()(const Convertible& value, Error& error) const { + optional<bool> converted = toBool(value); + if (!converted) { + error = { "value must be a boolean" }; + return {}; + } + return *converted; +} + +optional<float> Converter<float>::operator()(const Convertible& value, Error& error) const { + optional<float> converted = toNumber(value); + if (!converted) { + error = { "value must be a number" }; + return {}; + } + return *converted; +} + +optional<std::string> Converter<std::string>::operator()(const Convertible& value, Error& error) const { + optional<std::string> converted = toString(value); + if (!converted) { + error = { "value must be a string" }; + return {}; + } + return *converted; +} + +optional<Color> Converter<Color>::operator()(const Convertible& value, Error& error) const { + optional<std::string> string = toString(value); + if (!string) { + error = { "value must be a string" }; + return {}; + } + + optional<Color> color = Color::parse(*string); + if (!color) { + error = { "value must be a valid color" }; + return {}; + } + + return *color; +} + +optional<std::vector<float>> Converter<std::vector<float>>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector<float> result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional<float> number = toNumber(arrayMember(value, i)); + if (!number) { + error = { "value must be an array of numbers" }; + return {}; + } + result.push_back(*number); + } + + return result; +} + +optional<std::vector<std::string>> Converter<std::vector<std::string>>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector<std::string> result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional<std::string> string = toString(arrayMember(value, i)); + if (!string) { + error = { "value must be an array of strings" }; + return {}; + } + result.push_back(*string); + } + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/coordinate.cpp b/src/mbgl/style/conversion/coordinate.cpp new file mode 100644 index 0000000000..9b2be3381e --- /dev/null +++ b/src/mbgl/style/conversion/coordinate.cpp @@ -0,0 +1,29 @@ +#include <mbgl/style/conversion/coordinate.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<LatLng> Converter<LatLng>::operator() (const Convertible& value, Error& error) const { + if (!isArray(value) || arrayLength(value) < 2 ) { + error = { "coordinate array must contain numeric longitude and latitude values" }; + return {}; + } + //Style spec uses GeoJSON convention for specifying coordinates + optional<double> latitude = toDouble(arrayMember(value, 1)); + optional<double> longitude = toDouble(arrayMember(value, 0)); + + if (!latitude || !longitude) { + error = { "coordinate array must contain numeric longitude and latitude values" }; + return {}; + } + if (*latitude < -90 || *latitude > 90 ){ + error = { "coordinate latitude must be between -90 and 90" }; + return {}; + } + return LatLng(*latitude, *longitude); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp new file mode 100644 index 0000000000..3c941945fd --- /dev/null +++ b/src/mbgl/style/conversion/filter.cpp @@ -0,0 +1,304 @@ +#include <mbgl/style/conversion/filter.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/type.hpp> +#include <mbgl/style/expression/parsing_context.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +using GeometryValue = mapbox::geometry::value; + +// This is a port from https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/feature_filter/index.js +static bool isExpressionFilter(const Convertible& filter) { + if (!isArray(filter) || arrayLength(filter) == 0) { + return false; + } + + optional<std::string> op = toString(arrayMember(filter, 0)); + + if (!op) { + return false; + + } else if (*op == "has") { + if (arrayLength(filter) < 2) return false; + optional<std::string> operand = toString(arrayMember(filter, 1)); + return operand && *operand != "$id" && *operand != "$type"; + + } else if (*op == "in" || *op == "!in" || *op == "!has" || *op == "none") { + return false; + + } else if (*op == "==" || *op == "!=" || *op == ">" || *op == ">=" || *op == "<" || *op == "<=") { + return arrayLength(filter) == 3 && (isArray(arrayMember(filter, 1)) || isArray(arrayMember(filter, 2))); + + } else if (*op == "any" || *op == "all") { + for (std::size_t i = 1; i < arrayLength(filter); i++) { + Convertible f = arrayMember(filter, i); + if (!isExpressionFilter(f) && !toBool(f)) { + return false; + } + } + return true; + + } else { + return true; + } +} + +static optional<GeometryValue> normalizeValue(const optional<GeometryValue>& value, Error& error) { + if (!value) { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + } else { + return *value; + } +} + +static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) { + optional<std::string> type = toString(value); + if (!type) { + error = { "value for $type filter must be a string" }; + return {}; + } else if (*type == "Point") { + return FeatureType::Point; + } else if (*type == "LineString") { + return FeatureType::LineString; + } else if (*type == "Polygon") { + return FeatureType::Polygon; + } else { + error = { "value for $type filter must be Point, LineString, or Polygon" }; + return {}; + } +} + +static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) { + optional<GeometryValue> identifier = toValue(value); + if (!identifier) { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + } else { + return (*identifier).match( + [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; }, + [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; }, + [] ( double t) -> optional<FeatureIdentifier> { return { t }; }, + [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; }, + [&] (const auto&) -> optional<FeatureIdentifier> { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + }); + } +} + +template <class FilterType, class IdentifierFilterType> +optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must have 2 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$id") { + return { IdentifierFilterType {} }; + } else { + return { FilterType { *key } }; + } +} + +template <class FilterType, class TypeFilterType, class IdentifierFilterType> +optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { TypeFilterType { *filterValue } }; + + } else if (*key == "$id") { + optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { IdentifierFilterType { *filterValue } }; + + } else { + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; + } +} + +template <class FilterType> +optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; +} + +template <class FilterType, class TypeFilterType, class IdentifierFilterType> +optional<Filter> convertSetFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must at least 2 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + std::vector<FeatureType> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { TypeFilterType { std::move(values) } }; + + } else if (*key == "$id") { + std::vector<FeatureIdentifier> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { IdentifierFilterType { std::move(values) } }; + + } else { + std::vector<GeometryValue> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, i)), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { FilterType { *key, std::move(values) } }; + } +} + +template <class FilterType> +optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) { + std::vector<Filter> filters; + for (std::size_t i = 1; i < arrayLength(value); ++i) { + optional<Filter> element = convert<Filter>(arrayMember(value, i), error); + if (!element) { + return {}; + } + filters.push_back(*element); + } + + return { FilterType { std::move(filters) } }; +} + +optional<Filter> convertExpressionFilter(const Convertible& value, Error& error) { + expression::ParsingContext ctx(expression::type::Boolean); + expression::ParseResult expression = ctx.parseExpression(value); + if (!expression) { + error = { ctx.getCombinedErrors() }; + return {}; + } + + return { ExpressionFilter { std::move(*expression) } }; +} + +optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const { + if (isExpressionFilter(value)) { + return convertExpressionFilter(value, error); + } + + if (!isArray(value)) { + error = { "filter expression must be an array" }; + return {}; + } + + if (arrayLength(value) < 1) { + error = { "filter expression must have at least 1 element" }; + return {}; + } + + optional<std::string> op = toString(arrayMember(value, 0)); + if (!op) { + error = { "filter operator must be a string" }; + return {}; + } + + if (*op == "==") { + return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error); + } else if (*op == "!=") { + return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error); + } else if (*op == ">") { + return convertBinaryFilter<GreaterThanFilter>(value, error); + } else if (*op == ">=") { + return convertBinaryFilter<GreaterThanEqualsFilter>(value, error); + } else if (*op == "<") { + return convertBinaryFilter<LessThanFilter>(value, error); + } else if (*op == "<=") { + return convertBinaryFilter<LessThanEqualsFilter>(value, error); + } else if (*op == "in") { + return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error); + } else if (*op == "!in") { + return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error); + } else if (*op == "all") { + return convertCompoundFilter<AllFilter>(value, error); + } else if (*op == "any") { + return convertCompoundFilter<AnyFilter>(value, error); + } else if (*op == "none") { + return convertCompoundFilter<NoneFilter>(value, error); + } else if (*op == "has") { + return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error); + } else if (*op == "!has") { + return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error); + } + + error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" }; + return {}; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/geojson.cpp b/src/mbgl/style/conversion/geojson.cpp index 8103e9014a..e39a1a80eb 100644 --- a/src/mbgl/style/conversion/geojson.cpp +++ b/src/mbgl/style/conversion/geojson.cpp @@ -1,26 +1,16 @@ #include <mbgl/style/conversion/geojson.hpp> #include <mbgl/style/conversion/json.hpp> -#include <mbgl/util/rapidjson.hpp> - -#include <mapbox/geojson.hpp> -#include <mapbox/geojson/rapidjson.hpp> namespace mbgl { namespace style { namespace conversion { -optional<GeoJSON> Converter<GeoJSON>::operator()(const std::string& value, Error& error) const { - return convertJSON<GeoJSON>(value, error); +optional<GeoJSON> Converter<GeoJSON>::operator()(const Convertible& value, Error& error) const { + return toGeoJSON(value, error); } -template <> -optional<GeoJSON> Converter<GeoJSON>::operator()(const JSValue& value, Error& error) const { - try { - return mapbox::geojson::convert(value); - } catch (const std::exception& ex) { - error = { ex.what() }; - return {}; - } +optional<GeoJSON> parseGeoJSON(const std::string& value, Error& error) { + return convertJSON<GeoJSON>(value, error); } } // namespace conversion diff --git a/src/mbgl/style/conversion/geojson_options.cpp b/src/mbgl/style/conversion/geojson_options.cpp new file mode 100644 index 0000000000..a2c5ed8816 --- /dev/null +++ b/src/mbgl/style/conversion/geojson_options.cpp @@ -0,0 +1,85 @@ +#include <mbgl/style/conversion/geojson_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<GeoJSONOptions> Converter<GeoJSONOptions>::operator()(const Convertible& value, Error& error) const { + GeoJSONOptions options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.minzoom = static_cast<uint8_t>(*toNumber(*minzoomValue)); + } else { + error = { "GeoJSON source minzoom value must be a number" }; + return {}; + } + } + + const auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + if (toNumber(*maxzoomValue)) { + options.maxzoom = static_cast<uint8_t>(*toNumber(*maxzoomValue)); + } else { + error = { "GeoJSON source maxzoom value must be a number" }; + return {}; + } + } + + const auto bufferValue = objectMember(value, "buffer"); + if (bufferValue) { + if (toNumber(*bufferValue)) { + options.buffer = static_cast<uint16_t>(*toNumber(*bufferValue)); + } else { + error = { "GeoJSON source buffer value must be a number" }; + return {}; + } + } + + const auto toleranceValue = objectMember(value, "tolerance"); + if (toleranceValue) { + if (toNumber(*toleranceValue)) { + options.tolerance = static_cast<double>(*toNumber(*toleranceValue)); + } else { + error = { "GeoJSON source tolerance value must be a number" }; + return {}; + } + } + + const auto clusterValue = objectMember(value, "cluster"); + if (clusterValue) { + if (toBool(*clusterValue)) { + options.cluster = *toBool(*clusterValue); + } else { + error = { "GeoJSON source cluster value must be a boolean" }; + return {}; + } + } + + const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom"); + if (clusterMaxZoomValue) { + if (toNumber(*clusterMaxZoomValue)) { + options.clusterMaxZoom = static_cast<uint8_t>(*toNumber(*clusterMaxZoomValue)); + } else { + error = { "GeoJSON source clusterMaxZoom value must be a number" }; + return {}; + } + } + + const auto clusterRadiusValue = objectMember(value, "clusterRadius"); + if (clusterRadiusValue) { + if (toNumber(*clusterRadiusValue)) { + options.clusterRadius = static_cast<double>(*toNumber(*clusterRadiusValue)); + } else { + error = { "GeoJSON source clusterRadius value must be a number" }; + return {}; + } + } + + return { options }; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/get_json_type.cpp b/src/mbgl/style/conversion/get_json_type.cpp new file mode 100644 index 0000000000..cd3b4608b1 --- /dev/null +++ b/src/mbgl/style/conversion/get_json_type.cpp @@ -0,0 +1,34 @@ +#include <mbgl/style/conversion/get_json_type.hpp> +#include <mbgl/util/feature.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +std::string getJSONType(const Convertible& value) { + if (isUndefined(value)) { + return "null"; + } + if (isArray(value)) { + return "array"; + } + if (isObject(value)) { + return "object"; + } + optional<mbgl::Value> v = toValue(value); + + // Since we've already checked the non-atomic types above, value must then + // be a string, number, or boolean -- thus, assume that the toValue() + // conversion succeeds. + assert(v); + + return v->match( + [&] (const std::string&) { return "string"; }, + [&] (bool) { return "boolean"; }, + [&] (auto) { return "number"; } + ); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/json.hpp b/src/mbgl/style/conversion/json.hpp index 0817ac09df..7dd2378f6b 100644 --- a/src/mbgl/style/conversion/json.hpp +++ b/src/mbgl/style/conversion/json.hpp @@ -20,7 +20,7 @@ optional<T> convertJSON(const std::string& json, Error& error, Args&&...args) { return {}; } - return convert<T, JSValue>(document, error, std::forward<Args>(args)...); + return convert<T>(document, error, std::forward<Args>(args)...); } } // namespace conversion diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp new file mode 100644 index 0000000000..19472bc8d6 --- /dev/null +++ b/src/mbgl/style/conversion/layer.cpp @@ -0,0 +1,232 @@ +#include <mbgl/style/conversion/layer.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/filter.hpp> +#include <mbgl/style/conversion/make_property_setters.hpp> +#include <mbgl/style/layers/background_layer.hpp> +#include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/fill_layer.hpp> +#include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value) { + static const auto setters = makeLayoutPropertySetters(); + auto it = setters.find(name); + if (it == setters.end()) { + return Error { "property not found" }; + } + return it->second(layer, value); +} + +optional<Error> setPaintProperty(Layer& layer, const std::string& name, const Convertible& value) { + static const auto setters = makePaintPropertySetters(); + auto it = setters.find(name); + if (it == setters.end()) { + return Error { "property not found" }; + } + return it->second(layer, value); +} + +optional<Error> setPaintProperties(Layer& layer, const Convertible& value) { + auto paintValue = objectMember(value, "paint"); + if (!paintValue) { + return {}; + } + if (!isObject(*paintValue)) { + return { { "paint must be an object" } }; + } + return eachMember(*paintValue, [&] (const std::string& k, const Convertible& v) { + return setPaintProperty(layer, k, v); + }); +} + +template <class LayerType> +optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source); + + auto sourceLayerValue = objectMember(value, "source-layer"); + if (sourceLayerValue) { + optional<std::string> sourceLayer = toString(*sourceLayerValue); + if (!sourceLayer) { + error = { "layer source-layer must be a string" }; + return {}; + } + layer->setSourceLayer(*sourceLayer); + } + + auto filterValue = objectMember(value, "filter"); + if (filterValue) { + optional<Filter> filter = convert<Filter>(*filterValue, error); + if (!filter) { + return {}; + } + layer->setFilter(*filter); + } + + return { std::move(layer) }; +} + +static optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + return { std::make_unique<RasterLayer>(id, *source) }; +} + +static optional<std::unique_ptr<Layer>> convertHillshadeLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + return { std::make_unique<HillshadeLayer>(id, *source) }; +} + + +static optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) { + return { std::make_unique<BackgroundLayer>(id) }; +} + +optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "layer must be an object" }; + return {}; + } + + auto idValue = objectMember(value, "id"); + if (!idValue) { + error = { "layer must have an id" }; + return {}; + } + + optional<std::string> id = toString(*idValue); + if (!id) { + error = { "layer id must be a string" }; + return {}; + } + + auto typeValue = objectMember(value, "type"); + if (!typeValue) { + error = { "layer must have a type" }; + return {}; + } + + optional<std::string> type = toString(*typeValue); + if (!type) { + error = { "layer type must be a string" }; + return {}; + } + + optional<std::unique_ptr<Layer>> converted; + + if (*type == "fill") { + converted = convertVectorLayer<FillLayer>(*id, value, error); + } else if (*type == "fill-extrusion") { + converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error); + } else if (*type == "line") { + converted = convertVectorLayer<LineLayer>(*id, value, error); + } else if (*type == "circle") { + converted = convertVectorLayer<CircleLayer>(*id, value, error); + } else if (*type == "symbol") { + converted = convertVectorLayer<SymbolLayer>(*id, value, error); + } else if (*type == "raster") { + converted = convertRasterLayer(*id, value, error); + } else if (*type == "heatmap") { + converted = convertVectorLayer<HeatmapLayer>(*id, value, error); + } else if (*type == "hillshade") { + converted = convertHillshadeLayer(*id, value, error); + } else if (*type == "background") { + converted = convertBackgroundLayer(*id, value, error); + } else { + error = { "invalid layer type" }; + return {}; + } + + if (!converted) { + return converted; + } + + std::unique_ptr<Layer> layer = std::move(*converted); + + auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + optional<float> minzoom = toNumber(*minzoomValue); + if (!minzoom) { + error = { "minzoom must be numeric" }; + return {}; + } + layer->setMinZoom(*minzoom); + } + + auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + optional<float> maxzoom = toNumber(*maxzoomValue); + if (!maxzoom) { + error = { "maxzoom must be numeric" }; + return {}; + } + layer->setMaxZoom(*maxzoom); + } + + auto layoutValue = objectMember(value, "layout"); + if (layoutValue) { + if (!isObject(*layoutValue)) { + error = { "layout must be an object" }; + return {}; + } + optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) { + return setLayoutProperty(*layer, k, v); + }); + if (error_) { + error = *error_; + return {}; + } + } + + optional<Error> error_ = setPaintProperties(*layer, value); + if (error_) { + error = *error_; + return {}; + } + + return std::move(layer); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/light.cpp b/src/mbgl/style/conversion/light.cpp new file mode 100644 index 0000000000..f521f74386 --- /dev/null +++ b/src/mbgl/style/conversion/light.cpp @@ -0,0 +1,115 @@ +#include <mbgl/style/conversion/light.hpp> +#include <mbgl/style/conversion/position.hpp> +#include <mbgl/style/conversion/property_value.hpp> +#include <mbgl/style/conversion/transition_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Light> Converter<Light>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "light must be an object" }; + return {}; + } + + Light light; + + const auto anchor = objectMember(value, "anchor"); + if (anchor) { + optional<PropertyValue<LightAnchorType>> convertedAnchor = + convert<PropertyValue<LightAnchorType>>(*anchor, error); + + if (convertedAnchor) { + light.setAnchor(*convertedAnchor); + } else { + return {}; + } + } + + const auto anchorTransition = objectMember(value, "anchor-transition"); + if (anchorTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*anchorTransition, error); + if (transition) { + light.setAnchorTransition(*transition); + } else { + return {}; + } + } + + const auto color = objectMember(value, "color"); + if (color) { + optional<PropertyValue<Color>> convertedColor = + convert<PropertyValue<Color>>(*color, error); + + if (convertedColor) { + light.setColor(*convertedColor); + } else { + return {}; + } + } + + const auto colorTransition = objectMember(value, "color-transition"); + if (colorTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*colorTransition, error); + if (transition) { + light.setColorTransition(*transition); + } else { + return {}; + } + } + + const auto position = objectMember(value, "position"); + if (position) { + optional<PropertyValue<Position>> convertedPosition = + convert<PropertyValue<Position>>(*position, error); + + if (convertedPosition) { + light.setPosition(*convertedPosition); + } else { + return {}; + } + } + + const auto positionTransition = objectMember(value, "position-transition"); + if (positionTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*positionTransition, error); + if (transition) { + light.setPositionTransition(*transition); + } else { + return {}; + } + } + + const auto intensity = objectMember(value, "intensity"); + if (intensity) { + optional<PropertyValue<float>> convertedIntensity = + convert<PropertyValue<float>>(*intensity, error); + + if (convertedIntensity) { + light.setIntensity(*convertedIntensity); + } else { + return {}; + } + } + + const auto intensityTransition = objectMember(value, "intensity-transition"); + if (intensityTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*intensityTransition, error); + if (transition) { + light.setIntensityTransition(*transition); + } else { + return {}; + } + } + + return { std::move(light) }; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp new file mode 100644 index 0000000000..25c8fdb1ca --- /dev/null +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -0,0 +1,237 @@ +#pragma once + +// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`. + +#include <mbgl/style/conversion/property_setter.hpp> + +#include <mbgl/style/layers/fill_layer.hpp> +#include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> +#include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/background_layer.hpp> + +#include <unordered_map> + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["visibility"] = &setVisibility; + + + result["line-cap"] = &setProperty<LineLayer, PropertyValue<LineCapType>, &LineLayer::setLineCap>; + result["line-join"] = &setProperty<LineLayer, DataDrivenPropertyValue<LineJoinType>, &LineLayer::setLineJoin>; + result["line-miter-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineMiterLimit>; + result["line-round-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineRoundLimit>; + + result["symbol-placement"] = &setProperty<SymbolLayer, PropertyValue<SymbolPlacementType>, &SymbolLayer::setSymbolPlacement>; + result["symbol-spacing"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setSymbolSpacing>; + result["symbol-avoid-edges"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setSymbolAvoidEdges>; + result["icon-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconAllowOverlap>; + result["icon-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconIgnorePlacement>; + result["icon-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconOptional>; + result["icon-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconRotationAlignment>; + result["icon-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconSize>; + result["icon-text-fit"] = &setProperty<SymbolLayer, PropertyValue<IconTextFitType>, &SymbolLayer::setIconTextFit>; + result["icon-text-fit-padding"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 4>>, &SymbolLayer::setIconTextFitPadding>; + result["icon-image"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setIconImage>; + result["icon-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconRotate>; + result["icon-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>; + result["icon-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>; + result["icon-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>; + result["icon-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>; + result["icon-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>; + result["text-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>; + result["text-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>; + result["text-field"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>; + result["text-font"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>; + result["text-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>; + result["text-max-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>; + result["text-line-height"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>; + result["text-letter-spacing"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>; + result["text-justify"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>; + result["text-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>; + result["text-max-angle"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>; + result["text-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>; + result["text-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>; + result["text-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextKeepUpright>; + result["text-transform"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextTransformType>, &SymbolLayer::setTextTransform>; + result["text-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setTextOffset>; + result["text-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextAllowOverlap>; + result["text-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextIgnorePlacement>; + result["text-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextOptional>; + + + + + + + + return result; +} + +inline auto makePaintPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["fill-antialias"] = &setProperty<FillLayer, PropertyValue<bool>, &FillLayer::setFillAntialias>; + result["fill-antialias-transition"] = &setTransition<FillLayer, &FillLayer::setFillAntialiasTransition>; + result["fill-opacity"] = &setProperty<FillLayer, DataDrivenPropertyValue<float>, &FillLayer::setFillOpacity>; + result["fill-opacity-transition"] = &setTransition<FillLayer, &FillLayer::setFillOpacityTransition>; + result["fill-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillColor>; + result["fill-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillColorTransition>; + result["fill-outline-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillOutlineColor>; + result["fill-outline-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillOutlineColorTransition>; + result["fill-translate"] = &setProperty<FillLayer, PropertyValue<std::array<float, 2>>, &FillLayer::setFillTranslate>; + result["fill-translate-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateTransition>; + result["fill-translate-anchor"] = &setProperty<FillLayer, PropertyValue<TranslateAnchorType>, &FillLayer::setFillTranslateAnchor>; + result["fill-translate-anchor-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateAnchorTransition>; + result["fill-pattern"] = &setProperty<FillLayer, PropertyValue<std::string>, &FillLayer::setFillPattern>; + result["fill-pattern-transition"] = &setTransition<FillLayer, &FillLayer::setFillPatternTransition>; + + result["line-opacity"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOpacity>; + result["line-opacity-transition"] = &setTransition<LineLayer, &LineLayer::setLineOpacityTransition>; + result["line-color"] = &setProperty<LineLayer, DataDrivenPropertyValue<Color>, &LineLayer::setLineColor>; + result["line-color-transition"] = &setTransition<LineLayer, &LineLayer::setLineColorTransition>; + result["line-translate"] = &setProperty<LineLayer, PropertyValue<std::array<float, 2>>, &LineLayer::setLineTranslate>; + result["line-translate-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateTransition>; + result["line-translate-anchor"] = &setProperty<LineLayer, PropertyValue<TranslateAnchorType>, &LineLayer::setLineTranslateAnchor>; + result["line-translate-anchor-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateAnchorTransition>; + result["line-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineWidth>; + result["line-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineWidthTransition>; + result["line-gap-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineGapWidth>; + result["line-gap-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineGapWidthTransition>; + result["line-offset"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOffset>; + result["line-offset-transition"] = &setTransition<LineLayer, &LineLayer::setLineOffsetTransition>; + result["line-blur"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineBlur>; + result["line-blur-transition"] = &setTransition<LineLayer, &LineLayer::setLineBlurTransition>; + result["line-dasharray"] = &setProperty<LineLayer, PropertyValue<std::vector<float>>, &LineLayer::setLineDasharray>; + result["line-dasharray-transition"] = &setTransition<LineLayer, &LineLayer::setLineDasharrayTransition>; + result["line-pattern"] = &setProperty<LineLayer, PropertyValue<std::string>, &LineLayer::setLinePattern>; + result["line-pattern-transition"] = &setTransition<LineLayer, &LineLayer::setLinePatternTransition>; + + result["icon-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconOpacity>; + result["icon-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconOpacityTransition>; + result["icon-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconColor>; + result["icon-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconColorTransition>; + result["icon-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconHaloColor>; + result["icon-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloColorTransition>; + result["icon-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloWidth>; + result["icon-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloWidthTransition>; + result["icon-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloBlur>; + result["icon-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloBlurTransition>; + result["icon-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setIconTranslate>; + result["icon-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateTransition>; + result["icon-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setIconTranslateAnchor>; + result["icon-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateAnchorTransition>; + result["text-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextOpacity>; + result["text-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextOpacityTransition>; + result["text-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextColor>; + result["text-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextColorTransition>; + result["text-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextHaloColor>; + result["text-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloColorTransition>; + result["text-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloWidth>; + result["text-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloWidthTransition>; + result["text-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloBlur>; + result["text-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloBlurTransition>; + result["text-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setTextTranslate>; + result["text-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateTransition>; + result["text-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setTextTranslateAnchor>; + result["text-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateAnchorTransition>; + + result["circle-radius"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleRadius>; + result["circle-radius-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleRadiusTransition>; + result["circle-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleColor>; + result["circle-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleColorTransition>; + result["circle-blur"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleBlur>; + result["circle-blur-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleBlurTransition>; + result["circle-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleOpacity>; + result["circle-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleOpacityTransition>; + result["circle-translate"] = &setProperty<CircleLayer, PropertyValue<std::array<float, 2>>, &CircleLayer::setCircleTranslate>; + result["circle-translate-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateTransition>; + result["circle-translate-anchor"] = &setProperty<CircleLayer, PropertyValue<TranslateAnchorType>, &CircleLayer::setCircleTranslateAnchor>; + result["circle-translate-anchor-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateAnchorTransition>; + result["circle-pitch-scale"] = &setProperty<CircleLayer, PropertyValue<CirclePitchScaleType>, &CircleLayer::setCirclePitchScale>; + result["circle-pitch-scale-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchScaleTransition>; + result["circle-pitch-alignment"] = &setProperty<CircleLayer, PropertyValue<AlignmentType>, &CircleLayer::setCirclePitchAlignment>; + result["circle-pitch-alignment-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchAlignmentTransition>; + result["circle-stroke-width"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeWidth>; + result["circle-stroke-width-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeWidthTransition>; + result["circle-stroke-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleStrokeColor>; + result["circle-stroke-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeColorTransition>; + result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>; + result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>; + + result["heatmap-radius"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapRadius>; + result["heatmap-radius-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapRadiusTransition>; + result["heatmap-weight"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapWeight>; + result["heatmap-weight-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapWeightTransition>; + result["heatmap-intensity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapIntensity>; + result["heatmap-intensity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapIntensityTransition>; + result["heatmap-color"] = &setProperty<HeatmapLayer, HeatmapColorPropertyValue, &HeatmapLayer::setHeatmapColor>; + result["heatmap-color-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapColorTransition>; + result["heatmap-opacity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapOpacity>; + result["heatmap-opacity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapOpacityTransition>; + + result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>; + result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>; + result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>; + result["fill-extrusion-color-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionColorTransition>; + result["fill-extrusion-translate"] = &setProperty<FillExtrusionLayer, PropertyValue<std::array<float, 2>>, &FillExtrusionLayer::setFillExtrusionTranslate>; + result["fill-extrusion-translate-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateTransition>; + result["fill-extrusion-translate-anchor"] = &setProperty<FillExtrusionLayer, PropertyValue<TranslateAnchorType>, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>; + result["fill-extrusion-translate-anchor-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateAnchorTransition>; + result["fill-extrusion-pattern"] = &setProperty<FillExtrusionLayer, PropertyValue<std::string>, &FillExtrusionLayer::setFillExtrusionPattern>; + result["fill-extrusion-pattern-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionPatternTransition>; + result["fill-extrusion-height"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionHeight>; + result["fill-extrusion-height-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionHeightTransition>; + result["fill-extrusion-base"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionBase>; + result["fill-extrusion-base-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionBaseTransition>; + + result["raster-opacity"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterOpacity>; + result["raster-opacity-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterOpacityTransition>; + result["raster-hue-rotate"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterHueRotate>; + result["raster-hue-rotate-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterHueRotateTransition>; + result["raster-brightness-min"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMin>; + result["raster-brightness-min-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMinTransition>; + result["raster-brightness-max"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMax>; + result["raster-brightness-max-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMaxTransition>; + result["raster-saturation"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterSaturation>; + result["raster-saturation-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterSaturationTransition>; + result["raster-contrast"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterContrast>; + result["raster-contrast-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterContrastTransition>; + result["raster-fade-duration"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>; + result["raster-fade-duration-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterFadeDurationTransition>; + + result["hillshade-illumination-direction"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeIlluminationDirection>; + result["hillshade-illumination-direction-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationDirectionTransition>; + result["hillshade-illumination-anchor"] = &setProperty<HillshadeLayer, PropertyValue<HillshadeIlluminationAnchorType>, &HillshadeLayer::setHillshadeIlluminationAnchor>; + result["hillshade-illumination-anchor-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationAnchorTransition>; + result["hillshade-exaggeration"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeExaggeration>; + result["hillshade-exaggeration-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeExaggerationTransition>; + result["hillshade-shadow-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeShadowColor>; + result["hillshade-shadow-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeShadowColorTransition>; + result["hillshade-highlight-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeHighlightColor>; + result["hillshade-highlight-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeHighlightColorTransition>; + result["hillshade-accent-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeAccentColor>; + result["hillshade-accent-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeAccentColorTransition>; + + result["background-color"] = &setProperty<BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>; + result["background-color-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>; + result["background-pattern"] = &setProperty<BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>; + result["background-pattern-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundPatternTransition>; + result["background-opacity"] = &setProperty<BackgroundLayer, PropertyValue<float>, &BackgroundLayer::setBackgroundOpacity>; + result["background-opacity-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundOpacityTransition>; + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/make_property_setters.hpp.ejs b/src/mbgl/style/conversion/make_property_setters.hpp.ejs new file mode 100644 index 0000000000..2975cb19f2 --- /dev/null +++ b/src/mbgl/style/conversion/make_property_setters.hpp.ejs @@ -0,0 +1,46 @@ +#pragma once + +// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`. + +#include <mbgl/style/conversion/property_setter.hpp> + +<% for (const layer of locals.layers) { -%> +#include <mbgl/style/layers/<%- layer.type.replace('-', '_') %>_layer.hpp> +<% } -%> + +#include <unordered_map> + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["visibility"] = &setVisibility; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.layoutProperties) { -%> + result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; +<% } -%> + +<% } -%> + return result; +} + +inline auto makePaintPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.paintProperties) { -%> + result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; + result["<%- property.name %>-transition"] = &setTransition<<%- camelize(layer.type) %>Layer, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>Transition>; +<% } -%> + +<% } -%> + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/position.cpp b/src/mbgl/style/conversion/position.cpp new file mode 100644 index 0000000000..702d250dbf --- /dev/null +++ b/src/mbgl/style/conversion/position.cpp @@ -0,0 +1,22 @@ +#include <mbgl/style/conversion/position.hpp> +#include <mbgl/style/conversion/constant.hpp> + +#include <array> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Position> Converter<Position>::operator()(const Convertible& value, Error& error) const { + optional<std::array<float, 3>> spherical = convert<std::array<float, 3>>(value, error); + + if (!spherical) { + return {}; + } + + return Position(*spherical); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp new file mode 100644 index 0000000000..e3716a18dc --- /dev/null +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include <mbgl/style/layer.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/property_value.hpp> +#include <mbgl/style/conversion/data_driven_property_value.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> +#include <mbgl/style/conversion/transition_options.hpp> + +#include <string> + +namespace mbgl { +namespace style { +namespace conversion { + +using PropertySetter = optional<Error> (*) (Layer&, const Convertible&); + +template <class L, class PropertyValue, void (L::*setter)(PropertyValue)> +optional<Error> setProperty(Layer& layer, const Convertible& value) { + auto* typedLayer = layer.as<L>(); + if (!typedLayer) { + return Error { "layer doesn't support this property" }; + } + + Error error; + optional<PropertyValue> typedValue = convert<PropertyValue>(value, error); + if (!typedValue) { + return error; + } + + (typedLayer->*setter)(*typedValue); + return {}; +} + +template <class L, void (L::*setter)(const TransitionOptions&)> +optional<Error> setTransition(Layer& layer, const Convertible& value) { + auto* typedLayer = layer.as<L>(); + if (!typedLayer) { + return Error { "layer doesn't support this property" }; + } + + Error error; + optional<TransitionOptions> transition = convert<TransitionOptions>(value, error); + if (!transition) { + return error; + } + + (typedLayer->*setter)(*transition); + return {}; +} + +inline optional<Error> setVisibility(Layer& layer, const Convertible& value) { + if (isUndefined(value)) { + layer.setVisibility(VisibilityType::Visible); + return {}; + } + + Error error; + optional<VisibilityType> visibility = convert<VisibilityType>(value, error); + if (!visibility) { + return error; + } + + layer.setVisibility(*visibility); + return {}; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp new file mode 100644 index 0000000000..670f50c041 --- /dev/null +++ b/src/mbgl/style/conversion/source.cpp @@ -0,0 +1,200 @@ +#include <mbgl/style/conversion/source.hpp> +#include <mbgl/style/conversion/coordinate.hpp> +#include <mbgl/style/conversion/geojson.hpp> +#include <mbgl/style/conversion/geojson_options.hpp> +#include <mbgl/style/conversion/tileset.hpp> +#include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/sources/raster_source.hpp> +#include <mbgl/style/sources/raster_dem_source.hpp> +#include <mbgl/style/sources/vector_source.hpp> +#include <mbgl/style/sources/image_source.hpp> +#include <mbgl/util/geo.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +// A tile source can either specify a URL to TileJSON, or inline TileJSON. +static optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) { + auto urlVal = objectMember(value, "url"); + if (!urlVal) { + optional<Tileset> tileset = convert<Tileset>(value, error); + if (!tileset) { + return {}; + } + return { *tileset }; + } + + optional<std::string> url = toString(*urlVal); + if (!url) { + error = { "source url must be a string" }; + return {}; + } + + return { *url }; +} + +static optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + uint16_t tileSize = util::tileSize; + auto tileSizeValue = objectMember(value, "tileSize"); + if (tileSizeValue) { + optional<float> size = toNumber(*tileSizeValue); + if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) { + error = { "invalid tileSize" }; + return {}; + } + tileSize = *size; + } + + return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) }; +} + +static optional<std::unique_ptr<Source>> convertRasterDEMSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + uint16_t tileSize = util::tileSize; + auto tileSizeValue = objectMember(value, "tileSize"); + if (tileSizeValue) { + optional<float> size = toNumber(*tileSizeValue); + if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) { + error = { "invalid tileSize" }; + return {}; + } + tileSize = *size; + } + + return { std::make_unique<RasterDEMSource>(id, std::move(*urlOrTileset), tileSize) }; +} + +static optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + return { std::make_unique<VectorSource>(id, std::move(*urlOrTileset)) }; +} + +static optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id, + const Convertible& value, + Error& error) { + auto dataValue = objectMember(value, "data"); + if (!dataValue) { + error = { "GeoJSON source must have a data value" }; + return {}; + } + + optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error); + if (!options) { + return {}; + } + + auto result = std::make_unique<GeoJSONSource>(id, *options); + + if (isObject(*dataValue)) { + optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error); + if (!geoJSON) { + return {}; + } + result->setGeoJSON(std::move(*geoJSON)); + } else if (toString(*dataValue)) { + result->setURL(*toString(*dataValue)); + } else { + error = { "GeoJSON data must be a URL or an object" }; + return {}; + } + + return { std::move(result) }; +} + +static optional<std::unique_ptr<Source>> convertImageSource(const std::string& id, + const Convertible& value, + Error& error) { + auto urlValue = objectMember(value, "url"); + if (!urlValue) { + error = { "Image source must have a url value" }; + return {}; + } + + auto urlString = toString(*urlValue); + if (!urlString) { + error = { "Image url must be a URL string" }; + return {}; + } + + auto coordinatesValue = objectMember(value, "coordinates"); + if (!coordinatesValue) { + error = { "Image source must have a coordinates values" }; + return {}; + } + + if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) { + error = { "Image coordinates must be an array of four longitude latitude pairs" }; + return {}; + } + + std::array<LatLng, 4> coordinates; + for (std::size_t i=0; i < 4; i++) { + auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error); + if (!latLng) { + return {}; + } + coordinates[i] = *latLng; + } + auto result = std::make_unique<ImageSource>(id, coordinates); + result->setURL(*urlString); + + return { std::move(result) }; +} + +optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value, Error& error, const std::string& id) const { + if (!isObject(value)) { + error = { "source must be an object" }; + return {}; + } + + auto typeValue = objectMember(value, "type"); + if (!typeValue) { + error = { "source must have a type" }; + return {}; + } + + optional<std::string> type = toString(*typeValue); + if (!type) { + error = { "source type must be a string" }; + return {}; + } + const std::string tname = *type; + if (*type == "raster") { + return convertRasterSource(id, value, error); + } else if (*type == "raster-dem") { + return convertRasterDEMSource(id, value, error); + } else if (*type == "vector") { + return convertVectorSource(id, value, error); + } else if (*type == "geojson") { + return convertGeoJSONSource(id, value, error); + } else if (*type == "image") { + return convertImageSource(id, value, error); + } else { + error = { "invalid source type" }; + return {}; + } +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp index 6ae6fede42..7b7727d7c4 100644 --- a/src/mbgl/style/conversion/stringify.hpp +++ b/src/mbgl/style/conversion/stringify.hpp @@ -225,6 +225,10 @@ public: void operator()(const NotHasIdentifierFilter&) { stringifyUnaryFilter("!has", "$id"); } + + void operator()(const ExpressionFilter& filter) { + stringify(writer, filter.expression->serialize()); + } private: template <class F> @@ -286,138 +290,19 @@ void stringify(Writer& writer, const Undefined&) { writer.Null(); } -template <class Writer> -void stringify(Writer& writer, const CategoricalValue& v) { - CategoricalValue::visit(v, [&] (const auto& v_) { stringify(writer, v_); }); -} - -template <class Writer> -class StringifyStops { -public: - Writer& writer; - - template <class T> - void operator()(const ExponentialStops<T>& f) { - writer.Key("type"); - writer.String("exponential"); - writer.Key("base"); - writer.Double(f.base); - writer.Key("stops"); - stringifyStops(f.stops); - } - - template <class T> - void operator()(const IntervalStops<T>& f) { - writer.Key("type"); - writer.String("interval"); - writer.Key("stops"); - stringifyStops(f.stops); - } - - template <class T> - void operator()(const CategoricalStops<T>& f) { - writer.Key("type"); - writer.String("categorical"); - writer.Key("stops"); - stringifyStops(f.stops); - } - - template <class T> - void operator()(const IdentityStops<T>&) { - writer.Key("type"); - writer.String("identity"); - } - - template <class T> - void operator()(const CompositeExponentialStops<T>& f) { - writer.Key("type"); - writer.String("exponential"); - writer.Key("base"); - writer.Double(f.base); - writer.Key("stops"); - stringifyCompositeStops(f.stops); - } - - template <class T> - void operator()(const CompositeIntervalStops<T>& f) { - writer.Key("type"); - writer.String("interval"); - writer.Key("stops"); - stringifyCompositeStops(f.stops); - } - - template <class T> - void operator()(const CompositeCategoricalStops<T>& f) { - writer.Key("type"); - writer.String("categorical"); - writer.Key("stops"); - stringifyCompositeStops(f.stops); - } - -private: - template <class K, class V> - void stringifyStops(const std::map<K, V>& stops) { - writer.StartArray(); - for (const auto& stop : stops) { - writer.StartArray(); - stringify(writer, stop.first); - stringify(writer, stop.second); - writer.EndArray(); - } - writer.EndArray(); - } - - template <class InnerStops> - void stringifyCompositeStops(const std::map<float, InnerStops>& stops) { - writer.StartArray(); - for (const auto& outer : stops) { - for (const auto& inner : outer.second) { - writer.StartArray(); - writer.StartObject(); - writer.Key("zoom"); - writer.Double(outer.first); - writer.Key("value"); - stringify(writer, inner.first); - writer.EndObject(); - stringify(writer, inner.second); - writer.EndArray(); - } - } - writer.EndArray(); - } -}; - template <class Writer, class T> -void stringify(Writer& writer, const CameraFunction<T>& f) { - writer.StartObject(); - CameraFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer }); - writer.EndObject(); +void stringify(Writer& writer, const CameraFunction<T>& fn) { + stringify(writer, fn.getExpression().serialize()); } template <class Writer, class T> -void stringify(Writer& writer, const SourceFunction<T>& f) { - writer.StartObject(); - writer.Key("property"); - writer.String(f.property); - SourceFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer }); - if (f.defaultValue) { - writer.Key("default"); - stringify(writer, *f.defaultValue); - } - writer.EndObject(); +void stringify(Writer& writer, const SourceFunction<T>& fn) { + stringify(writer, fn.getExpression().serialize()); } template <class Writer, class T> -void stringify(Writer& writer, const CompositeFunction<T>& f) { - writer.StartObject(); - writer.Key("property"); - writer.String(f.property); - CompositeFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer }); - if (f.defaultValue) { - writer.Key("default"); - stringify(writer, *f.defaultValue); - } - writer.EndObject(); +void stringify(Writer& writer, const CompositeFunction<T>& fn) { + stringify(writer, fn.getExpression().serialize()); } template <class Writer, class T> diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp index 6e559c0cac..a2c4aa80b3 100644 --- a/src/mbgl/style/conversion/tileset.cpp +++ b/src/mbgl/style/conversion/tileset.cpp @@ -6,7 +6,7 @@ namespace style { namespace conversion { bool validateLatitude(const double lat) { - return lat < 90 && lat > -90; + return lat <= 90 && lat >= -90; } optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const { @@ -40,6 +40,16 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error } } + auto encodingValue = objectMember(value, "encoding"); + if (encodingValue) { + optional<std::string> encoding = toString(*encodingValue); + if (encoding && *encoding == "terrarium") { + result.encoding = Tileset::DEMEncoding::Terrarium; + } else if (encoding && *encoding != "mapbox") { + error = { "invalid raster-dem encoding type - valid types are 'mapbox' and 'terrarium' " }; + } + } + auto minzoomValue = objectMember(value, "minzoom"); if (minzoomValue) { optional<float> minzoom = toNumber(*minzoomValue); @@ -93,6 +103,8 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error error = { "bounds left longitude should be less than right longitude" }; return {}; } + *left = util::max(-180.0, *left); + *right = util::min(180.0, *right); result.bounds = LatLngBounds::hull({ *bottom, *left }, { *top, *right }); } diff --git a/src/mbgl/style/conversion/transition_options.cpp b/src/mbgl/style/conversion/transition_options.cpp new file mode 100644 index 0000000000..8a60c5bfd8 --- /dev/null +++ b/src/mbgl/style/conversion/transition_options.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/conversion/transition_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<TransitionOptions> Converter<TransitionOptions>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "transition must be an object" }; + return {}; + } + + TransitionOptions result; + + auto duration = objectMember(value, "duration"); + if (duration) { + auto number = toNumber(*duration); + if (!number) { + error = { "duration must be a number" }; + return {}; + } + result.duration = { std::chrono::milliseconds(int64_t(*number)) }; + } + + auto delay = objectMember(value, "delay"); + if (delay) { + auto number = toNumber(*delay); + if (!number) { + error = { "delay must be a number" }; + return {}; + } + result.delay = { std::chrono::milliseconds(int64_t(*number)) }; + } + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp new file mode 100644 index 0000000000..1c587302b8 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.cpp @@ -0,0 +1,116 @@ +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/tile/custom_geometry_tile.hpp> +#include <mbgl/util/tile_range.hpp> + +namespace mbgl { +namespace style { + +CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) { + fetchTileFunction = fetchTileFn; + cancelTileFunction = cancelTileFn; +} + +void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef<CustomGeometryTile> tileRef) { + auto cachedTileData = dataCache.find(tileID.canonical); + if (cachedTileData != dataCache.end()) { + tileRef.invoke(&CustomGeometryTile::setTileData, *(cachedTileData->second)); + } + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) { + auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef); + tileCallbackMap.insert({ tileID.canonical, std::vector<OverscaledIDFunctionTuple>(1, tuple) }); + } else { + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + std::get<2>(*iter) = tileRef; + return; + } + } + tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef)); + } + if (cachedTileData == dataCache.end()) { + invokeTileFetch(tileID.canonical); + } +} + +void CustomTileLoader::cancelTile(const OverscaledTileID& tileID) { + if (tileCallbackMap.find(tileID.canonical) != tileCallbackMap.end()) { + invokeTileCancel(tileID.canonical); + } +} + +void CustomTileLoader::removeTile(const OverscaledTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) return; + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + tileCallbacks->second.erase(iter); + invokeTileCancel(tileID.canonical); + break; + } + } + if (tileCallbacks->second.size() == 0) { + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID.canonical); + } +} + +void CustomTileLoader::setTileData(const CanonicalTileID& tileID, const GeoJSON& data) { + + auto iter = tileCallbackMap.find(tileID); + if (iter == tileCallbackMap.end()) return; + auto dataPtr = std::make_unique<mapbox::geojson::geojson>(std::move(data)); + for (auto tuple : iter->second) { + auto actor = std::get<2>(tuple); + actor.invoke(&CustomGeometryTile::setTileData, *dataPtr); + } + dataCache[tileID] = std::move(dataPtr); +} + +void CustomTileLoader::invalidateTile(const CanonicalTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID); + if (tileCallbacks == tileCallbackMap.end()) { return; } + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&CustomGeometryTile::invalidateTileData); + invokeTileCancel(tileID); + } + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID); +} + +void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range<uint8_t> ) { + std::map<uint8_t, util::TileRange> tileRanges; + + for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) { + auto zoom = idtuple->first.z; + auto tileRange = tileRanges.find(zoom); + if(tileRange == tileRanges.end()) { + tileRange = tileRanges.emplace(std::make_pair(zoom, util::TileRange::fromLatLngBounds(bounds, zoom))).first; + } + if (tileRange->second.contains(idtuple->first)) { + for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&CustomGeometryTile::invalidateTileData); + invokeTileCancel(idtuple->first); + dataCache.erase(idtuple->first); + } + idtuple->second.clear(); + } + } +} + +void CustomTileLoader::invokeTileFetch(const CanonicalTileID& tileID) { + if (fetchTileFunction != nullptr) { + fetchTileFunction(tileID); + } +} + +void CustomTileLoader::invokeTileCancel(const CanonicalTileID& tileID) { + if (cancelTileFunction != nullptr) { + cancelTileFunction(tileID); + } +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.hpp b/src/mbgl/style/custom_tile_loader.hpp new file mode 100644 index 0000000000..335d8c6143 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/geojson.hpp> +#include <mbgl/actor/actor_ref.hpp> + +#include <map> + +namespace mbgl { + +class CustomGeometryTile; + +namespace style { + +class CustomTileLoader : private util::noncopyable { +public: + + using OverscaledIDFunctionTuple = std::tuple<uint8_t, int16_t, ActorRef<CustomGeometryTile>>; + + CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn); + + void fetchTile(const OverscaledTileID& tileID, ActorRef<CustomGeometryTile> tileRef); + void cancelTile(const OverscaledTileID& tileID); + + void removeTile(const OverscaledTileID& tileID); + void setTileData(const CanonicalTileID& tileID, const GeoJSON& data); + + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&, Range<uint8_t>); + +private: + void invokeTileFetch(const CanonicalTileID& tileID); + void invokeTileCancel(const CanonicalTileID& tileID); + + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + std::unordered_map<CanonicalTileID, std::vector<OverscaledIDFunctionTuple>> tileCallbackMap; + // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles + std::map<CanonicalTileID, std::unique_ptr<GeoJSON>> dataCache; + +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/array_assertion.cpp b/src/mbgl/style/expression/array_assertion.cpp new file mode 100644 index 0000000000..4049301b0b --- /dev/null +++ b/src/mbgl/style/expression/array_assertion.cpp @@ -0,0 +1,105 @@ +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult ArrayAssertion::evaluate(const EvaluationContext& params) const { + auto result = input->evaluate(params); + if (!result) { + return result.error(); + } + type::Type expected = getType(); + type::Type actual = typeOf(*result); + if (checkSubtype(expected, actual)) { + return EvaluationError { + "Expected value to be of type " + toString(expected) + + ", but found " + toString(actual) + " instead." + }; + } + return *result; +} + +void ArrayAssertion::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); +} + +using namespace mbgl::style::conversion; +ParseResult ArrayAssertion::parse(const Convertible& value, ParsingContext& ctx) { + + static std::unordered_map<std::string, type::Type> itemTypes { + {"string", type::String}, + {"number", type::Number}, + {"boolean", type::Boolean} + }; + + auto length = arrayLength(value); + if (length < 2 || length > 4) { + ctx.error("Expected 1, 2, or 3 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + optional<type::Type> itemType; + optional<std::size_t> N; + if (length > 2) { + optional<std::string> itemTypeName = toString(arrayMember(value, 1)); + auto it = itemTypeName ? itemTypes.find(*itemTypeName) : itemTypes.end(); + if (it == itemTypes.end()) { + ctx.error( + R"(The item type argument of "array" must be one of string, number, boolean)", + 1 + ); + return ParseResult(); + } + itemType = it->second; + } else { + itemType = {type::Value}; + } + + if (length > 3) { + auto n = toNumber(arrayMember(value, 2)); + if (!n || *n != std::floor(*n)) { + ctx.error( + R"(The length argument to "array" must be a positive integer literal.)", + 2 + ); + return ParseResult(); + } + N = optional<std::size_t>(*n); + } + + auto input = ctx.parse(arrayMember(value, length - 1), length - 1, {type::Value}); + if (!input) { + return input; + } + + return ParseResult(std::make_unique<ArrayAssertion>( + type::Array(*itemType, N), + std::move(*input) + )); +} + +mbgl::Value ArrayAssertion::serialize() const { + std::vector<mbgl::Value> serialized; + serialized.emplace_back(getOperator()); + + + const auto array = getType().get<type::Array>(); + if (array.itemType.is<type::StringType>() + || array.itemType.is<type::NumberType>() + || array.itemType.is<type::BooleanType>()) { + serialized.emplace_back(type::toString(array.itemType)); + if (array.N) { + serialized.emplace_back(uint64_t(*array.N)); + } + } + + serialized.emplace_back(input->serialize()); + return serialized; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp new file mode 100644 index 0000000000..d6f3f1b584 --- /dev/null +++ b/src/mbgl/style/expression/assertion.cpp @@ -0,0 +1,87 @@ +#include <mbgl/style/expression/assertion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +using namespace mbgl::style::conversion; +ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) { + static std::unordered_map<std::string, type::Type> types { + {"string", type::String}, + {"number", type::Number}, + {"boolean", type::Boolean}, + {"object", type::Object} + }; + + std::size_t length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + auto it = types.find(*toString(arrayMember(value, 0))); + assert(it != types.end()); + + std::vector<std::unique_ptr<Expression>> parsed; + parsed.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value}); + if (!input) return ParseResult(); + parsed.push_back(std::move(*input)); + } + + return ParseResult(std::make_unique<Assertion>(it->second, std::move(parsed))); +} + +std::string Assertion::getOperator() const { + return type::toString(getType()); +} + +EvaluationResult Assertion::evaluate(const EvaluationContext& params) const { + for (std::size_t i = 0; i < inputs.size(); i++) { + EvaluationResult value = inputs[i]->evaluate(params); + if (!value) return value; + if (!type::checkSubtype(getType(), typeOf(*value))) { + return value; + } else if (i == inputs.size() - 1) { + return EvaluationError { + "Expected value to be of type " + toString(getType()) + + ", but found " + toString(typeOf(*value)) + " instead." + }; + } + } + + assert(false); + return EvaluationError { "Unreachable" }; +}; + +void Assertion::eachChild(const std::function<void(const Expression&)>& visit) const { + for(const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +}; + +bool Assertion::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Assertion*>(&e)) { + return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Assertion::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +} // namespace expression +} // namespace style +} // namespace mbgl + + diff --git a/src/mbgl/style/expression/at.cpp b/src/mbgl/style/expression/at.cpp new file mode 100644 index 0000000000..725e5ddb51 --- /dev/null +++ b/src/mbgl/style/expression/at.cpp @@ -0,0 +1,70 @@ +#include <mbgl/style/expression/at.hpp> +#include <mbgl/util/string.hpp> + + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult At::evaluate(const EvaluationContext& params) const { + const EvaluationResult evaluatedIndex = index->evaluate(params); + const EvaluationResult evaluatedInput = input->evaluate(params); + if (!evaluatedIndex) { + return evaluatedIndex.error(); + } + if (!evaluatedInput) { + return evaluatedInput.error(); + } + + const auto i = evaluatedIndex->get<double>(); + const auto inputArray = evaluatedInput->get<std::vector<Value>>(); + + if (i < 0) { + return EvaluationError { + "Array index out of bounds: " + util::toString(i) + " < 0." + }; + } + + if (i >= inputArray.size()) { + return EvaluationError { + "Array index out of bounds: " + util::toString(i) + + " > " + util::toString(inputArray.size() - 1) + "." + }; + } + if (i != std::floor(i)) { + return EvaluationError { + "Array index must be an integer, but found " + util::toString(i) + " instead." + }; + } + return inputArray[static_cast<std::size_t>(i)]; +} + +void At::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*index); + visit(*input); +} + +using namespace mbgl::style::conversion; +ParseResult At::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + std::size_t length = arrayLength(value); + if (length != 3) { + ctx.error("Expected 2 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + ParseResult index = ctx.parse(arrayMember(value, 1), 1, {type::Number}); + + type::Type inputType = type::Array(ctx.getExpected() ? *ctx.getExpected() : type::Value); + ParseResult input = ctx.parse(arrayMember(value, 2), 2, {inputType}); + + if (!index || !input) return ParseResult(); + + return ParseResult(std::make_unique<At>(std::move(*index), std::move(*input))); + +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/boolean_operator.cpp b/src/mbgl/style/expression/boolean_operator.cpp new file mode 100644 index 0000000000..8d277450ba --- /dev/null +++ b/src/mbgl/style/expression/boolean_operator.cpp @@ -0,0 +1,95 @@ +#include <mbgl/style/expression/boolean_operator.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Any::evaluate(const EvaluationContext& params) const { + for (auto it = inputs.begin(); it != inputs.end(); it++) { + const EvaluationResult result = (*it)->evaluate(params); + if (!result) return result; + if (result->get<bool>()) return EvaluationResult(true); + } + return EvaluationResult(false); +} + +void Any::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +} + +bool Any::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Any*>(&e)) { + return Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Any::possibleOutputs() const { + return {{ true }, { false }}; +} + + +EvaluationResult All::evaluate(const EvaluationContext& params) const { + for (auto it = inputs.begin(); it != inputs.end(); it++) { + const EvaluationResult result = (*it)->evaluate(params); + if (!result) return result; + if (!result->get<bool>()) return EvaluationResult(false); + } + return EvaluationResult(true); +} + +void All::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +} + +bool All::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const All*>(&e)) { + return Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> All::possibleOutputs() const { + return {{ true }, { false }}; +} + +using namespace mbgl::style::conversion; + +template <class T> +ParseResult parseBooleanOp(const Convertible& value, ParsingContext& ctx) { + + assert(isArray(value)); + auto length = arrayLength(value); + + std::vector<std::unique_ptr<Expression>> parsedInputs; + + parsedInputs.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + auto parsed = ctx.parse(arrayMember(value, i), i, {type::Boolean}); + if (!parsed) { + return parsed; + } + + parsedInputs.push_back(std::move(*parsed)); + } + + return ParseResult(std::make_unique<T>(std::move(parsedInputs))); +} + +ParseResult Any::parse(const Convertible& value, ParsingContext& ctx) { + return parseBooleanOp<Any>(value, ctx); +} + +ParseResult All::parse(const Convertible& value, ParsingContext& ctx) { + return parseBooleanOp<All>(value, ctx); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/case.cpp b/src/mbgl/style/expression/case.cpp new file mode 100644 index 0000000000..295e694189 --- /dev/null +++ b/src/mbgl/style/expression/case.cpp @@ -0,0 +1,104 @@ +#include <mbgl/style/expression/case.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Case::evaluate(const EvaluationContext& params) const { + for (const auto& branch : branches) { + const EvaluationResult evaluatedTest = branch.first->evaluate(params); + if (!evaluatedTest) { + return evaluatedTest.error(); + } + if (evaluatedTest->get<bool>()) { + return branch.second->evaluate(params); + } + } + + return otherwise->evaluate(params); +} + +void Case::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const Branch& branch : branches) { + visit(*branch.first); + visit(*branch.second); + } + visit(*otherwise); +} + +bool Case::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Case*>(&e)) { + return *otherwise == *(rhs->otherwise) && Expression::childrenEqual(branches, rhs->branches); + } + return false; +} + +std::vector<optional<Value>> Case::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + +using namespace mbgl::style::conversion; +ParseResult Case::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 4) { + ctx.error("Expected at least 3 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // Expect even-length array: ["case", 2 * (n pairs)..., otherwise] + if (length % 2 != 0) { + ctx.error("Expected an odd number of arguments"); + return ParseResult(); + } + + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + std::vector<Case::Branch> branches; + branches.reserve((length - 2) / 2); + for (size_t i = 1; i + 1 < length; i += 2) { + auto test = ctx.parse(arrayMember(value, i), i, {type::Boolean}); + if (!test) { + return test; + } + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return output; + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + branches.push_back(std::make_pair(std::move(*test), std::move(*output))); + } + + assert(outputType); + + auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType); + if (!otherwise) { + return otherwise; + } + + return ParseResult(std::make_unique<Case>(*outputType, + std::move(branches), + std::move(*otherwise))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp new file mode 100644 index 0000000000..04a1643f0c --- /dev/null +++ b/src/mbgl/style/expression/check_subtype.cpp @@ -0,0 +1,60 @@ +#include <string> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { +namespace type { + +std::string errorMessage(const Type& expected, const Type& t) { + return {"Expected " + toString(expected) + " but found " + toString(t) + " instead."}; +} + +optional<std::string> checkSubtype(const Type& expected, const Type& t) { + if (t.is<ErrorType>()) return {}; + + optional<std::string> result = expected.match( + [&] (const Array& expectedArray) -> optional<std::string> { + if (!t.is<Array>()) { return {errorMessage(expected, t)}; } + const auto& actualArray = t.get<Array>(); + const auto err = checkSubtype(expectedArray.itemType, actualArray.itemType); + if (err) return { errorMessage(expected, t) }; + if (expectedArray.N && expectedArray.N != actualArray.N) return { errorMessage(expected, t) }; + return {}; + }, + [&] (const ValueType&) -> optional<std::string> { + if (t.is<ValueType>()) return {}; + + const Type members[] = { + Null, + Boolean, + Number, + String, + Object, + Color, + Array(Value) + }; + + for (const auto& member : members) { + const auto err = checkSubtype(member, t); + if (!err) { + return {}; + } + } + return { errorMessage(expected, t) }; + }, + [&] (const auto&) -> optional<std::string> { + if (expected != t) { + return { errorMessage(expected, t) }; + } + return {}; + } + ); + + return result; +} + +} // namespace type +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/coalesce.cpp b/src/mbgl/style/expression/coalesce.cpp new file mode 100644 index 0000000000..872a9abbef --- /dev/null +++ b/src/mbgl/style/expression/coalesce.cpp @@ -0,0 +1,84 @@ +#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Coalesce::evaluate(const EvaluationContext& params) const { + EvaluationResult result = Null; + for (const auto& arg : args) { + result = arg->evaluate(params); + if (!result || *result != Null) break; + } + return result; +} + +void Coalesce::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& arg : args) { + visit(*arg); + } +} + +bool Coalesce::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Coalesce*>(&e)) { + return Expression::childrenEqual(args, rhs->args); + } + return false; +} + +std::vector<optional<Value>> Coalesce::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& arg : args) { + for (auto& output : arg->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +using namespace mbgl::style::conversion; +ParseResult Coalesce::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + optional<type::Type> outputType; + optional<type::Type> expectedType = ctx.getExpected(); + if (expectedType && *expectedType != type::Value) { + outputType = expectedType; + } + + Coalesce::Args args; + args.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + auto parsed = ctx.parse(arrayMember(value, i), i, outputType, ParsingContext::omitTypeAnnotations); + if (!parsed) { + return parsed; + } + if (!outputType) { + outputType = (*parsed)->getType(); + } + args.push_back(std::move(*parsed)); + } + assert(outputType); + + // Above, we parse arguments without inferred type annotation so that + // they don't produce a runtime error for `null` input, which would + // preempt the desired null-coalescing behavior. + // Thus, if any of our arguments would have needed an annotation, we + // need to wrap the enclosing coalesce expression with it instead. + bool needsAnnotation = expectedType && + std::any_of(args.begin(), args.end(), [&] (const auto& arg) { + return type::checkSubtype(*expectedType, arg->getType()); + }); + + return ParseResult(std::make_unique<Coalesce>(needsAnnotation ? type::Value : *outputType, std::move(args))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp new file mode 100644 index 0000000000..d9cd3ffdc9 --- /dev/null +++ b/src/mbgl/style/expression/coercion.cpp @@ -0,0 +1,161 @@ +#include <mbgl/style/expression/coercion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/util.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult toNumber(const Value& v) { + optional<double> result = v.match( + [](const double f) -> optional<double> { return f; }, + [](const std::string& s) -> optional<double> { + try { + return util::stof(s); + } catch(std::exception) { + return optional<double>(); + } + }, + [](const auto&) { return optional<double>(); } + ); + if (!result) { + return EvaluationError { + "Could not convert " + stringify(v) + " to number." + }; + } + return *result; +} + +EvaluationResult toColor(const Value& colorValue) { + return colorValue.match( + [&](const std::string& colorString) -> EvaluationResult { + const optional<Color> result = Color::parse(colorString); + if (result) { + return *result; + } else { + return EvaluationError{ + "Could not parse color from value '" + colorString + "'" + }; + } + }, + [&](const std::vector<Value>& components) -> EvaluationResult { + std::size_t len = components.size(); + bool isNumeric = std::all_of(components.begin(), components.end(), [](const Value& item) { + return item.template is<double>(); + }); + if ((len == 3 || len == 4) && isNumeric) { + Result<Color> c = {rgba( + components[0].template get<double>(), + components[1].template get<double>(), + components[2].template get<double>(), + len == 4 ? components[3].template get<double>() : 1.0 + )}; + if (!c) return c.error(); + return *c; + } else { + return EvaluationError{ + "Invalid rbga value " + stringify(colorValue) + ": expected an array containing either three or four numeric values." + }; + } + }, + [&](const auto&) -> EvaluationResult { + return EvaluationError{ + "Could not parse color from value '" + stringify(colorValue) + "'" + }; + } + ); +} + +Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) : + Expression(std::move(type_)), + inputs(std::move(inputs_)) +{ + type::Type t = getType(); + if (t.is<type::NumberType>()) { + coerceSingleValue = toNumber; + } else if (t.is<type::ColorType>()) { + coerceSingleValue = toColor; + } else { + assert(false); + } +} + +std::string Coercion::getOperator() const { + return getType().match( + [](const type::NumberType&) { return "to-number"; }, + [](const type::ColorType&) { return "to-color"; }, + [](const auto&) { assert(false); return ""; }); +} + +using namespace mbgl::style::conversion; +ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) { + static std::unordered_map<std::string, type::Type> types { + {"to-number", type::Number}, + {"to-color", type::Color} + }; + + std::size_t length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + auto it = types.find(*toString(arrayMember(value, 0))); + assert(it != types.end()); + + std::vector<std::unique_ptr<Expression>> parsed; + parsed.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value}); + if (!input) return ParseResult(); + parsed.push_back(std::move(*input)); + } + + return ParseResult(std::make_unique<Coercion>(it->second, std::move(parsed))); +} + +EvaluationResult Coercion::evaluate(const EvaluationContext& params) const { + for (std::size_t i = 0; i < inputs.size(); i++) { + EvaluationResult value = inputs[i]->evaluate(params); + if (!value) return value; + EvaluationResult coerced = coerceSingleValue(*value); + if (coerced || i == inputs.size() - 1) { + return coerced; + } + } + + assert(false); + return EvaluationError { "Unreachable" }; +}; + +void Coercion::eachChild(const std::function<void(const Expression&)>& visit) const { + for(const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +}; + +bool Coercion::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Coercion*>(&e)) { + return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Coercion::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +} // namespace expression +} // namespace style +} // namespace mbgl + + + diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp new file mode 100644 index 0000000000..c257dbf2bb --- /dev/null +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -0,0 +1,554 @@ +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/util.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/math/log2.hpp> +#include <mbgl/util/i18n.hpp> +#include <mbgl/util/ignore.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/util/platform.hpp> +#include <cmath> + +namespace mbgl { +namespace style { +namespace expression { + +namespace detail { + +/* + The Signature<Fn> structs are wrappers around an "evaluate()" function whose + purpose is to extract the necessary Type data from the evaluate function's + type. There are three key (partial) specializations: + + Signature<R (Params...)>: + Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U> + + Signature<R (const Varargs<T>&)>: + Wraps an evaluate function that takes an arbitrary number of arguments (via + a Varargs<T>, which is just an alias for std::vector). + + Signature<R (const EvaluationContext&, Params...)>: + Wraps an evaluate function that needs to access the expression evaluation + parameters in addition to its subexpressions, i.e., + (const EvaluationParams& const T0&, const T1&, ...) -> Result<U>. Needed + for expressions like ["zoom"], ["get", key], etc. + + In each of the above evaluate signatures, T0, T1, etc. are the types of + the successfully evaluated subexpressions. +*/ +template <class, class Enable = void> +struct Signature; + +// Simple evaluate function (const T0&, const T1&, ...) -> Result<U> +template <class R, class... Params> +struct Signature<R (Params...)> : SignatureBase { + using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; + + Signature(R (*evaluate_)(Params...), std::string name_) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}, + std::move(name_) + ), + evaluate(evaluate_) {} + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); + } + + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { + typename Signature::Args argsArray; + std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray)); + } + + R (*evaluate)(Params...); +private: + template <std::size_t ...I> + EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const { + const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}}; + for (const auto& arg : evaluated) { + if(!arg) return arg.error(); + } + const R value = evaluate(*fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...); + if (!value) return value.error(); + return *value; + } +}; + +// Varargs evaluate function (const Varargs<T>&) -> Result<U> +template <class R, typename T> +struct Signature<R (const Varargs<T>&)> : SignatureBase { + using Args = std::vector<std::unique_ptr<Expression>>; + + Signature(R (*evaluate_)(const Varargs<T>&), std::string name_) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + VarargsType { valueTypeToExpressionType<T>() }, + std::move(name_) + ), + evaluate(evaluate_) + {} + + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args)); + }; + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + Varargs<T> evaluated; + evaluated.reserve(args.size()); + for (const auto& arg : args) { + const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters); + if(!evaluatedArg) return evaluatedArg.error(); + evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg)); + } + const R value = evaluate(evaluated); + if (!value) return value.error(); + return *value; + } + + R (*evaluate)(const Varargs<T>&); +}; + +// Evaluate function needing parameter access, +// (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U> +template <class R, class... Params> +struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase { + using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; + + Signature(R (*evaluate_)(const EvaluationContext&, Params...), std::string name_) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...}, + std::move(name_) + ), + evaluate(evaluate_) + {} + + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { + typename Signature::Args argsArray; + std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray)); + } + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); + } + +private: + template <std::size_t ...I> + EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const { + const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}}; + for (const auto& arg : evaluated) { + if(!arg) return arg.error(); + } + // TODO: assert correct runtime type of each arg value + const R value = evaluate(evaluationParameters, *fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...); + if (!value) return value.error(); + return *value; + } + + R (*evaluate)(const EvaluationContext&, Params...); +}; + +// Machinery to pull out function types from class methods, lambdas, etc. +template <class R, class... Params> +struct Signature<R (*)(Params...)> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class T, class R, class... Params> +struct Signature<R (T::*)(Params...) const> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class T, class R, class... Params> +struct Signature<R (T::*)(Params...)> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class Lambda> +struct Signature<Lambda, std::enable_if_t<std::is_class<Lambda>::value>> + : Signature<decltype(&Lambda::operator())> +{ using Signature<decltype(&Lambda::operator())>::Signature; }; + +} // namespace detail + +using Definition = CompoundExpressionRegistry::Definition; + +template <typename Fn> +static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction, std::string name) { + return std::make_unique<detail::Signature<Fn>>(evaluateFunction, std::move(name)); +} + +std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() { + std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions; + auto define = [&](std::string name, auto fn) { + definitions[name].push_back(makeSignature(fn, name)); + }; + + define("e", []() -> Result<double> { return 2.718281828459045; }); + define("pi", []() -> Result<double> { return 3.141592653589793; }); + define("ln2", []() -> Result<double> { return 0.6931471805599453; }); + + define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); }); + + define("to-string", [](const Value& value) -> Result<std::string> { + return value.match( + [](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting + [](const std::string& s) -> Result<std::string> { return s; }, // avoid quoting + [](const auto& v) -> Result<std::string> { return stringify(v); } + ); + }); + + define("to-boolean", [](const Value& v) -> Result<bool> { + return v.match( + [&] (double f) { return (bool)f; }, + [&] (const std::string& s) { return s.length() > 0; }, + [&] (bool b) { return b; }, + [&] (const NullValue&) { return false; }, + [&] (const auto&) { return true; } + ); + }); + define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> { + return color.toArray(); + }); + + define("rgba", rgba); + define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); }); + + define("zoom", [](const EvaluationContext& params) -> Result<double> { + if (!params.zoom) { + return EvaluationError { + "The 'zoom' expression is unavailable in the current evaluation context." + }; + } + return *(params.zoom); + }); + + define("heatmap-density", [](const EvaluationContext& params) -> Result<double> { + if (!params.heatmapDensity) { + return EvaluationError { + "The 'heatmap-density' expression is unavailable in the current evaluation context." + }; + } + return *(params.heatmapDensity); + }); + + define("has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + return params.feature->getValue(key) ? true : false; + }); + define("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> { + return object.find(key) != object.end(); + }); + + define("get", [](const EvaluationContext& params, const std::string& key) -> Result<Value> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto propertyValue = params.feature->getValue(key); + if (!propertyValue) { + return Null; + } + return Value(toExpressionValue(*propertyValue)); + }); + define("get", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<Value> { + if (object.find(key) == object.end()) { + return Null; + } + return object.at(key); + }); + + define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + std::unordered_map<std::string, Value> result; + const PropertyMap properties = params.feature->getProperties(); + for (const auto& entry : properties) { + result[entry.first] = toExpressionValue(entry.second); + } + return result; + }); + + define("geometry-type", [](const EvaluationContext& params) -> Result<std::string> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto type = params.feature->getType(); + if (type == FeatureType::Point) { + return "Point"; + } else if (type == FeatureType::LineString) { + return "LineString"; + } else if (type == FeatureType::Polygon) { + return "Polygon"; + } else { + return "Unknown"; + } + }); + + define("id", [](const EvaluationContext& params) -> Result<Value> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto id = params.feature->getID(); + if (!id) { + return Null; + } + return id->match( + [](const auto& idValue) { + return toExpressionValue(mbgl::Value(idValue)); + } + ); + }); + + define("+", [](const Varargs<double>& args) -> Result<double> { + double sum = 0.0f; + for (auto arg : args) { + sum += arg; + } + return sum; + }); + define("-", [](double a, double b) -> Result<double> { return a - b; }); + define("-", [](double a) -> Result<double> { return -a; }); + define("*", [](const Varargs<double>& args) -> Result<double> { + double prod = 1.0f; + for (auto arg : args) { + prod *= arg; + } + return prod; + }); + define("/", [](double a, double b) -> Result<double> { return a / b; }); + define("%", [](double a, double b) -> Result<double> { return fmod(a, b); }); + define("^", [](double a, double b) -> Result<double> { return pow(a, b); }); + define("sqrt", [](double x) -> Result<double> { return sqrt(x); }); + define("log10", [](double x) -> Result<double> { return log10(x); }); + define("ln", [](double x) -> Result<double> { return log(x); }); + define("log2", [](double x) -> Result<double> { return util::log2(x); }); + define("sin", [](double x) -> Result<double> { return sin(x); }); + define("cos", [](double x) -> Result<double> { return cos(x); }); + define("tan", [](double x) -> Result<double> { return tan(x); }); + define("asin", [](double x) -> Result<double> { return asin(x); }); + define("acos", [](double x) -> Result<double> { return acos(x); }); + define("atan", [](double x) -> Result<double> { return atan(x); }); + + define("min", [](const Varargs<double>& args) -> Result<double> { + double result = std::numeric_limits<double>::infinity(); + for (double arg : args) { + result = fmin(arg, result); + } + return result; + }); + define("max", [](const Varargs<double>& args) -> Result<double> { + double result = -std::numeric_limits<double>::infinity(); + for (double arg : args) { + result = fmax(arg, result); + } + return result; + }); + + define("round", [](double x) -> Result<double> { return std::round(x); }); + define("floor", [](double x) -> Result<double> { return std::floor(x); }); + define("ceil", [](double x) -> Result<double> { return std::ceil(x); }); + define("abs", [](double x) -> Result<double> { return std::abs(x); }); + + define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; }); + define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; }); + define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; }); + define(">=",[](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs >= rhs; }); + define("<", [](double lhs, double rhs) -> Result<bool> { return lhs < rhs; }); + define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; }); + define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; }); + define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; }); + + define("!", [](bool e) -> Result<bool> { return !e; }); + + define("is-supported-script", [](const std::string& x) -> Result<bool> { + return util::i18n::isStringInSupportedScript(x); + }); + + define("upcase", [](const std::string& input) -> Result<std::string> { + return platform::uppercase(input); + }); + define("downcase", [](const std::string& input) -> Result<std::string> { + return platform::lowercase(input); + }); + define("concat", [](const Varargs<std::string>& args) -> Result<std::string> { + std::string s; + for (const std::string& arg : args) { + s += arg; + } + return s; + }); + define("error", [](const std::string& input) -> Result<type::ErrorType> { + return EvaluationError { input }; + }); + + return definitions; +} + +std::unordered_map<std::string, Definition> CompoundExpressionRegistry::definitions = initializeDefinitions(); + +using namespace mbgl::style::conversion; +ParseResult parseCompoundExpression(const std::string name, const Convertible& value, ParsingContext& ctx) { + assert(isArray(value) && arrayLength(value) > 0); + + auto it = CompoundExpressionRegistry::definitions.find(name); + if (it == CompoundExpressionRegistry::definitions.end()) { + ctx.error( + R"(Unknown expression ")" + name + R"(". If you wanted a literal array, use ["literal", [...]].)", + 0 + ); + return ParseResult(); + } + const CompoundExpressionRegistry::Definition& definition = it->second; + + auto length = arrayLength(value); + + // Check if we have a single signature with the correct number of + // parameters. If so, then use that signature's parameter types for parsing + // (and inferring the types of) the arguments. + optional<std::size_t> singleMatchingSignature; + for (std::size_t j = 0; j < definition.size(); j++) { + const std::unique_ptr<detail::SignatureBase>& signature = definition[j]; + if ( + signature->params.is<VarargsType>() || + signature->params.get<std::vector<type::Type>>().size() == length - 1 + ) { + if (singleMatchingSignature) { + singleMatchingSignature = {}; + } else { + singleMatchingSignature = j; + } + } + } + + // parse subexpressions first + std::vector<std::unique_ptr<Expression>> args; + args.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + optional<type::Type> expected; + + if (singleMatchingSignature) { + expected = definition[*singleMatchingSignature]->params.match( + [](const VarargsType& varargs) { return varargs.type; }, + [&](const std::vector<type::Type>& params_) { return params_[i - 1]; } + ); + } + + auto parsed = ctx.parse(arrayMember(value, i), i, expected); + if (!parsed) { + return parsed; + } + args.push_back(std::move(*parsed)); + } + return createCompoundExpression(definition, std::move(args), ctx); +} + + +ParseResult createCompoundExpression(const std::string& name, + std::vector<std::unique_ptr<Expression>> args, + ParsingContext& ctx) +{ + return createCompoundExpression(CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx); +} + + +ParseResult createCompoundExpression(const Definition& definition, + std::vector<std::unique_ptr<Expression>> args, + ParsingContext& ctx) +{ + ParsingContext signatureContext(ctx.getKey()); + + for (const std::unique_ptr<detail::SignatureBase>& signature : definition) { + signatureContext.clearErrors(); + + if (signature->params.is<std::vector<type::Type>>()) { + const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>(); + if (params.size() != args.size()) { + signatureContext.error( + "Expected " + util::toString(params.size()) + + " arguments, but found " + util::toString(args.size()) + " instead." + ); + continue; + } + + for (std::size_t i = 0; i < args.size(); i++) { + const std::unique_ptr<Expression>& arg = args[i]; + optional<std::string> err = type::checkSubtype(params.at(i), arg->getType()); + if (err) { + signatureContext.error(*err, i + 1); + } + } + } else if (signature->params.is<VarargsType>()) { + const type::Type& paramType = signature->params.get<VarargsType>().type; + for (std::size_t i = 0; i < args.size(); i++) { + const std::unique_ptr<Expression>& arg = args[i]; + optional<std::string> err = type::checkSubtype(paramType, arg->getType()); + if (err) { + signatureContext.error(*err, i + 1); + } + } + } + + if (signatureContext.getErrors().size() == 0) { + return ParseResult(signature->makeExpression(std::move(args))); + } + } + + if (definition.size() == 1) { + ctx.appendErrors(std::move(signatureContext)); + } else { + std::string signatures; + for (const auto& signature : definition) { + signatures += (signatures.size() > 0 ? " | " : ""); + signature->params.match( + [&](const VarargsType& varargs) { + signatures += "(" + toString(varargs.type) + ")"; + }, + [&](const std::vector<type::Type>& params) { + signatures += "("; + bool first = true; + for (const type::Type& param : params) { + if (!first) signatures += ", "; + signatures += toString(param); + first = false; + } + signatures += ")"; + } + ); + + } + std::string actualTypes; + for (const auto& arg : args) { + if (actualTypes.size() > 0) { + actualTypes += ", "; + } + actualTypes += toString(arg->getType()); + } + ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead."); + } + + return ParseResult(); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/equals.cpp b/src/mbgl/style/expression/equals.cpp new file mode 100644 index 0000000000..6d963cc1d8 --- /dev/null +++ b/src/mbgl/style/expression/equals.cpp @@ -0,0 +1,87 @@ +#include <mbgl/style/expression/equals.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Equals::Equals(std::unique_ptr<Expression> lhs_, std::unique_ptr<Expression> rhs_, bool negate_) + : Expression(type::Boolean), + lhs(std::move(lhs_)), + rhs(std::move(rhs_)), + negate(negate_) { +} + +EvaluationResult Equals::evaluate(const EvaluationContext& params) const { + EvaluationResult lhsResult = lhs->evaluate(params); + if (!lhsResult) return lhsResult; + + EvaluationResult rhsResult = rhs->evaluate(params); + if (!rhsResult) return lhsResult; + + bool result = *lhsResult == *rhsResult; + if (negate) { + result = !result; + } + return result; +} + +void Equals::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*lhs); + visit(*rhs); +} + +bool Equals::operator==(const Expression& e) const { + if (auto eq = dynamic_cast<const Equals*>(&e)) { + return eq->negate == negate && *eq->lhs == *lhs && *eq->rhs == *rhs; + } + return false; +} + +std::vector<optional<Value>> Equals::possibleOutputs() const { + return {{ true }, { false }}; +} + +static bool isComparableType(const type::Type& type) { + return type == type::String || + type == type::Number || + type == type::Boolean || + type == type::Null; +} + +using namespace mbgl::style::conversion; +ParseResult Equals::parse(const Convertible& value, ParsingContext& ctx) { + std::size_t length = arrayLength(value); + + if (length != 3) { + ctx.error("Expected two arguments."); + return ParseResult(); + } + + bool negate = toString(arrayMember(value, 0)) == std::string("!="); + + ParseResult lhs = ctx.parse(arrayMember(value, 1), 1, {type::Value}); + if (!lhs) return ParseResult(); + + ParseResult rhs = ctx.parse(arrayMember(value, 2), 2, {type::Value}); + if (!rhs) return ParseResult(); + + type::Type lhsType = (*lhs)->getType(); + type::Type rhsType = (*rhs)->getType(); + + if (!isComparableType(lhsType) && !isComparableType(rhsType)) { + ctx.error("Expected at least one argument to be a string, number, boolean, or null, but found (" + + toString(lhsType) + ", " + toString(rhsType) + ") instead."); + return ParseResult(); + } + + if (lhsType != rhsType && lhsType != type::Value && rhsType != type::Value) { + ctx.error("Cannot compare " + toString(lhsType) + " and " + toString(rhsType) + "."); + return ParseResult(); + } + + return ParseResult(std::make_unique<Equals>(std::move(*lhs), std::move(*rhs), negate)); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/find_zoom_curve.cpp b/src/mbgl/style/expression/find_zoom_curve.cpp new file mode 100644 index 0000000000..5d39e0791e --- /dev/null +++ b/src/mbgl/style/expression/find_zoom_curve.cpp @@ -0,0 +1,76 @@ +#include <mbgl/style/expression/find_zoom_curve.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/expression/coalesce.hpp> + +#include <mbgl/util/variant.hpp> +#include <mbgl/util/optional.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> result; + + if (auto let = dynamic_cast<const Let*>(e)) { + result = findZoomCurve(let->getResult()); + } else if (auto coalesce = dynamic_cast<const Coalesce*>(e)) { + std::size_t length = coalesce->getLength(); + for (std::size_t i = 0; i < length; i++) { + result = findZoomCurve(coalesce->getChild(i)); + if (result) { + break; + } + } + } else if (auto curve = dynamic_cast<const InterpolateBase*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(curve->getInput().get()); + if (z && z->getName() == "zoom") { + result = {curve}; + } + } else if (auto step = dynamic_cast<const Step*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(step->getInput().get()); + if (z && z->getName() == "zoom") { + result = {step}; + } + } + + if (result && result->is<ParsingError>()) { + return result; + } + + e->eachChild([&](const Expression& child) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> childResult(findZoomCurve(&child)); + if (childResult) { + if (childResult->is<ParsingError>()) { + result = childResult; + } else if (!result && childResult) { + result = {ParsingError { + R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", "" + }}; + } else if (result && childResult && result != childResult) { + result = {ParsingError { + R"(Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.)", "" + }}; + } + } + }); + + return result; +} + +variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e) { + return findZoomCurve(e)->match( + [](const ParsingError&) -> variant<const InterpolateBase*, const Step*> { + assert(false); + return {}; + }, + [](auto zoomCurve) -> variant<const InterpolateBase*, const Step*> { + return {std::move(zoomCurve)}; + } + ); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/get_covering_stops.cpp b/src/mbgl/style/expression/get_covering_stops.cpp new file mode 100644 index 0000000000..c9f87d93ac --- /dev/null +++ b/src/mbgl/style/expression/get_covering_stops.cpp @@ -0,0 +1,26 @@ +#include <mbgl/style/expression/get_covering_stops.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops, + const double lower, const double upper) { + assert(!stops.empty()); + auto minIt = stops.lower_bound(lower); + auto maxIt = stops.lower_bound(upper); + + // lower_bound yields first element >= lowerZoom, but we want the *last* + // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one. + if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) { + minIt--; + } + return Range<float> { + static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first), + static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first) + }; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp new file mode 100644 index 0000000000..daad8523f2 --- /dev/null +++ b/src/mbgl/style/expression/interpolate.cpp @@ -0,0 +1,249 @@ +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +using Interpolator = variant<ExponentialInterpolator, + CubicBezierInterpolator>; + +using namespace mbgl::style::conversion; + +ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + auto length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected an interpolation type expression."); + return ParseResult(); + } + + const Convertible& interp = arrayMember(value, 1); + if (!isArray(interp) || arrayLength(interp) == 0) { + ctx.error("Expected an interpolation type expression."); + return ParseResult(); + } + + optional<Interpolator> interpolator; + + const optional<std::string> interpName = toString(arrayMember(interp, 0)); + if (interpName && *interpName == "linear") { + interpolator = {ExponentialInterpolator(1.0)}; + } else if (interpName && *interpName == "exponential") { + optional<double> base; + if (arrayLength(interp) == 2) { + base = toDouble(arrayMember(interp, 1)); + } + if (!base) { + ctx.error("Exponential interpolation requires a numeric base.", 1, 1); + return ParseResult(); + } + interpolator = {ExponentialInterpolator(*base)}; + } else if (interpName && *interpName == "cubic-bezier") { + optional<double> x1; + optional<double> y1; + optional<double> x2; + optional<double> y2; + if (arrayLength(interp) == 5) { + x1 = toDouble(arrayMember(interp, 1)); + y1 = toDouble(arrayMember(interp, 2)); + x2 = toDouble(arrayMember(interp, 3)); + y2 = toDouble(arrayMember(interp, 4)); + } + if ( + !x1 || !y1 || !x2 || !y2 || + *x1 < 0 || *x1 > 1 || + *y1 < 0 || *y1 > 1 || + *x2 < 0 || *x2 > 1 || + *y2 < 0 || *y2 > 1 + ) { + ctx.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.", 1); + return ParseResult(); + + } + interpolator = {CubicBezierInterpolator(*x1, *y1, *x2, *y2)}; + } + + if (!interpolator) { + ctx.error("Unknown interpolation type " + (interpName ? *interpName : ""), 1, 0); + return ParseResult(); + } + + std::size_t minArgs = 4; + if (length - 1 < minArgs) { + ctx.error("Expected at least 4 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // [interpolation, interp_type, input, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 2), 2, {type::Number}); + if (!input) { + return input; + } + + std::map<double, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + double previous = - std::numeric_limits<double>::infinity(); + + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); + optional<double> label; + optional<std::string> labelError; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(labelError ? *labelError : + R"(Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", + i); + return ParseResult(); + } + + if (*label <= previous) { + ctx.error( + R"(Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + if ( + *outputType != type::Number && + *outputType != type::Color && + !( + outputType->is<type::Array>() && + outputType->get<type::Array>().itemType == type::Number && + outputType->get<type::Array>().N + ) + ) + { + ctx.error("Type " + toString(*outputType) + " is not interpolatable."); + return ParseResult(); + } + + return outputType->match( + [&](const type::NumberType&) -> ParseResult { + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<double>>( + *outputType, interpolator_, std::move(*input), std::move(stops) + )); + }); + }, + [&](const type::ColorType&) -> ParseResult { + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<Color>>( + *outputType, interpolator_, std::move(*input), std::move(stops) + )); + }); + }, + [&](const type::Array& arrayType) -> ParseResult { + return interpolator->match( + [&](const auto& continuousInterpolator) { + if (arrayType.itemType != type::Number || !arrayType.N) { + assert(false); // interpolability already checked above. + return ParseResult(); + } + return ParseResult(std::make_unique<Interpolate<std::vector<Value>>>( + *outputType, continuousInterpolator, std::move(*input), std::move(stops) + )); + } + ); + }, + [&](const auto&) { + // unreachable: Null, Boolean, String, Object, Value output types + // are not interpolatable, and interpolability was already checked above + assert(false); + return ParseResult(); + } + ); +} + +std::vector<optional<Value>> InterpolateBase::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +template <typename T> +mbgl::Value Interpolate<T>::serialize() const { + std::vector<mbgl::Value> serialized; + serialized.emplace_back(getOperator()); + + interpolator.match( + [&](const ExponentialInterpolator& exponential) { + if (exponential.base == 1) { + serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("linear") }}); + } else { + serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("exponential"), exponential.base }}); + } + }, + [&](const CubicBezierInterpolator& cubicBezier) { + static const std::string cubicBezierTag("cubic-bezier"); + auto p1 = cubicBezier.ub.getP1(); + auto p2 = cubicBezier.ub.getP2(); + serialized.emplace_back(std::vector<mbgl::Value>{{ cubicBezierTag, p1.first, p1.second, p2.first, p2.second }}); + } + ); + serialized.emplace_back(input->serialize()); + for (auto& entry : stops) { + serialized.emplace_back(entry.first); + serialized.emplace_back(entry.second->serialize()); + }; + return serialized; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp new file mode 100644 index 0000000000..0ebb37faa9 --- /dev/null +++ b/src/mbgl/style/expression/is_constant.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/expression/is_constant.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +bool isFeatureConstant(const Expression& expression) { + if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) { + const std::string name = e->getName(); + optional<std::size_t> parameterCount = e->getParameterCount(); + if (name == "get" && parameterCount && *parameterCount == 1) { + return false; + } else if (name == "has" && parameterCount && *parameterCount == 1) { + return false; + } else if ( + name == "properties" || + name == "geometry-type" || + name == "id" + ) { + return false; + } + } + + bool featureConstant = true; + expression.eachChild([&](const Expression& e) { + if (featureConstant && !isFeatureConstant(e)) { + featureConstant = false; + } + }); + return featureConstant; +} + +bool isZoomConstant(const Expression& e) { + return isGlobalPropertyConstant(e, std::array<std::string, 1>{{"zoom"}}); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/is_expression.cpp b/src/mbgl/style/expression/is_expression.cpp new file mode 100644 index 0000000000..77212f6a1e --- /dev/null +++ b/src/mbgl/style/expression/is_expression.cpp @@ -0,0 +1,29 @@ +#include <mbgl/style/expression/is_expression.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/parsing_context.hpp> + +#include <mbgl/style/conversion.hpp> + +#include <unordered_set> + +namespace mbgl { +namespace style { +namespace expression { + +using namespace mbgl::style::conversion; + +bool isExpression(const Convertible& value) { + const ExpressionRegistry& registry = getExpressionRegistry(); + + if (!isArray(value) || arrayLength(value) == 0) return false; + optional<std::string> name = toString(arrayMember(value, 0)); + if (!name) return false; + + return (registry.find(*name) != registry.end()) || + (CompoundExpressionRegistry::definitions.find(*name) != CompoundExpressionRegistry::definitions.end()); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/length.cpp b/src/mbgl/style/expression/length.cpp new file mode 100644 index 0000000000..258353ae4e --- /dev/null +++ b/src/mbgl/style/expression/length.cpp @@ -0,0 +1,66 @@ +#include <mbgl/style/expression/length.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Length::Length(std::unique_ptr<Expression> input_) + : Expression(type::Number), + input(std::move(input_)) { +} + +EvaluationResult Length::evaluate(const EvaluationContext& params) const { + const EvaluationResult value = input->evaluate(params); + if (!value) return value; + return value->match( + [] (const std::string& s) { + return EvaluationResult { double(s.size()) }; + }, + [] (const std::vector<Value>& v) { + return EvaluationResult { double(v.size()) }; + }, + [&] (const auto&) -> EvaluationResult { + return EvaluationError { "Expected value to be of type string or array, but found " + toString(typeOf(*value)) + " instead." }; + }); +} + +void Length::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); +} + +bool Length::operator==(const Expression& e) const { + if (auto eq = dynamic_cast<const Length*>(&e)) { + return *eq->input == *input; + } + return false; +} + +std::vector<optional<Value>> Length::possibleOutputs() const { + return { nullopt }; +} + +using namespace mbgl::style::conversion; +ParseResult Length::parse(const Convertible& value, ParsingContext& ctx) { + std::size_t length = arrayLength(value); + + if (length != 2) { + ctx.error("Expected one argument, but found " + util::toString(length) + " instead."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 1), 1); + if (!input) return ParseResult(); + + type::Type type = (*input)->getType(); + if (!type.is<type::Array>() && !type.is<type::StringType>() && !type.is<type::ValueType>()) { + ctx.error("Expected argument of type string or array, but found " + toString(type) + " instead."); + return ParseResult(); + } + + return ParseResult(std::make_unique<Length>(std::move(*input))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp new file mode 100644 index 0000000000..242a995b0b --- /dev/null +++ b/src/mbgl/style/expression/let.cpp @@ -0,0 +1,115 @@ +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/conversion/get_json_type.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Let::evaluate(const EvaluationContext& params) const { + return result->evaluate(params); +} + +void Let::eachChild(const std::function<void(const Expression&)>& visit) const { + for (auto it = bindings.begin(); it != bindings.end(); it++) { + visit(*it->second); + } + visit(*result); +} + +std::vector<optional<Value>> Let::possibleOutputs() const { + return result->possibleOutputs(); +} + +using namespace mbgl::style::conversion; + +ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + std::size_t length = arrayLength(value); + + if (length < 4) { + ctx.error("Expected at least 3 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + std::map<std::string, std::shared_ptr<Expression>> bindings_; + for(std::size_t i = 1; i < length - 1; i += 2) { + optional<std::string> name = toString(arrayMember(value, i)); + if (!name) { + ctx.error("Expected string, but found " + getJSONType(arrayMember(value, i)) + " instead.", i); + return ParseResult(); + } + + bool isValidName = std::all_of(name->begin(), name->end(), [](unsigned char c) { + return ::isalnum(c) || c == '_'; + }); + if (!isValidName) { + ctx.error("Variable names must contain only alphanumeric characters or '_'.", 1); + return ParseResult(); + } + + ParseResult bindingValue = ctx.parse(arrayMember(value, i + 1), i + 1); + if (!bindingValue) { + return ParseResult(); + } + + bindings_.emplace(*name, std::move(*bindingValue)); + } + + ParseResult result_ = ctx.parse(arrayMember(value, length - 1), length - 1, ctx.getExpected(), bindings_); + if (!result_) { + return ParseResult(); + } + + return ParseResult(std::make_unique<Let>(std::move(bindings_), std::move(*result_))); +} + +mbgl::Value Let::serialize() const { + std::vector<mbgl::Value> serialized; + serialized.emplace_back(getOperator()); + for (auto entry : bindings) { + serialized.emplace_back(entry.first); + serialized.emplace_back(entry.second->serialize()); + } + serialized.emplace_back(result->serialize()); + return serialized; +} + +EvaluationResult Var::evaluate(const EvaluationContext& params) const { + return value->evaluate(params); +} + +void Var::eachChild(const std::function<void(const Expression&)>&) const {} + +std::vector<optional<Value>> Var::possibleOutputs() const { + return { nullopt }; +} + +ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) { + assert(isArray(value_)); + + if (arrayLength(value_) != 2 || !toString(arrayMember(value_, 1))) { + ctx.error("'var' expression requires exactly one string literal argument."); + return ParseResult(); + } + + std::string name_ = *toString(arrayMember(value_, 1)); + + optional<std::shared_ptr<Expression>> bindingValue = ctx.getBinding(name_); + if (!bindingValue) { + ctx.error(R"(Unknown variable ")" + name_ + R"(". Make sure ")" + + name_ + R"(" has been bound in an enclosing "let" expression before using it.)", 1); + return ParseResult(); + } + + return ParseResult(std::make_unique<Var>(name_, std::move(*bindingValue))); +} + +mbgl::Value Var::serialize() const { + return std::vector<mbgl::Value>{{ getOperator(), name }}; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp new file mode 100644 index 0000000000..8a63980dba --- /dev/null +++ b/src/mbgl/style/expression/literal.cpp @@ -0,0 +1,116 @@ +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template <typename T> +optional<Value> checkNumber(T n) { + if (n > std::numeric_limits<double>::max()) { + return {std::numeric_limits<double>::infinity()}; + } else { + return {static_cast<double>(n)}; + } +} + +using namespace mbgl::style::conversion; +optional<Value> parseValue(const Convertible& value, ParsingContext& ctx) { + if (isUndefined(value)) return {Null}; + if (isObject(value)) { + std::unordered_map<std::string, Value> result; + bool error = false; + eachMember(value, [&] (const std::string& k, const mbgl::style::conversion::Convertible& v) -> optional<conversion::Error> { + if (!error) { + optional<Value> memberValue = parseValue(v, ctx); + if (memberValue) { + result.emplace(k, *memberValue); + } else { + error = true; + } + } + return {}; + }); + return error ? optional<Value>() : optional<Value>(result); + } + + if (isArray(value)) { + std::vector<Value> result; + const auto length = arrayLength(value); + for(std::size_t i = 0; i < length; i++) { + optional<Value> item = parseValue(arrayMember(value, i), ctx); + if (item) { + result.emplace_back(*item); + } else { + return optional<Value>(); + } + } + return optional<Value>(result); + } + + optional<mbgl::Value> v = toValue(value); + // since value represents a JSON value, if it's not undefined, object, or + // array, it must be convertible to mbgl::Value + assert(v); + + return v->match( + [&](uint64_t n) { return checkNumber(n); }, + [&](int64_t n) { return checkNumber(n); }, + [&](double n) { return checkNumber(n); }, + [&](const auto&) { + return optional<Value>(toExpressionValue(*v)); + } + ); +} + +ParseResult Literal::parse(const Convertible& value, ParsingContext& ctx) { + if (isObject(value)) { + ctx.error(R"(Bare objects invalid. Use ["literal", {...}] instead.)"); + return ParseResult(); + } else if (isArray(value)) { + // object or array value, quoted with ["literal", value] + if (arrayLength(value) != 2) { + ctx.error("'literal' expression requires exactly one argument, but found " + util::toString(arrayLength(value) - 1) + " instead."); + return ParseResult(); + } + const optional<Value> parsedValue = parseValue(arrayMember(value, 1), ctx); + if (!parsedValue) { + return ParseResult(); + } + + // special case: infer the item type if possible for zero-length arrays + if ( + ctx.getExpected() && + ctx.getExpected()->template is<type::Array>() && + parsedValue->template is<std::vector<Value>>() + ) { + auto type = typeOf(*parsedValue).template get<type::Array>(); + auto expected = ctx.getExpected()->template get<type::Array>(); + if ( + type.N && (*type.N == 0) && + (!expected.N || (*expected.N == 0)) + ) { + return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>())); + } + } + + return ParseResult(std::make_unique<Literal>(*parsedValue)); + } else { + // bare primitive value (string, number, boolean, null) + const optional<Value> parsedValue = parseValue(value, ctx); + return ParseResult(std::make_unique<Literal>(*parsedValue)); + } +} + +mbgl::Value Literal::serialize() const { + if (getType().is<type::Array>() || getType().is<type::ObjectType>()) { + return std::vector<mbgl::Value>{{ getOperator(), *fromExpressionValue<mbgl::Value>(value) }}; + } else { + return *fromExpressionValue<mbgl::Value>(value); + } +} + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp new file mode 100644 index 0000000000..3d41f0bdd3 --- /dev/null +++ b/src/mbgl/style/expression/match.cpp @@ -0,0 +1,313 @@ +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template <typename T> +void Match<T>::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); + for (const std::pair<T, std::shared_ptr<Expression>>& branch : branches) { + visit(*branch.second); + } + visit(*otherwise); +} + +template <typename T> +bool Match<T>::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Match*>(&e)) { + return (*input == *(rhs->input) && + *otherwise == *(rhs->otherwise) && + Expression::childrenEqual(branches, rhs->branches)); + } + return false; +} + +template <typename T> +std::vector<optional<Value>> Match<T>::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + +template <typename T> +mbgl::Value Match<T>::serialize() const { + std::vector<mbgl::Value> serialized; + serialized.emplace_back(getOperator()); + serialized.emplace_back(input->serialize()); + + // Sort so serialization has an arbitrary defined order, even though branch order doesn't affect evaluation + std::map<T, std::shared_ptr<Expression>> sortedBranches(branches.begin(), branches.end()); + + // Group branches by unique match expression to support condensed serializations + // of the form [case1, case2, ...] -> matchExpression + std::map<Expression*, size_t> outputLookup; + std::vector<std::pair<Expression*, std::vector<mbgl::Value>>> groupedByOutput; + for (auto& entry : sortedBranches) { + auto outputIndex = outputLookup.find(entry.second.get()); + if (outputIndex == outputLookup.end()) { + // First time seeing this output, add it to the end of the grouped list + outputLookup[entry.second.get()] = groupedByOutput.size(); + groupedByOutput.emplace_back(entry.second.get(), std::vector<mbgl::Value>{{entry.first}}); + } else { + // We've seen this expression before, add the label to that output's group + groupedByOutput[outputIndex->second].second.emplace_back(entry.first); + } + }; + + for (auto& entry : groupedByOutput) { + entry.second.size() == 1 + ? serialized.emplace_back(entry.second[0]) // Only a single label matches this output expression + : serialized.emplace_back(entry.second); // Array of literal labels pointing to this output expression + serialized.emplace_back(entry.first->serialize()); // The output expression itself + } + + serialized.emplace_back(otherwise->serialize()); + return serialized; +} + + +template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext& params) const { + const EvaluationResult inputValue = input->evaluate(params); + if (!inputValue) { + return inputValue.error(); + } + + auto it = branches.find(inputValue->get<std::string>()); + if (it != branches.end()) { + return (*it).second->evaluate(params); + } + + return otherwise->evaluate(params); +} + +template<> EvaluationResult Match<int64_t>::evaluate(const EvaluationContext& params) const { + const EvaluationResult inputValue = input->evaluate(params); + if (!inputValue) { + return inputValue.error(); + } + + const auto numeric = inputValue->get<double>(); + int64_t rounded = std::floor(numeric); + if (numeric == rounded) { + auto it = branches.find(rounded); + if (it != branches.end()) { + return (*it).second->evaluate(params); + } + } + + return otherwise->evaluate(params); +} + +template class Match<int64_t>; +template class Match<std::string>; + +using InputType = variant<int64_t, std::string>; + +using namespace mbgl::style::conversion; +optional<InputType> parseInputValue(const Convertible& input, ParsingContext& parentContext, std::size_t index, optional<type::Type>& inputType) { + using namespace mbgl::style::conversion; + optional<InputType> result; + optional<type::Type> type; + + auto value = toValue(input); + + if (value) { + value->match( + [&] (uint64_t n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (int64_t n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else { + type = {type::Number}; + result = {n}; + } + }, + [&] (double n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else if (n != std::floor(n)) { + parentContext.error("Numeric branch labels must be integer values.", index); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (const std::string& s) { + type = {type::String}; + result = {s}; + }, + [&] (const auto&) { + parentContext.error("Branch labels must be numbers or strings.", index); + } + ); + } else { + parentContext.error("Branch labels must be numbers or strings.", index); + } + + if (!type) { + return result; + } + + if (!inputType) { + inputType = type; + } else { + optional<std::string> err = type::checkSubtype(*inputType, *type); + if (err) { + parentContext.error(*err, index); + return optional<InputType>(); + } + } + + return result; +} + +template <typename T> +static ParseResult create(type::Type outputType, + std::unique_ptr<Expression>input, + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> branches, + std::unique_ptr<Expression> otherwise, + ParsingContext& ctx) { + typename Match<T>::Branches typedBranches; + + std::size_t index = 2; + + typedBranches.reserve(branches.size()); + for (std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>& pair : branches) { + std::shared_ptr<Expression> result = std::move(pair.second); + for (const InputType& label : pair.first) { + const auto& typedLabel = label.template get<T>(); + if (typedBranches.find(typedLabel) != typedBranches.end()) { + ctx.error("Branch labels must be unique.", index); + return ParseResult(); + } + typedBranches.emplace(typedLabel, result); + } + + index += 2; + } + return ParseResult(std::make_unique<Match<T>>( + outputType, + std::move(input), + std::move(typedBranches), + std::move(otherwise) + )); +} + +ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 5) { + ctx.error( + "Expected at least 4 arguments, but found only " + util::toString(length - 1) + "." + ); + return ParseResult(); + } + + // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise] + if (length % 2 != 1) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + optional<type::Type> inputType; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> branches; + + branches.reserve((length - 3) / 2); + for (size_t i = 2; i + 1 < length; i += 2) { + const auto& label = arrayMember(value, i); + + std::vector<InputType> labels; + // Match pair inputs are provided as either a literal value or a + // raw JSON array of string / number / boolean values. + if (isArray(label)) { + auto groupLength = arrayLength(label); + if (groupLength == 0) { + ctx.error("Expected at least one branch label.", i); + return ParseResult(); + } + + labels.reserve(groupLength); + for (size_t j = 0; j < groupLength; j++) { + const optional<InputType> inputValue = parseInputValue(arrayMember(label, j), ctx, i, inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + } else { + const optional<InputType> inputValue = parseInputValue(label, ctx, i, inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + + ParseResult output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + branches.push_back(std::make_pair(std::move(labels), std::move(*output))); + } + + auto input = ctx.parse(arrayMember(value, 1), 1, inputType); + if (!input) { + return ParseResult(); + } + + auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType); + if (!otherwise) { + return ParseResult(); + } + + assert(inputType && outputType); + + return inputType->match( + [&](const type::NumberType&) { + return create<int64_t>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); + }, + [&](const type::StringType&) { + return create<std::string>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); + }, + [&](const auto&) { + // unreachable: inputType is set by parseInputValue(), which only + // accepts string and (integer) numeric values. + assert(false); + return ParseResult(); + } + ); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp new file mode 100644 index 0000000000..b522aeff9a --- /dev/null +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -0,0 +1,256 @@ + +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/type.hpp> + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/at.hpp> +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/assertion.hpp> +#include <mbgl/style/expression/boolean_operator.hpp> +#include <mbgl/style/expression/case.hpp> +#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/coercion.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/equals.hpp> +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/style/expression/length.hpp> +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/step.hpp> + +#include <mbgl/style/expression/find_zoom_curve.hpp> + +#include <mbgl/style/conversion/get_json_type.hpp> + +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +bool isConstant(const Expression& expression) { + if (auto varExpression = dynamic_cast<const Var*>(&expression)) { + return isConstant(*varExpression->getBoundExpression()); + } + + if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) { + if (compound->getName() == "error") { + return false; + } + } + + bool isTypeAnnotation = dynamic_cast<const Coercion*>(&expression) || + dynamic_cast<const Assertion*>(&expression) || + dynamic_cast<const ArrayAssertion*>(&expression); + + bool childrenConstant = true; + expression.eachChild([&](const Expression& child) { + // We can _almost_ assume that if `expressions` children are constant, + // they would already have been evaluated to Literal values when they + // were parsed. Type annotations are the exception, because they might + // have been inferred and added after a child was parsed. + + // So we recurse into isConstant() for the children of type annotations, + // but otherwise simply check whether they are Literals. + if (isTypeAnnotation) { + childrenConstant = childrenConstant && isConstant(child); + } else { + childrenConstant = childrenConstant && dynamic_cast<const Literal*>(&child); + } + }); + if (!childrenConstant) { + return false; + } + + return isFeatureConstant(expression) && + isGlobalPropertyConstant(expression, std::array<std::string, 2>{{"zoom", "heatmap-density"}}); +} + +using namespace mbgl::style::conversion; + +ParseResult ParsingContext::parse(const Convertible& value, + std::size_t index_, + optional<type::Type> expected_, + TypeAnnotationOption typeAnnotationOption) { + ParsingContext child(key + "[" + util::toString(index_) + "]", + errors, + std::move(expected_), + scope); + return child.parse(value, typeAnnotationOption); +} + +ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_, optional<type::Type> expected_, + const std::map<std::string, std::shared_ptr<Expression>>& bindings) { + ParsingContext child(key + "[" + util::toString(index_) + "]", + errors, + std::move(expected_), + std::make_shared<detail::Scope>(bindings, scope)); + return child.parse(value); +} + +const ExpressionRegistry& getExpressionRegistry() { + static ExpressionRegistry registry {{ + {"==", Equals::parse}, + {"!=", Equals::parse}, + {"all", All::parse}, + {"any", Any::parse}, + {"array", ArrayAssertion::parse}, + {"at", At::parse}, + {"boolean", Assertion::parse}, + {"case", Case::parse}, + {"coalesce", Coalesce::parse}, + {"interpolate", parseInterpolate}, + {"length", Length::parse}, + {"let", Let::parse}, + {"literal", Literal::parse}, + {"match", parseMatch}, + {"number", Assertion::parse}, + {"object", Assertion::parse}, + {"step", Step::parse}, + {"string", Assertion::parse}, + {"to-color", Coercion::parse}, + {"to-number", Coercion::parse}, + {"var", Var::parse} + }}; + return registry; +} + +ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption typeAnnotationOption) { + ParseResult parsed; + + if (isArray(value)) { + const std::size_t length = arrayLength(value); + if (length == 0) { + error(R"(Expected an array with at least one element. If you wanted a literal array, use ["literal", []].)"); + return ParseResult(); + } + + const optional<std::string> op = toString(arrayMember(value, 0)); + if (!op) { + error( + "Expression name must be a string, but found " + getJSONType(arrayMember(value, 0)) + + R"( instead. If you wanted a literal array, use ["literal", [...]].)", + 0 + ); + return ParseResult(); + } + + const ExpressionRegistry& registry = getExpressionRegistry(); + auto parseFunction = registry.find(*op); + if (parseFunction != registry.end()) { + parsed = parseFunction->second(value, *this); + } else { + parsed = parseCompoundExpression(*op, value, *this); + } + } else { + parsed = Literal::parse(value, *this); + } + + if (!parsed) { + assert(errors->size() > 0); + return parsed; + } + + auto array = [&](std::unique_ptr<Expression> expression) { + std::vector<std::unique_ptr<Expression>> args; + args.push_back(std::move(expression)); + return args; + }; + + if (expected) { + const type::Type actual = (*parsed)->getType(); + if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean || *expected == type::Object) && actual == type::Value) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<Assertion>(*expected, array(std::move(*parsed))) }; + } + } else if (expected->is<type::Array>() && actual == type::Value) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<ArrayAssertion>(expected->get<type::Array>(), std::move(*parsed)) }; + } + } else if (*expected == type::Color && (actual == type::Value || actual == type::String)) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<Coercion>(*expected, array(std::move(*parsed))) }; + } + } else { + checkType((*parsed)->getType()); + if (errors->size() > 0) { + return ParseResult(); + } + } + } + + // If an expression's arguments are all constant, we can evaluate + // it immediately and replace it with a literal value in the + // parsed result. + if (!dynamic_cast<Literal *>(parsed->get()) && isConstant(**parsed)) { + EvaluationContext params(nullptr); + EvaluationResult evaluated((*parsed)->evaluate(params)); + if (!evaluated) { + error(evaluated.error().message); + return ParseResult(); + } + + const type::Type type = (*parsed)->getType(); + if (type.is<type::Array>()) { + // keep the original expression's array type, even if the evaluated + // type is more specific. + return ParseResult(std::make_unique<Literal>( + type.get<type::Array>(), + evaluated->get<std::vector<Value>>()) + ); + } else { + return ParseResult(std::make_unique<Literal>(*evaluated)); + } + } + + return parsed; +} + +ParseResult ParsingContext::parseExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) { + return parse(value, typeAnnotationOption); +} + +ParseResult ParsingContext::parseLayerPropertyExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) { + ParseResult parsed = parse(value, typeAnnotationOption); + if (parsed && !isZoomConstant(**parsed)) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> zoomCurve = findZoomCurve(parsed->get()); + if (!zoomCurve) { + error(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)"); + return ParseResult(); + } else if (zoomCurve->is<ParsingError>()) { + error(zoomCurve->get<ParsingError>().message); + return ParseResult(); + } + } + return parsed; +} + +const std::string ParsingContext::getCombinedErrors() const { + std::string combinedError; + for (const ParsingError& parsingError : *errors) { + if (combinedError.size() > 0) { + combinedError += "\n"; + } + if (parsingError.key.size() > 0) { + combinedError += parsingError.key + ": "; + } + combinedError += parsingError.message; + } + return combinedError; +} + +optional<std::string> ParsingContext::checkType(const type::Type& t) { + assert(expected); + optional<std::string> err = type::checkSubtype(*expected, t); + if (err) { + error(*err); + } + return err; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp new file mode 100644 index 0000000000..ddaf9417cb --- /dev/null +++ b/src/mbgl/style/expression/step.cpp @@ -0,0 +1,187 @@ +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/get_covering_stops.hpp> +#include <mbgl/util/string.hpp> + +#include <cmath> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Step::evaluate(const EvaluationContext& params) const { + const EvaluationResult evaluatedInput = input->evaluate(params); + if (!evaluatedInput) { + return evaluatedInput.error(); + } + + float x = *fromExpressionValue<float>(*evaluatedInput); + if (std::isnan(x)) { + return EvaluationError { "Input is not a number." }; + } + + if (stops.empty()) { + return EvaluationError { "No stops in step curve." }; + } + + auto it = stops.upper_bound(x); + if (it == stops.end()) { + return stops.rbegin()->second->evaluate(params); + } else if (it == stops.begin()) { + return stops.begin()->second->evaluate(params); + } else { + return std::prev(it)->second->evaluate(params); + } +} + +void Step::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); + for (auto it = stops.begin(); it != stops.end(); it++) { + visit(*it->second); + } +} + +void Step::eachStop(const std::function<void(double, const Expression&)>& visit) const { + for (const auto &stop : stops) { + visit(stop.first, *stop.second); + } +} + +bool Step::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Step*>(&e)) { + return *input == *(rhs->input) && Expression::childrenEqual(stops, rhs->stops); + } + return false; +} + +std::vector<optional<Value>> Step::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +Range<float> Step::getCoveringStops(const double lower, const double upper) const { + return ::mbgl::style::expression::getCoveringStops(stops, lower, upper); +} + + +ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + auto length = arrayLength(value); + + if (length - 1 < 4) { + ctx.error("Expected at least 4 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // [step, input, firstOutput_value, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 1), 1, {type::Number}); + if (!input) { + return input; + } + + std::map<double, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + double previous = - std::numeric_limits<double>::infinity(); + + // consume the first output value, which doesn't have a corresponding input value, + // before proceeding into the "stops" loop below. + auto firstOutput = ctx.parse(arrayMember(value, 2), 2, outputType); + if (!firstOutput) { + return ParseResult(); + } + if (!outputType) { + outputType = (*firstOutput)->getType(); + } + stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*firstOutput)); + + + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); + optional<double> label; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(R"(Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", i); + return ParseResult(); + } + + if (*label <= previous) { + ctx.error( + R"(Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops))); +} + +mbgl::Value Step::serialize() const { + std::vector<mbgl::Value> serialized; + serialized.emplace_back(getOperator()); + serialized.emplace_back(input->serialize()); + for (auto& entry : stops) { + if (entry.first > -std::numeric_limits<double>::infinity()) { + serialized.emplace_back(entry.first); + } + serialized.emplace_back(entry.second->serialize()); + } + return serialized; +} + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/util.cpp b/src/mbgl/style/expression/util.cpp new file mode 100644 index 0000000000..ee680dab08 --- /dev/null +++ b/src/mbgl/style/expression/util.cpp @@ -0,0 +1,37 @@ +#include <mbgl/style/expression/util.hpp> +#include <mbgl/style/expression/value.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +std::string stringifyColor(double r, double g, double b, double a) { + return stringify(r) + ", " + + stringify(g) + ", " + + stringify(b) + ", " + + stringify(a); +} + +Result<Color> rgba(double r, double g, double b, double a) { + if ( + r < 0 || r > 255 || + g < 0 || g > 255 || + b < 0 || b > 255 + ) { + return EvaluationError { + "Invalid rgba value [" + stringifyColor(r, g, b, a) + + "]: 'r', 'g', and 'b' must be between 0 and 255." + }; + } + if (a < 0 || a > 1) { + return EvaluationError { + "Invalid rgba value [" + stringifyColor(r, g, b, a) + + "]: 'a' must be between 0 and 1." + }; + } + return Color(r / 255 * a, g / 255 * a, b / 255 * a, a); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/util.hpp b/src/mbgl/style/expression/util.hpp new file mode 100644 index 0000000000..b6fc408ed9 --- /dev/null +++ b/src/mbgl/style/expression/util.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/util/color.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Result<Color> rgba(double r, double g, double b, double a); + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp new file mode 100644 index 0000000000..1b3257c755 --- /dev/null +++ b/src/mbgl/style/expression/value.cpp @@ -0,0 +1,354 @@ +#include <rapidjson/writer.h> +#include <rapidjson/stringbuffer.h> +#include <mbgl/style/expression/value.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +type::Type typeOf(const Value& value) { + return value.match( + [&](bool) -> type::Type { return type::Boolean; }, + [&](double) -> type::Type { return type::Number; }, + [&](const std::string&) -> type::Type { return type::String; }, + [&](const Color&) -> type::Type { return type::Color; }, + [&](const NullValue&) -> type::Type { return type::Null; }, + [&](const std::unordered_map<std::string, Value>&) -> type::Type { return type::Object; }, + [&](const std::vector<Value>& arr) -> type::Type { + optional<type::Type> itemType; + for (const auto& item : arr) { + const type::Type t = typeOf(item); + if (!itemType) { + itemType = {t}; + } else if (*itemType == t) { + continue; + } else { + itemType = {type::Value}; + break; + } + } + + return type::Array(itemType.value_or(type::Value), arr.size()); + } + ); +} + +void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value& value) { + value.match( + [&] (const NullValue&) { writer.Null(); }, + [&] (bool b) { writer.Bool(b); }, + [&] (double f) { + // make sure integer values are stringified without trailing ".0". + f == std::floor(f) ? writer.Int(f) : writer.Double(f); + }, + [&] (const std::string& s) { writer.String(s); }, + [&] (const Color& c) { writer.String(c.stringify()); }, + [&] (const std::vector<Value>& arr) { + writer.StartArray(); + for(const auto& item : arr) { + writeJSON(writer, item); + } + writer.EndArray(); + }, + [&] (const std::unordered_map<std::string, Value>& obj) { + writer.StartObject(); + for(const auto& entry : obj) { + writer.Key(entry.first.c_str()); + writeJSON(writer, entry.second); + } + writer.EndObject(); + } + ); +} + +std::string stringify(const Value& value) { + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + writeJSON(writer, value); + return buffer.GetString(); +} + +struct FromMBGLValue { + Value operator()(const std::vector<mbgl::Value>& v) { + std::vector<Value> result; + result.reserve(v.size()); + for(const auto& item : v) { + result.emplace_back(toExpressionValue(item)); + } + return result; + } + + Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) { + std::unordered_map<std::string, Value> result; + result.reserve(v.size()); + for(const auto& entry : v) { + result.emplace(entry.first, toExpressionValue(entry.second)); + } + return result; + } + + Value operator()(const std::string& s) { return s; } + Value operator()(const bool b) { return b; } + Value operator()(const NullValue) { return Null; } + Value operator()(const double v) { return v; } + Value operator()(const uint64_t& v) { + return static_cast<double>(v); + } + Value operator()(const int64_t& v) { + return static_cast<double>(v); + } +}; + +Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) { + return mbgl::Value::visit(value, FromMBGLValue()); +} + +mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value) { + return value.match( + [&](const Color& color)->mbgl::Value { + std::array<double, 4> array = color.toArray(); + return std::vector<mbgl::Value>{ + std::string("rgba"), + array[0], + array[1], + array[2], + array[3], + }; + }, + [&](const std::vector<Value>& values)->mbgl::Value { + std::vector<mbgl::Value> converted; + converted.reserve(values.size()); + for (const Value& v : values) { + converted.emplace_back(fromExpressionValue(v)); + } + return converted; + }, + [&](const std::unordered_map<std::string, Value>& values)->mbgl::Value { + std::unordered_map<std::string, mbgl::Value> converted; + converted.reserve(values.size()); + for(const auto& entry : values) { + converted.emplace(entry.first, fromExpressionValue(entry.second)); + } + return converted; + }, + [&](const auto& a)->mbgl::Value { return a; } + ); +} + +Value ValueConverter<float>::toExpressionValue(const float value) { + return static_cast<double>(value); +} + +optional<float> ValueConverter<float>::fromExpressionValue(const Value& value) { + return value.template is<double>() + ? static_cast<float>(value.template get<double>()) + : optional<float>(); +} + + +template <typename T, typename Container> +std::vector<Value> toArrayValue(const Container& value) { + std::vector<Value> result; + result.reserve(value.size()); + for (const T& item : value) { + result.push_back(ValueConverter<T>::toExpressionValue(item)); + } + return result; +} + +template <typename T, std::size_t N> +Value ValueConverter<std::array<T, N>>::toExpressionValue(const std::array<T, N>& value) { + return toArrayValue<T>(value); +} + +template <typename T, std::size_t N> +optional<std::array<T, N>> ValueConverter<std::array<T, N>>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::vector<Value>& v) -> optional<std::array<T, N>> { + if (v.size() != N) return optional<std::array<T, N>>(); + std::array<T, N> result; + auto it = result.begin(); + for(const Value& item : v) { + optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::array<T, N>>(); + } + *it = *convertedItem; + it = std::next(it); + } + return result; + }, + [&] (const auto&) { return optional<std::array<T, N>>(); } + ); +} + + +template <typename T> +Value ValueConverter<std::vector<T>>::toExpressionValue(const std::vector<T>& value) { + return toArrayValue<T>(value); +} + +template <typename T> +optional<std::vector<T>> ValueConverter<std::vector<T>>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::vector<Value>& v) -> optional<std::vector<T>> { + std::vector<T> result; + result.reserve(v.size()); + for(const Value& item : v) { + optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::vector<T>>(); + } + result.push_back(*convertedItem); + } + return result; + }, + [&] (const auto&) { return optional<std::vector<T>>(); } + ); +} + +Value ValueConverter<Position>::toExpressionValue(const mbgl::style::Position& value) { + return ValueConverter<std::array<float, 3>>::toExpressionValue(value.getSpherical()); +} + +optional<Position> ValueConverter<Position>::fromExpressionValue(const Value& v) { + auto pos = ValueConverter<std::array<float, 3>>::fromExpressionValue(v); + return pos ? optional<Position>(Position(*pos)) : optional<Position>(); +} + +template <typename T> +Value ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::toExpressionValue(const T& value) { + return std::string(Enum<T>::toString(value)); +} + +template <typename T> +optional<T> ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::string& v) { return Enum<T>::toEnum(v); }, + [&] (const auto&) { return optional<T>(); } + ); +} + + +Value toExpressionValue(const Value& v) { + return v; +} + +template <typename T, typename Enable> +Value toExpressionValue(const T& value) { + return ValueConverter<T>::toExpressionValue(value); +} + +optional<Value> fromExpressionValue(const Value& v) { + return optional<Value>(v); +} + +template <typename T> +std::enable_if_t< !std::is_convertible<T, Value>::value, +optional<T>> fromExpressionValue(const Value& v) +{ + return ValueConverter<T>::fromExpressionValue(v); +} + +template <typename T> +type::Type valueTypeToExpressionType() { + return ValueConverter<T>::expressionType(); +} + +template <> type::Type valueTypeToExpressionType<Value>() { return type::Value; } +template <> type::Type valueTypeToExpressionType<NullValue>() { return type::Null; } +template <> type::Type valueTypeToExpressionType<bool>() { return type::Boolean; } +template <> type::Type valueTypeToExpressionType<double>() { return type::Number; } +template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; } +template <> type::Type valueTypeToExpressionType<Color>() { return type::Color; } +template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; } +template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); } + +// used only for the special (and private) "error" expression +template <> type::Type valueTypeToExpressionType<type::ErrorType>() { return type::Error; } + + +template Value toExpressionValue(const mbgl::Value&); +template optional<mbgl::Value> fromExpressionValue<mbgl::Value>(const Value&); + +// for to_rgba expression +template type::Type valueTypeToExpressionType<std::array<double, 4>>(); +template optional<std::array<double, 4>> fromExpressionValue<std::array<double, 4>>(const Value&); +template Value toExpressionValue(const std::array<double, 4>&); + +// layout/paint property types +template type::Type valueTypeToExpressionType<float>(); +template optional<float> fromExpressionValue<float>(const Value&); +template Value toExpressionValue(const float&); + +template type::Type valueTypeToExpressionType<std::array<float, 2>>(); +template optional<std::array<float, 2>> fromExpressionValue<std::array<float, 2>>(const Value&); +template Value toExpressionValue(const std::array<float, 2>&); + +template type::Type valueTypeToExpressionType<std::array<float, 4>>(); +template optional<std::array<float, 4>> fromExpressionValue<std::array<float, 4>>(const Value&); +template Value toExpressionValue(const std::array<float, 4>&); + +template type::Type valueTypeToExpressionType<std::vector<float>>(); +template optional<std::vector<float>> fromExpressionValue<std::vector<float>>(const Value&); +template Value toExpressionValue(const std::vector<float>&); + +template type::Type valueTypeToExpressionType<std::vector<std::string>>(); +template optional<std::vector<std::string>> fromExpressionValue<std::vector<std::string>>(const Value&); +template Value toExpressionValue(const std::vector<std::string>&); + +template type::Type valueTypeToExpressionType<AlignmentType>(); +template optional<AlignmentType> fromExpressionValue<AlignmentType>(const Value&); +template Value toExpressionValue(const AlignmentType&); + +template type::Type valueTypeToExpressionType<CirclePitchScaleType>(); +template optional<CirclePitchScaleType> fromExpressionValue<CirclePitchScaleType>(const Value&); +template Value toExpressionValue(const CirclePitchScaleType&); + +template type::Type valueTypeToExpressionType<IconTextFitType>(); +template optional<IconTextFitType> fromExpressionValue<IconTextFitType>(const Value&); +template Value toExpressionValue(const IconTextFitType&); + +template type::Type valueTypeToExpressionType<LineCapType>(); +template optional<LineCapType> fromExpressionValue<LineCapType>(const Value&); +template Value toExpressionValue(const LineCapType&); + +template type::Type valueTypeToExpressionType<LineJoinType>(); +template optional<LineJoinType> fromExpressionValue<LineJoinType>(const Value&); +template Value toExpressionValue(const LineJoinType&); + +template type::Type valueTypeToExpressionType<SymbolPlacementType>(); +template optional<SymbolPlacementType> fromExpressionValue<SymbolPlacementType>(const Value&); +template Value toExpressionValue(const SymbolPlacementType&); + +template type::Type valueTypeToExpressionType<SymbolAnchorType>(); +template optional<SymbolAnchorType> fromExpressionValue<SymbolAnchorType>(const Value&); +template Value toExpressionValue(const SymbolAnchorType&); + +template type::Type valueTypeToExpressionType<TextJustifyType>(); +template optional<TextJustifyType> fromExpressionValue<TextJustifyType>(const Value&); +template Value toExpressionValue(const TextJustifyType&); + +template type::Type valueTypeToExpressionType<TextTransformType>(); +template optional<TextTransformType> fromExpressionValue<TextTransformType>(const Value&); +template Value toExpressionValue(const TextTransformType&); + +template type::Type valueTypeToExpressionType<TranslateAnchorType>(); +template optional<TranslateAnchorType> fromExpressionValue<TranslateAnchorType>(const Value&); +template Value toExpressionValue(const TranslateAnchorType&); + +template type::Type valueTypeToExpressionType<HillshadeIlluminationAnchorType>(); +template optional<HillshadeIlluminationAnchorType> fromExpressionValue<HillshadeIlluminationAnchorType>(const Value&); +template Value toExpressionValue(const HillshadeIlluminationAnchorType&); + +template type::Type valueTypeToExpressionType<LightAnchorType>(); +template optional<LightAnchorType> fromExpressionValue<LightAnchorType>(const Value&); +template Value toExpressionValue(const LightAnchorType&); + +template type::Type valueTypeToExpressionType<Position>(); +template optional<Position> fromExpressionValue<Position>(const Value&); +template Value toExpressionValue(const Position&); + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/filter.cpp b/src/mbgl/style/filter.cpp new file mode 100644 index 0000000000..51aa6bcf82 --- /dev/null +++ b/src/mbgl/style/filter.cpp @@ -0,0 +1,13 @@ +#include <mbgl/style/filter.hpp> +#include <mbgl/style/filter_evaluator.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> + +namespace mbgl { +namespace style { + +bool Filter::operator()(const expression::EvaluationContext &context) const { + return FilterBase::visit(*this, FilterEvaluator { context }); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/filter_evaluator.cpp b/src/mbgl/style/filter_evaluator.cpp new file mode 100644 index 0000000000..72022172f4 --- /dev/null +++ b/src/mbgl/style/filter_evaluator.cpp @@ -0,0 +1,225 @@ +#include <mbgl/style/filter.hpp> +#include <mbgl/style/filter_evaluator.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> + +namespace mbgl { +namespace style { + +template <class Op> +struct Comparator { + const Op& op; + + template <class T> + bool operator()(const T& lhs, const T& rhs) const { + return op(lhs, rhs); + } + + template <class T0, class T1> + auto operator()(const T0& lhs, const T1& rhs) const + -> typename std::enable_if_t<std::is_arithmetic<T0>::value && !std::is_same<T0, bool>::value && + std::is_arithmetic<T1>::value && !std::is_same<T1, bool>::value, bool> { + return op(double(lhs), double(rhs)); + } + + template <class T0, class T1> + auto operator()(const T0&, const T1&) const + -> typename std::enable_if_t<!std::is_arithmetic<T0>::value || std::is_same<T0, bool>::value || + !std::is_arithmetic<T1>::value || std::is_same<T1, bool>::value, bool> { + return false; + } + + bool operator()(const NullValue&, + const NullValue&) const { + // Should be unreachable; null is not currently allowed by the style specification. + assert(false); + return false; + } + + bool operator()(const std::vector<Value>&, + const std::vector<Value>&) const { + // Should be unreachable; nested values are not currently allowed by the style specification. + assert(false); + return false; + } + + bool operator()(const PropertyMap&, + const PropertyMap&) const { + // Should be unreachable; nested values are not currently allowed by the style specification. + assert(false); + return false; + } +}; + +template <class Op> +bool compare(const Value& lhs, const Value& rhs, const Op& op) { + return Value::binary_visit(lhs, rhs, Comparator<Op> { op }); +} + +bool equal(const Value& lhs, const Value& rhs) { + return compare(lhs, rhs, [] (const auto& lhs_, const auto& rhs_) { return lhs_ == rhs_; }); +} + +bool FilterEvaluator::operator()(const NullFilter&) const { + return true; +} + +bool FilterEvaluator::operator()(const EqualsFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return actual && equal(*actual, filter.value); +} + +bool FilterEvaluator::operator()(const NotEqualsFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return !actual || !equal(*actual, filter.value); +} + +bool FilterEvaluator::operator()(const LessThanFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ < rhs_; }); +} + +bool FilterEvaluator::operator()(const LessThanEqualsFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ <= rhs_; }); +} + +bool FilterEvaluator::operator()(const GreaterThanFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ > rhs_; }); +} + +bool FilterEvaluator::operator()(const GreaterThanEqualsFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ >= rhs_; }); +} + +bool FilterEvaluator::operator()(const InFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + if (!actual) + return false; + for (const auto& v: filter.values) { + if (equal(*actual, v)) { + return true; + } + } + return false; +} + +bool FilterEvaluator::operator()(const NotInFilter& filter) const { + optional<Value> actual = context.feature->getValue(filter.key); + if (!actual) + return true; + for (const auto& v: filter.values) { + if (equal(*actual, v)) { + return false; + } + } + return true; +} + +bool FilterEvaluator::operator()(const AnyFilter& filter) const { + for (const auto& f: filter.filters) { + if (Filter::visit(f, *this)) { + return true; + } + } + return false; +} + +bool FilterEvaluator::operator()(const AllFilter& filter) const { + for (const auto& f: filter.filters) { + if (!Filter::visit(f, *this)) { + return false; + } + } + return true; +} + +bool FilterEvaluator::operator()(const NoneFilter& filter) const { + for (const auto& f: filter.filters) { + if (Filter::visit(f, *this)) { + return false; + } + } + return true; +} + +bool FilterEvaluator::operator()(const HasFilter& filter) const { + return bool(context.feature->getValue(filter.key)); +} + +bool FilterEvaluator::operator()(const NotHasFilter& filter) const { + return !context.feature->getValue(filter.key); +} + +bool FilterEvaluator::operator()(const TypeEqualsFilter& filter) const { + return context.feature->getType() == filter.value; +} + +bool FilterEvaluator::operator()(const TypeNotEqualsFilter& filter) const { + return context.feature->getType() != filter.value; +} + +bool FilterEvaluator::operator()(const TypeInFilter& filter) const { + for (const auto& v: filter.values) { + if (context.feature->getType() == v) { + return true; + } + } + return false; +} + +bool FilterEvaluator::operator()(const TypeNotInFilter& filter) const { + for (const auto& v: filter.values) { + if (context.feature->getType() == v) { + return false; + } + } + return true; +} + +bool FilterEvaluator::operator()(const IdentifierEqualsFilter& filter) const { + return context.feature->getID() == filter.value; +} + +bool FilterEvaluator::operator()(const IdentifierNotEqualsFilter& filter) const { + return context.feature->getID() != filter.value; +} + +bool FilterEvaluator::operator()(const IdentifierInFilter& filter) const { + for (const auto& v: filter.values) { + if (context.feature->getID() == v) { + return true; + } + } + return false; +} + +bool FilterEvaluator::operator()(const IdentifierNotInFilter& filter) const { + for (const auto& v: filter.values) { + if (context.feature->getID() == v) { + return false; + } + } + return true; +} + +bool FilterEvaluator::operator()(const HasIdentifierFilter&) const { + return bool(context.feature->getID()); +} + +bool FilterEvaluator::operator()(const NotHasIdentifierFilter&) const { + return !context.feature->getID(); +} + +bool FilterEvaluator::operator()(const ExpressionFilter& filter) const { + const expression::EvaluationResult result = filter.expression->evaluate(context); + if (result) { + const optional<bool> typed = expression::fromExpressionValue<bool>(*result); + return typed ? *typed : false; + } + return false; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp new file mode 100644 index 0000000000..d9dbbfa1d3 --- /dev/null +++ b/src/mbgl/style/function/expression.cpp @@ -0,0 +1,38 @@ +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +class GeoJSONFeature : public GeometryTileFeature { +public: + const Feature& feature; + + GeoJSONFeature(const Feature& feature_) : feature(feature_) {} + + FeatureType getType() const override { + return apply_visitor(ToFeatureType(), feature.geometry); + } + PropertyMap getProperties() const override { return feature.properties; } + optional<FeatureIdentifier> getID() const override { return feature.id; } + GeometryCollection getGeometries() const override { return {}; } + optional<mbgl::Value> getValue(const std::string& key) const override { + auto it = feature.properties.find(key); + if (it != feature.properties.end()) { + return optional<mbgl::Value>(it->second); + } + return optional<mbgl::Value>(); + } +}; + + +EvaluationResult Expression::evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const { + GeoJSONFeature f(feature); + return this->evaluate(EvaluationContext(zoom, &f, heatmapDensity)); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/background_layer.cpp b/src/mbgl/style/layers/background_layer.cpp index d4ead18816..66ab46c078 100644 --- a/src/mbgl/style/layers/background_layer.cpp +++ b/src/mbgl/style/layers/background_layer.cpp @@ -53,12 +53,14 @@ void BackgroundLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void BackgroundLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/circle_layer.cpp b/src/mbgl/style/layers/circle_layer.cpp index 9854932699..6dd744df1f 100644 --- a/src/mbgl/style/layers/circle_layer.cpp +++ b/src/mbgl/style/layers/circle_layer.cpp @@ -81,12 +81,14 @@ void CircleLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void CircleLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/custom_layer.cpp b/src/mbgl/style/layers/custom_layer.cpp index 854c771847..0e51a70e50 100644 --- a/src/mbgl/style/layers/custom_layer.cpp +++ b/src/mbgl/style/layers/custom_layer.cpp @@ -6,20 +6,8 @@ namespace mbgl { namespace style { CustomLayer::CustomLayer(const std::string& layerID, - CustomLayerInitializeFunction init, - CustomLayerRenderFunction render, - CustomLayerContextLostFunction contextLost, - CustomLayerDeinitializeFunction deinit, - void* context) - : Layer(makeMutable<Impl>(layerID, init, render, contextLost, deinit, context)) { -} - -CustomLayer::CustomLayer(const std::string& layerID, - CustomLayerInitializeFunction init, - CustomLayerRenderFunction render, - CustomLayerDeinitializeFunction deinit, - void* context) - : Layer(makeMutable<Impl>(layerID, init, render, nullptr, deinit, context)) { + std::unique_ptr<CustomLayerHost> host) + : Layer(makeMutable<Impl>(layerID, std::move(host))) { } CustomLayer::~CustomLayer() = default; diff --git a/src/mbgl/style/layers/custom_layer_impl.cpp b/src/mbgl/style/layers/custom_layer_impl.cpp index 1de268d2e2..05c41623c4 100644 --- a/src/mbgl/style/layers/custom_layer_impl.cpp +++ b/src/mbgl/style/layers/custom_layer_impl.cpp @@ -4,17 +4,9 @@ namespace mbgl { namespace style { CustomLayer::Impl::Impl(const std::string& id_, - CustomLayerInitializeFunction initializeFn_, - CustomLayerRenderFunction renderFn_, - CustomLayerContextLostFunction contextLostFn_, - CustomLayerDeinitializeFunction deinitializeFn_, - void* context_) + std::unique_ptr<CustomLayerHost> host_) : Layer::Impl(LayerType::Custom, id_, std::string()) { - initializeFn = initializeFn_; - renderFn = renderFn_; - deinitializeFn = deinitializeFn_; - contextLostFn = contextLostFn_; - context = context_; + host = std::move(host_); } bool CustomLayer::Impl::hasLayoutDifference(const Layer::Impl&) const { diff --git a/src/mbgl/style/layers/custom_layer_impl.hpp b/src/mbgl/style/layers/custom_layer_impl.hpp index 62efbbe15b..a41962c276 100644 --- a/src/mbgl/style/layers/custom_layer_impl.hpp +++ b/src/mbgl/style/layers/custom_layer_impl.hpp @@ -3,6 +3,8 @@ #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/layers/custom_layer.hpp> +#include <memory> + namespace mbgl { class TransformState; @@ -12,20 +14,12 @@ namespace style { class CustomLayer::Impl : public Layer::Impl { public: Impl(const std::string& id, - CustomLayerInitializeFunction, - CustomLayerRenderFunction, - CustomLayerContextLostFunction, - CustomLayerDeinitializeFunction, - void* context); + std::unique_ptr<CustomLayerHost> host); bool hasLayoutDifference(const Layer::Impl&) const override; void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override; - CustomLayerInitializeFunction initializeFn = nullptr; - CustomLayerRenderFunction renderFn = nullptr; - CustomLayerContextLostFunction contextLostFn = nullptr; - CustomLayerDeinitializeFunction deinitializeFn = nullptr; - void* context = nullptr; + std::shared_ptr<CustomLayerHost> host; }; } // namespace style diff --git a/src/mbgl/style/layers/fill_extrusion_layer.cpp b/src/mbgl/style/layers/fill_extrusion_layer.cpp index 62f92cef75..c5b4ef0ef3 100644 --- a/src/mbgl/style/layers/fill_extrusion_layer.cpp +++ b/src/mbgl/style/layers/fill_extrusion_layer.cpp @@ -81,12 +81,14 @@ void FillExtrusionLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void FillExtrusionLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/fill_layer.cpp b/src/mbgl/style/layers/fill_layer.cpp index 65975752db..99a2a51ed0 100644 --- a/src/mbgl/style/layers/fill_layer.cpp +++ b/src/mbgl/style/layers/fill_layer.cpp @@ -81,12 +81,14 @@ void FillLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void FillLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp new file mode 100644 index 0000000000..3f7881ddd3 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer.cpp @@ -0,0 +1,241 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/style/layer_observer.hpp> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> + +namespace mbgl { +namespace style { + +HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable<Impl>(LayerType::Heatmap, layerID, sourceID)) { +} + +HeatmapLayer::HeatmapLayer(Immutable<Impl> impl_) + : Layer(std::move(impl_)) { +} + +HeatmapLayer::~HeatmapLayer() = default; + +const HeatmapLayer::Impl& HeatmapLayer::impl() const { + return static_cast<const Impl&>(*baseImpl); +} + +Mutable<HeatmapLayer::Impl> HeatmapLayer::mutableImpl() const { + return makeMutable<Impl>(impl()); +} + +std::unique_ptr<Layer> HeatmapLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HeatmapPaintProperties::Transitionable(); + return std::make_unique<HeatmapLayer>(std::move(impl_)); +} + +void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const { +} + +// Source + +const std::string& HeatmapLayer::getSourceID() const { + return impl().source; +} + +void HeatmapLayer::setSourceLayer(const std::string& sourceLayer) { + auto impl_ = mutableImpl(); + impl_->sourceLayer = sourceLayer; + baseImpl = std::move(impl_); +} + +const std::string& HeatmapLayer::getSourceLayer() const { + return impl().sourceLayer; +} + +// Filter + +void HeatmapLayer::setFilter(const Filter& filter) { + auto impl_ = mutableImpl(); + impl_->filter = filter; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +const Filter& HeatmapLayer::getFilter() const { + return impl().filter; +} + +// Visibility + +void HeatmapLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HeatmapLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Layout properties + + +// Paint properties + +DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapRadius() { + return { 30 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapRadius() const { + return impl().paint.template get<HeatmapRadius>().value; +} + +void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapRadius()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const { + return impl().paint.template get<HeatmapRadius>().options; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapWeight() { + return { 1 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapWeight() const { + return impl().paint.template get<HeatmapWeight>().value; +} + +void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapWeight()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const { + return impl().paint.template get<HeatmapWeight>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapIntensity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapIntensity() const { + return impl().paint.template get<HeatmapIntensity>().value; +} + +void HeatmapLayer::setHeatmapIntensity(PropertyValue<float> value) { + if (value == getHeatmapIntensity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const { + return impl().paint.template get<HeatmapIntensity>().options; +} + +HeatmapColorPropertyValue HeatmapLayer::getDefaultHeatmapColor() { + conversion::Error error; + std::string rawValue = R"JSON(["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",0.1,"royalblue",0.3,"cyan",0.5,"lime",0.7,"yellow",1,"red"])JSON"; + return *conversion::convertJSON<HeatmapColorPropertyValue>(rawValue, error); +} + +HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const { + return impl().paint.template get<HeatmapColor>().value; +} + +void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) { + if (value == getHeatmapColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapColorTransition() const { + return impl().paint.template get<HeatmapColor>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapOpacity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapOpacity() const { + return impl().paint.template get<HeatmapOpacity>().value; +} + +void HeatmapLayer::setHeatmapOpacity(PropertyValue<float> value) { + if (value == getHeatmapOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const { + return impl().paint.template get<HeatmapOpacity>().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.cpp b/src/mbgl/style/layers/heatmap_layer_impl.cpp new file mode 100644 index 0000000000..af20888d9d --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.cpp @@ -0,0 +1,15 @@ +#include <mbgl/style/layers/heatmap_layer_impl.hpp> + +namespace mbgl { +namespace style { + +bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const { + assert(dynamic_cast<const HeatmapLayer::Impl*>(&other)); + const auto& impl = static_cast<const style::HeatmapLayer::Impl&>(other); + return filter != impl.filter || + visibility != impl.visibility || + paint.hasDataDrivenPropertyDifference(impl.paint); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.hpp b/src/mbgl/style/layers/heatmap_layer_impl.hpp new file mode 100644 index 0000000000..cc27c3076a --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { +namespace style { + +class HeatmapLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override; + + HeatmapPaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.cpp b/src/mbgl/style/layers/heatmap_layer_properties.cpp new file mode 100644 index 0000000000..2edb839589 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.hpp b/src/mbgl/style/layers/heatmap_layer_properties.hpp new file mode 100644 index 0000000000..f7afa5fbeb --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.hpp @@ -0,0 +1,40 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include <mbgl/style/types.hpp> +#include <mbgl/style/layout_property.hpp> +#include <mbgl/style/paint_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> + +namespace mbgl { +namespace style { + +struct HeatmapRadius : DataDrivenPaintProperty<float, attributes::a_radius, uniforms::u_radius> { + static float defaultValue() { return 30; } +}; + +struct HeatmapWeight : DataDrivenPaintProperty<float, attributes::a_weight, uniforms::u_weight> { + static float defaultValue() { return 1; } +}; + +struct HeatmapIntensity : PaintProperty<float> { + static float defaultValue() { return 1; } +}; + +struct HeatmapOpacity : PaintProperty<float> { + static float defaultValue() { return 1; } +}; + +class HeatmapPaintProperties : public Properties< + HeatmapRadius, + HeatmapWeight, + HeatmapIntensity, + HeatmapColor, + HeatmapOpacity +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp new file mode 100644 index 0000000000..e352ae090c --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer.cpp @@ -0,0 +1,240 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/hillshade_layer_impl.hpp> +#include <mbgl/style/layer_observer.hpp> + +namespace mbgl { +namespace style { + +HillshadeLayer::HillshadeLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable<Impl>(LayerType::Hillshade, layerID, sourceID)) { +} + +HillshadeLayer::HillshadeLayer(Immutable<Impl> impl_) + : Layer(std::move(impl_)) { +} + +HillshadeLayer::~HillshadeLayer() = default; + +const HillshadeLayer::Impl& HillshadeLayer::impl() const { + return static_cast<const Impl&>(*baseImpl); +} + +Mutable<HillshadeLayer::Impl> HillshadeLayer::mutableImpl() const { + return makeMutable<Impl>(impl()); +} + +std::unique_ptr<Layer> HillshadeLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HillshadePaintProperties::Transitionable(); + return std::make_unique<HillshadeLayer>(std::move(impl_)); +} + +void HillshadeLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const { +} + +// Source + +const std::string& HillshadeLayer::getSourceID() const { + return impl().source; +} + + +// Visibility + +void HillshadeLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HillshadeLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Layout properties + + +// Paint properties + +PropertyValue<float> HillshadeLayer::getDefaultHillshadeIlluminationDirection() { + return { 335 }; +} + +PropertyValue<float> HillshadeLayer::getHillshadeIlluminationDirection() const { + return impl().paint.template get<HillshadeIlluminationDirection>().value; +} + +void HillshadeLayer::setHillshadeIlluminationDirection(PropertyValue<float> value) { + if (value == getHillshadeIlluminationDirection()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationDirection>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeIlluminationDirectionTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationDirection>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeIlluminationDirectionTransition() const { + return impl().paint.template get<HillshadeIlluminationDirection>().options; +} + +PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getDefaultHillshadeIlluminationAnchor() { + return { HillshadeIlluminationAnchorType::Viewport }; +} + +PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getHillshadeIlluminationAnchor() const { + return impl().paint.template get<HillshadeIlluminationAnchor>().value; +} + +void HillshadeLayer::setHillshadeIlluminationAnchor(PropertyValue<HillshadeIlluminationAnchorType> value) { + if (value == getHillshadeIlluminationAnchor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationAnchor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeIlluminationAnchorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationAnchor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeIlluminationAnchorTransition() const { + return impl().paint.template get<HillshadeIlluminationAnchor>().options; +} + +PropertyValue<float> HillshadeLayer::getDefaultHillshadeExaggeration() { + return { 0.5 }; +} + +PropertyValue<float> HillshadeLayer::getHillshadeExaggeration() const { + return impl().paint.template get<HillshadeExaggeration>().value; +} + +void HillshadeLayer::setHillshadeExaggeration(PropertyValue<float> value) { + if (value == getHillshadeExaggeration()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeExaggeration>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeExaggerationTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeExaggeration>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeExaggerationTransition() const { + return impl().paint.template get<HillshadeExaggeration>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeShadowColor() { + return { Color::black() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeShadowColor() const { + return impl().paint.template get<HillshadeShadowColor>().value; +} + +void HillshadeLayer::setHillshadeShadowColor(PropertyValue<Color> value) { + if (value == getHillshadeShadowColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeShadowColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeShadowColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeShadowColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeShadowColorTransition() const { + return impl().paint.template get<HillshadeShadowColor>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeHighlightColor() { + return { Color::white() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeHighlightColor() const { + return impl().paint.template get<HillshadeHighlightColor>().value; +} + +void HillshadeLayer::setHillshadeHighlightColor(PropertyValue<Color> value) { + if (value == getHillshadeHighlightColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeHighlightColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeHighlightColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeHighlightColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeHighlightColorTransition() const { + return impl().paint.template get<HillshadeHighlightColor>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeAccentColor() { + return { Color::black() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeAccentColor() const { + return impl().paint.template get<HillshadeAccentColor>().value; +} + +void HillshadeLayer::setHillshadeAccentColor(PropertyValue<Color> value) { + if (value == getHillshadeAccentColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeAccentColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeAccentColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeAccentColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeAccentColorTransition() const { + return impl().paint.template get<HillshadeAccentColor>().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_impl.cpp b/src/mbgl/style/layers/hillshade_layer_impl.cpp new file mode 100644 index 0000000000..ed5aa922bf --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_impl.cpp @@ -0,0 +1,11 @@ +#include <mbgl/style/layers/hillshade_layer_impl.hpp> + +namespace mbgl { +namespace style { + +bool HillshadeLayer::Impl::hasLayoutDifference(const Layer::Impl&) const { + return false; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_impl.hpp b/src/mbgl/style/layers/hillshade_layer_impl.hpp new file mode 100644 index 0000000000..5618b7dfe2 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/hillshade_layer_properties.hpp> + +namespace mbgl { +namespace style { + +class HillshadeLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override; + + HillshadePaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_properties.cpp b/src/mbgl/style/layers/hillshade_layer_properties.cpp new file mode 100644 index 0000000000..f296ab4520 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/hillshade_layer_properties.hpp> + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_properties.hpp b/src/mbgl/style/layers/hillshade_layer_properties.hpp new file mode 100644 index 0000000000..260d7ea808 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_properties.hpp @@ -0,0 +1,49 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include <mbgl/style/types.hpp> +#include <mbgl/style/layout_property.hpp> +#include <mbgl/style/paint_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> + +namespace mbgl { +namespace style { + +struct HillshadeIlluminationDirection : PaintProperty<float> { + static float defaultValue() { return 335; } +}; + +struct HillshadeIlluminationAnchor : PaintProperty<HillshadeIlluminationAnchorType> { + static HillshadeIlluminationAnchorType defaultValue() { return HillshadeIlluminationAnchorType::Viewport; } +}; + +struct HillshadeExaggeration : PaintProperty<float> { + static float defaultValue() { return 0.5; } +}; + +struct HillshadeShadowColor : PaintProperty<Color> { + static Color defaultValue() { return Color::black(); } +}; + +struct HillshadeHighlightColor : PaintProperty<Color> { + static Color defaultValue() { return Color::white(); } +}; + +struct HillshadeAccentColor : PaintProperty<Color> { + static Color defaultValue() { return Color::black(); } +}; + +class HillshadePaintProperties : public Properties< + HillshadeIlluminationDirection, + HillshadeIlluminationAnchor, + HillshadeExaggeration, + HillshadeShadowColor, + HillshadeHighlightColor, + HillshadeAccentColor +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index 573aabda8b..6d748311bf 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -8,6 +8,12 @@ #include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer.hpp> #include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer_impl.hpp> #include <mbgl/style/layer_observer.hpp> +<% if (type === 'heatmap') { -%> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> +<% } -%> namespace mbgl { namespace style { @@ -59,7 +65,7 @@ const std::string& <%- camelize(type) %>Layer::getSourceID() const { return impl().source; } -<% if (type !== 'raster') { -%> +<% if (type !== 'raster' && type !== 'hillshade') { -%> void <%- camelize(type) %>Layer::setSourceLayer(const std::string& sourceLayer) { auto impl_ = mutableImpl(); impl_->sourceLayer = sourceLayer; @@ -102,12 +108,14 @@ void <%- camelize(type) %>Layer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void <%- camelize(type) %>Layer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties @@ -134,7 +142,13 @@ void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(<%- propertyV // Paint properties <% for (const property of paintProperties) { %> <%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() { +<% if (property.name === 'heatmap-color') { -%> + conversion::Error error; + std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON"; + return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error); +<% } else { -%> return { <%- defaultValue(property) %> }; +<% } -%> } <%- propertyValueType(property) %> <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const { diff --git a/src/mbgl/style/layers/layer_properties.hpp.ejs b/src/mbgl/style/layers/layer_properties.hpp.ejs index cde1b80b7b..1bceb84960 100644 --- a/src/mbgl/style/layers/layer_properties.hpp.ejs +++ b/src/mbgl/style/layers/layer_properties.hpp.ejs @@ -25,6 +25,7 @@ struct <%- camelize(property.name) %> : <%- layoutPropertyType(property, type) % <% } -%> <% for (const property of paintProperties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> struct <%- camelize(property.name) %> : <%- paintPropertyType(property, type) %> { static <%- evaluatedType(property) %> defaultValue() { return <%- defaultValue(property) %>; } }; diff --git a/src/mbgl/style/layers/line_layer.cpp b/src/mbgl/style/layers/line_layer.cpp index 1c7f0d28ee..56eac34c00 100644 --- a/src/mbgl/style/layers/line_layer.cpp +++ b/src/mbgl/style/layers/line_layer.cpp @@ -82,12 +82,14 @@ void LineLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void LineLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/raster_layer.cpp b/src/mbgl/style/layers/raster_layer.cpp index a9a8d273fa..36b2e3e027 100644 --- a/src/mbgl/style/layers/raster_layer.cpp +++ b/src/mbgl/style/layers/raster_layer.cpp @@ -59,12 +59,14 @@ void RasterLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void RasterLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 9a944657ca..c940f3b00a 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -82,12 +82,14 @@ void SymbolLayer::setMinZoom(float minZoom) { auto impl_ = mutableImpl(); impl_->minZoom = minZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } void SymbolLayer::setMaxZoom(float maxZoom) { auto impl_ = mutableImpl(); impl_->maxZoom = maxZoom; baseImpl = std::move(impl_); + observer->onLayerChanged(*this); } // Layout properties @@ -412,15 +414,15 @@ void SymbolLayer::setTextField(DataDrivenPropertyValue<std::string> value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -PropertyValue<std::vector<std::string>> SymbolLayer::getDefaultTextFont() { +DataDrivenPropertyValue<std::vector<std::string>> SymbolLayer::getDefaultTextFont() { return TextFont::defaultValue(); } -PropertyValue<std::vector<std::string>> SymbolLayer::getTextFont() const { +DataDrivenPropertyValue<std::vector<std::string>> SymbolLayer::getTextFont() const { return impl().layout.get<TextFont>(); } -void SymbolLayer::setTextFont(PropertyValue<std::vector<std::string>> value) { +void SymbolLayer::setTextFont(DataDrivenPropertyValue<std::vector<std::string>> value) { if (value == getTextFont()) return; auto impl_ = mutableImpl(); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 436b5cbd4f..e70ac28d59 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -112,7 +112,7 @@ struct TextField : DataDrivenLayoutProperty<std::string> { static std::string defaultValue() { return ""; } }; -struct TextFont : LayoutProperty<std::vector<std::string>> { +struct TextFont : DataDrivenLayoutProperty<std::vector<std::string>> { static constexpr const char * key = "text-font"; static std::vector<std::string> defaultValue() { return { "Open Sans Regular", "Arial Unicode MS Regular" }; } }; diff --git a/src/mbgl/style/paint_property.hpp b/src/mbgl/style/paint_property.hpp index c4c996b3bd..195eb645a9 100644 --- a/src/mbgl/style/paint_property.hpp +++ b/src/mbgl/style/paint_property.hpp @@ -2,6 +2,7 @@ #include <mbgl/style/properties.hpp> #include <mbgl/style/property_value.hpp> +#include <mbgl/style/heatmap_color_property_value.hpp> #include <mbgl/style/data_driven_property_value.hpp> #include <mbgl/renderer/property_evaluator.hpp> #include <mbgl/renderer/cross_faded_property_evaluator.hpp> @@ -48,5 +49,27 @@ public: static constexpr bool IsDataDriven = false; }; +/* + * Special-case paint property traits for heatmap-color, needed because + * heatmap-color values do not fit into the + * Undefined | Value | {Camera,Source,Composite}Function taxonomy that applies + * to all other paint properties. + * + * These traits are provided here--despite the fact that heatmap-color + * is not used like other paint properties--to allow the parameter-pack-based + * batch evaluation of paint properties to compile properly. + */ +class HeatmapColor { +public: + using TransitionableType = Transitionable<HeatmapColorPropertyValue>; + using UnevaluatedType = Transitioning<HeatmapColorPropertyValue>; + using EvaluatorType = PropertyEvaluator<Color>; + using PossiblyEvaluatedType = Color; + using Type = Color; + static constexpr bool IsDataDriven = false; + + static Color defaultValue() { return {}; } +}; + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index a83897dbf5..8d14d7972c 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -1,11 +1,13 @@ #include <mbgl/style/parser.hpp> #include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/rapidjson_conversion.hpp> #include <mbgl/style/conversion.hpp> #include <mbgl/style/conversion/coordinate.hpp> #include <mbgl/style/conversion/source.hpp> #include <mbgl/style/conversion/layer.hpp> #include <mbgl/style/conversion/light.hpp> +#include <mbgl/style/conversion/transition_options.hpp> #include <mbgl/util/logging.hpp> #include <mbgl/util/string.hpp> @@ -117,6 +119,9 @@ StyleParseResult Parser::parse(const std::string& json) { } } + // Call for side effect of logging warnings for invalid values. + fontStacks(); + return nullptr; } @@ -149,7 +154,7 @@ void Parser::parseSources(const JSValue& value) { } for (const auto& property : value.GetObject()) { - std::string id = *conversion::toString(property.name); + std::string id { property.name.GetString(), property.name.GetStringLength() }; conversion::Error error; optional<std::unique_ptr<Source>> source = @@ -256,7 +261,7 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } layer = reference->cloneRef(id); - conversion::setPaintProperties(*layer, value); + conversion::setPaintProperties(*layer, conversion::Convertible(&value)); } else { conversion::Error error; optional<std::unique_ptr<Layer>> converted = conversion::convert<std::unique_ptr<Layer>>(value, error); @@ -269,28 +274,32 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } std::vector<FontStack> Parser::fontStacks() const { - std::set<FontStack> optional; + std::set<FontStack> result; for (const auto& layer : layers) { - if (layer->is<SymbolLayer>()) { - PropertyValue<FontStack> textFont = layer->as<SymbolLayer>()->getTextFont(); - if (textFont.isUndefined()) { - optional.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); - } else if (textFont.isConstant()) { - optional.insert(textFont.asConstant()); - } else if (textFont.isCameraFunction()) { - textFont.asCameraFunction().stops.match( - [&] (const auto& stops) { - for (const auto& stop : stops.stops) { - optional.insert(stop.second); + if (layer->is<SymbolLayer>() && !layer->as<SymbolLayer>()->getTextField().isUndefined()) { + layer->as<SymbolLayer>()->getTextFont().match( + [&] (Undefined) { + result.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); + }, + [&] (const FontStack& constant) { + result.insert(constant); + }, + [&] (const auto& function) { + for (const auto& value : function.possibleOutputs()) { + if (value) { + result.insert(*value); + } else { + Log::Warning(Event::ParseStyle, "Layer '%s' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression.", layer->getID().c_str()); + break; } } - ); - } + } + ); } } - return std::vector<FontStack>(optional.begin(), optional.end()); + return std::vector<FontStack>(result.begin(), result.end()); } } // namespace style diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp index 48a764ccb4..79bd9c928b 100644 --- a/src/mbgl/style/rapidjson_conversion.hpp +++ b/src/mbgl/style/rapidjson_conversion.hpp @@ -1,103 +1,125 @@ #pragma once #include <mbgl/util/rapidjson.hpp> -#include <mbgl/util/feature.hpp> #include <mbgl/style/conversion.hpp> +#include <mapbox/geojson.hpp> +#include <mapbox/geojson/rapidjson.hpp> + namespace mbgl { namespace style { namespace conversion { -inline bool isUndefined(const JSValue& value) { - return value.IsNull(); -} - -inline bool isArray(const JSValue& value) { - return value.IsArray(); -} +template <> +class ConversionTraits<const JSValue*> { +public: + static bool isUndefined(const JSValue* value) { + return value->IsNull(); + } -inline std::size_t arrayLength(const JSValue& value) { - return value.Size(); -} + static bool isArray(const JSValue* value) { + return value->IsArray(); + } -inline const JSValue& arrayMember(const JSValue& value, std::size_t i) { - return value[rapidjson::SizeType(i)]; -} + static std::size_t arrayLength(const JSValue* value) { + return value->Size(); + } -inline bool isObject(const JSValue& value) { - return value.IsObject(); -} + static const JSValue* arrayMember(const JSValue* value, std::size_t i) { + return &(*value)[rapidjson::SizeType(i)]; + } -inline const JSValue* objectMember(const JSValue& value, const char * name) { - if (!value.HasMember(name)) { - return nullptr; + static bool isObject(const JSValue* value) { + return value->IsObject(); } - return &value[name]; -} -template <class Fn> -optional<Error> eachMember(const JSValue& value, Fn&& fn) { - assert(value.IsObject()); - for (const auto& property : value.GetObject()) { - optional<Error> result = - fn({ property.name.GetString(), property.name.GetStringLength() }, property.value); - if (result) { - return result; + static optional<const JSValue*> objectMember(const JSValue* value, const char * name) { + if (!value->HasMember(name)) { + return optional<const JSValue*>(); } + const JSValue* const& member = &(*value)[name]; + return {member}; } - return {}; -} -inline optional<bool> toBool(const JSValue& value) { - if (!value.IsBool()) { + template <class Fn> + static optional<Error> eachMember(const JSValue* value, Fn&& fn) { + assert(value->IsObject()); + for (const auto& property : value->GetObject()) { + optional<Error> result = + fn({ property.name.GetString(), property.name.GetStringLength() }, &property.value); + if (result) { + return result; + } + } return {}; } - return value.GetBool(); -} -inline optional<float> toNumber(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional<bool> toBool(const JSValue* value) { + if (!value->IsBool()) { + return {}; + } + return value->GetBool(); } - return value.GetDouble(); -} -inline optional<double> toDouble(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional<float> toNumber(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); } - return value.GetDouble(); -} -inline optional<std::string> toString(const JSValue& value) { - if (!value.IsString()) { - return {}; + static optional<double> toDouble(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); + } + + static optional<std::string> toString(const JSValue* value) { + if (!value->IsString()) { + return {}; + } + return {{ value->GetString(), value->GetStringLength() }}; } - return {{ value.GetString(), value.GetStringLength() }}; -} -inline optional<Value> toValue(const JSValue& value) { - switch (value.GetType()) { - case rapidjson::kNullType: - case rapidjson::kFalseType: - return { false }; + static optional<Value> toValue(const JSValue* value) { + switch (value->GetType()) { + case rapidjson::kNullType: + case rapidjson::kFalseType: + return { false }; - case rapidjson::kTrueType: - return { true }; + case rapidjson::kTrueType: + return { true }; - case rapidjson::kStringType: - return { std::string { value.GetString(), value.GetStringLength() } }; + 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::kNumberType: + if (value->IsUint64()) return { value->GetUint64() }; + if (value->IsInt64()) return { value->GetInt64() }; + return { value->GetDouble() }; - default: + default: + return {}; + } + } + + static optional<GeoJSON> toGeoJSON(const JSValue* value, Error& error) { + try { + return mapbox::geojson::convert(*value); + } catch (const std::exception& ex) { + error = { ex.what() }; return {}; + } } +}; + +template <class T, class...Args> +optional<T> convert(const JSValue& value, Error& error, Args&&...args) { + return convert<T>(Convertible(&value), error, std::forward<Args>(args)...); } } // namespace conversion } // namespace style } // namespace mbgl + diff --git a/src/mbgl/style/sources/custom_geometry_source.cpp b/src/mbgl/style/sources/custom_geometry_source.cpp new file mode 100644 index 0000000000..b37490a5ce --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source.cpp @@ -0,0 +1,45 @@ +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/style/sources/custom_geometry_source_impl.hpp> +#include <mbgl/actor/actor.hpp> +#include <mbgl/actor/scheduler.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/shared_thread_pool.hpp> +#include <tuple> +#include <map> + +namespace mbgl { +namespace style { + +CustomGeometrySource::CustomGeometrySource(std::string id, + const CustomGeometrySource::Options options) + : Source(makeMutable<CustomGeometrySource::Impl>(std::move(id), options)), + loader(std::make_unique<Actor<CustomTileLoader>>(*sharedThreadPool(), options.fetchTileFunction, options.cancelTileFunction)) { +} + +CustomGeometrySource::~CustomGeometrySource() = default; + +const CustomGeometrySource::Impl& CustomGeometrySource::impl() const { + return static_cast<const CustomGeometrySource::Impl&>(*baseImpl); +} + +void CustomGeometrySource::loadDescription(FileSource&) { + baseImpl = makeMutable<CustomGeometrySource::Impl>(impl(), loader->self()); + loaded = true; +} + +void CustomGeometrySource::setTileData(const CanonicalTileID& tileID, + const GeoJSON& data) { + loader->invoke(&CustomTileLoader::setTileData, tileID, data); +} + +void CustomGeometrySource::invalidateTile(const CanonicalTileID& tileID) { + loader->invoke(&CustomTileLoader::invalidateTile, tileID); +} + +void CustomGeometrySource::invalidateRegion(const LatLngBounds& bounds) { + loader->invoke(&CustomTileLoader::invalidateRegion, bounds, impl().getZoomRange()); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.cpp b/src/mbgl/style/sources/custom_geometry_source_impl.cpp new file mode 100644 index 0000000000..67d52bdc24 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/sources/custom_geometry_source_impl.hpp> +#include <mbgl/style/source_observer.hpp> + +namespace mbgl { +namespace style { + +CustomGeometrySource::Impl::Impl(std::string id_, + const CustomGeometrySource::Options options) + : Source::Impl(SourceType::CustomVector, std::move(id_)), + tileOptions(options.tileOptions), + zoomRange(options.zoomRange), + loaderRef({}) { +} + +CustomGeometrySource::Impl::Impl(const Impl& impl, ActorRef<CustomTileLoader> loaderRef_) + : Source::Impl(impl), + tileOptions(impl.tileOptions), + zoomRange(impl.zoomRange), + loaderRef(loaderRef_){ + +} + +optional<std::string> CustomGeometrySource::Impl::getAttribution() const { + return {}; +} + +CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const { + return tileOptions; +} + +Range<uint8_t> CustomGeometrySource::Impl::getZoomRange() const { + return zoomRange; +} + +optional<ActorRef<CustomTileLoader>> CustomGeometrySource::Impl::getTileLoader() const { + return loaderRef; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.hpp b/src/mbgl/style/sources/custom_geometry_source_impl.hpp new file mode 100644 index 0000000000..ce7187202d --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include <mbgl/style/source_impl.hpp> +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/actor/actor_ref.hpp> + +namespace mbgl { +namespace style { + +class CustomGeometrySource::Impl : public Source::Impl { +public: + Impl(std::string id, CustomGeometrySource::Options options); + Impl(const Impl&, ActorRef<CustomTileLoader>); + + optional<std::string> getAttribution() const final; + + CustomGeometrySource::TileOptions getTileOptions() const; + Range<uint8_t> getZoomRange() const; + optional<ActorRef<CustomTileLoader>> getTileLoader() const; + +private: + CustomGeometrySource::TileOptions tileOptions; + Range<uint8_t> zoomRange; + optional<ActorRef<CustomTileLoader>> loaderRef; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/raster_dem_source.cpp b/src/mbgl/style/sources/raster_dem_source.cpp new file mode 100644 index 0000000000..dc9feb8eeb --- /dev/null +++ b/src/mbgl/style/sources/raster_dem_source.cpp @@ -0,0 +1,19 @@ +#include <mbgl/style/sources/raster_dem_source.hpp> +#include <mbgl/style/sources/raster_source_impl.hpp> +#include <mbgl/style/source_observer.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/tileset.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/util/mapbox.hpp> + +namespace mbgl { +namespace style { + +RasterDEMSource::RasterDEMSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize) + : RasterSource(std::move(id), urlOrTileset_, tileSize, SourceType::RasterDEM){ +} + + + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/raster_source.cpp b/src/mbgl/style/sources/raster_source.cpp index 0a0412a4ed..53f29d660b 100644 --- a/src/mbgl/style/sources/raster_source.cpp +++ b/src/mbgl/style/sources/raster_source.cpp @@ -9,8 +9,8 @@ namespace mbgl { namespace style { -RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize) - : Source(makeMutable<Impl>(std::move(id), tileSize)), +RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize, SourceType sourceType) + : Source(makeMutable<Impl>(sourceType, std::move(id), tileSize)), urlOrTileset(std::move(urlOrTileset_)) { } diff --git a/src/mbgl/style/sources/raster_source_impl.cpp b/src/mbgl/style/sources/raster_source_impl.cpp index 50dae1f07e..4db25aafd1 100644 --- a/src/mbgl/style/sources/raster_source_impl.cpp +++ b/src/mbgl/style/sources/raster_source_impl.cpp @@ -3,8 +3,8 @@ namespace mbgl { namespace style { -RasterSource::Impl::Impl(std::string id_, uint16_t tileSize_) - : Source::Impl(SourceType::Raster, std::move(id_)), +RasterSource::Impl::Impl(SourceType sourceType, std::string id_, uint16_t tileSize_) + : Source::Impl(sourceType, std::move(id_)), tileSize(tileSize_) { } diff --git a/src/mbgl/style/sources/raster_source_impl.hpp b/src/mbgl/style/sources/raster_source_impl.hpp index c41d5485b2..96f59a2159 100644 --- a/src/mbgl/style/sources/raster_source_impl.hpp +++ b/src/mbgl/style/sources/raster_source_impl.hpp @@ -8,7 +8,7 @@ namespace style { class RasterSource::Impl : public Source::Impl { public: - Impl(std::string id, uint16_t tileSize); + Impl(SourceType sourceType, std::string id, uint16_t tileSize); Impl(const Impl&, Tileset); optional<Tileset> getTileset() const; diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 39e1c17722..0c7f924917 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -6,9 +6,11 @@ #include <mbgl/style/layers/background_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/parser.hpp> #include <mbgl/style/transition_options.hpp> diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 0a1781e01b..bdfa20a047 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -12,6 +12,7 @@ MBGL_DEFINE_ENUM(SourceType, { { SourceType::Video, "video" }, { SourceType::Annotations, "annotations" }, { SourceType::Image, "image" }, + { SourceType::CustomVector, "customvector" } }); MBGL_DEFINE_ENUM(VisibilityType, { @@ -24,6 +25,11 @@ MBGL_DEFINE_ENUM(TranslateAnchorType, { { TranslateAnchorType::Viewport, "viewport" }, }); +MBGL_DEFINE_ENUM(HillshadeIlluminationAnchorType, { + { HillshadeIlluminationAnchorType::Map, "map" }, + { HillshadeIlluminationAnchorType::Viewport, "viewport" }, +}); + MBGL_DEFINE_ENUM(RotateAnchorType, { { RotateAnchorType::Map, "map" }, { RotateAnchorType::Viewport, "viewport" }, |