#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace style { namespace conversion { namespace { // A tile source can either specify a URL to TileJSON, or inline TileJSON. std::optional> convertURLOrTileset(const Convertible& value, Error& error) { auto urlVal = objectMember(value, "url"); if (!urlVal) { std::optional tileset = convert(value, error); if (!tileset) { return std::nullopt; } return {std::move(*tileset)}; } std::optional url = toString(*urlVal); if (!url) { error.message = "source url must be a string"; return std::nullopt; } return {std::move(*url)}; } std::optional> convertRasterSource(const std::string& id, const Convertible& value, Error& error) { std::optional> urlOrTileset = convertURLOrTileset(value, error); if (!urlOrTileset) { return std::nullopt; } uint16_t tileSize = util::tileSize_I; auto tileSizeValue = objectMember(value, "tileSize"); if (tileSizeValue) { std::optional size = toNumber(*tileSizeValue); if (!size || *size < 0 || *size > std::numeric_limits::max()) { error.message = "invalid tileSize"; return std::nullopt; } tileSize = static_cast(*size); } return {std::make_unique(id, std::move(*urlOrTileset), tileSize)}; } std::optional> convertRasterDEMSource(const std::string& id, const Convertible& value, Error& error) { std::optional> urlOrTileset = convertURLOrTileset(value, error); if (!urlOrTileset) { return std::nullopt; } uint16_t tileSize = util::tileSize_I; auto tileSizeValue = objectMember(value, "tileSize"); if (tileSizeValue) { std::optional size = toNumber(*tileSizeValue); if (!size || *size < 0 || *size > std::numeric_limits::max()) { error.message = "invalid tileSize"; return std::nullopt; } tileSize = static_cast(*size); } /* TODO: Refactor TileJSON parsing responsibilities. Currently `Tileset` handles full TileJSON parsing with optional parameters. Since style.json can contain TileJSON parameter overrides or non-TileJSON spec parameters, a separate mechanism is needed. Ideally these two pathways share parsing logic. */ auto options = convert(value, error); return {std::make_unique(id, std::move(*urlOrTileset), tileSize, options)}; } std::optional> convertVectorSource(const std::string& id, const Convertible& value, Error& error) { auto urlOrTileset = convertURLOrTileset(value, error); if (!urlOrTileset) { return std::nullopt; } const auto maxzoomValue = objectMember(value, "maxzoom"); std::optional maxzoom; if (maxzoomValue) { maxzoom = toNumber(*maxzoomValue); if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits::max()) { error.message = "invalid maxzoom"; return std::nullopt; } } const auto minzoomValue = objectMember(value, "minzoom"); std::optional minzoom; if (minzoomValue) { minzoom = toNumber(*minzoomValue); if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits::max()) { error.message = "invalid minzoom"; return std::nullopt; } } const auto encodingValue = objectMember(value, "encoding"); auto encoding = Tileset::VectorEncoding::Mapbox; if (encodingValue) { const auto encodingStr = toString(*encodingValue); if (encodingStr && encodingStr == "mvt") { encoding = Tileset::VectorEncoding::Mapbox; } else if (encodingStr && encodingStr == "mlt") { encoding = Tileset::VectorEncoding::MLT; } else { error.message = "invalid encoding"; return std::nullopt; } } return {std::make_unique(id, std::move(*urlOrTileset), maxzoom, minzoom, encoding)}; } std::optional> convertGeoJSONSource(const std::string& id, const Convertible& value, Error& error) { auto dataValue = objectMember(value, "data"); if (!dataValue) { error.message = "GeoJSON source must have a data value"; return std::nullopt; } Immutable options = GeoJSONOptions::defaultOptions(); if (std::optional converted = convert(value, error)) { options = makeMutable(std::move(*converted)); } auto result = std::make_unique(id, std::move(options)); if (isObject(*dataValue)) { auto geoJSON = convert(*dataValue, error); if (!geoJSON) { return std::nullopt; } result->setGeoJSON(*geoJSON); } else if (toString(*dataValue)) { result->setURL(*toString(*dataValue)); } else { error.message = "GeoJSON data must be a URL or an object"; return std::nullopt; } return {std::move(result)}; } std::optional> convertImageSource(const std::string& id, const Convertible& value, Error& error) { auto urlValue = objectMember(value, "url"); if (!urlValue) { error.message = "Image source must have a url value"; return std::nullopt; } auto urlString = toString(*urlValue); if (!urlString) { error.message = "Image url must be a URL string"; return std::nullopt; } auto coordinatesValue = objectMember(value, "coordinates"); if (!coordinatesValue) { error.message = "Image source must have a coordinates values"; return std::nullopt; } if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) { error.message = "Image coordinates must be an array of four longitude latitude " "pairs"; return std::nullopt; } std::array coordinates; for (std::size_t i = 0; i < 4; i++) { auto latLng = conversion::convert(arrayMember(*coordinatesValue, i), error); if (!latLng) { return std::nullopt; } coordinates[i] = *latLng; } auto result = std::make_unique(id, coordinates); result->setURL(*urlString); return {std::move(result)}; } std::optional> convertRasterArraySource(const std::string& id, const Convertible& value, Error& error) { if (const auto localFieldsValue = objectMember(value, "production-fields")) { if (!isArray(*localFieldsValue) || arrayLength(*localFieldsValue) < 2) { error.message = "raster-array production-fields must contain at least two entries"; return std::nullopt; } std::vector localFields; for (size_t index = 0; index < arrayLength(*localFieldsValue); ++index) { const auto entry = arrayMember(*localFieldsValue, index); const auto metadata = objectMember(entry, "metadata"); const auto field = objectMember(entry, "field"); if (!metadata || !field) { error.message = "raster-array production field requires metadata and field"; return std::nullopt; } const auto metadataURL = toString(*metadata); const auto fieldURL = toString(*field); if (!metadataURL || !fieldURL) { error.message = "raster-array production field inputs must be strings"; return std::nullopt; } localFields.push_back({*metadataURL, *fieldURL}); } return {std::make_unique(id, std::move(localFields))}; } const auto fixture = objectMember(value, "fixture"); const auto metadata = objectMember(value, "metadata"); const auto field = objectMember(value, "field"); if (!fixture || !metadata || !field) { error.message = "raster-array source requires fixture, metadata, and field"; return std::nullopt; } const auto fixtureID = toString(*fixture); const auto metadataURL = toString(*metadata); const auto fieldURL = toString(*field); if (!fixtureID || !metadataURL || !fieldURL) { error.message = "raster-array fixture inputs must be strings"; return std::nullopt; } const auto nextFixture = objectMember(value, "next-fixture"); const auto nextMetadata = objectMember(value, "next-metadata"); const auto nextField = objectMember(value, "next-field"); if (static_cast(nextFixture) != static_cast(nextMetadata) || static_cast(nextFixture) != static_cast(nextField)) { error.message = "raster-array next fixture requires next-fixture, next-metadata, and next-field"; return std::nullopt; } std::vector runtimeFields; if (const auto runtimeFieldsValue = objectMember(value, "runtime-fields")) { if (!isArray(*runtimeFieldsValue)) { error.message = "raster-array runtime-fields must be an array"; return std::nullopt; } for (size_t index = 0; index < arrayLength(*runtimeFieldsValue); ++index) { const auto runtimeField = arrayMember(*runtimeFieldsValue, index); const auto runtimeFixture = objectMember(runtimeField, "fixture"); const auto runtimeMetadata = objectMember(runtimeField, "metadata"); const auto runtimeData = objectMember(runtimeField, "field"); if (!runtimeFixture || !runtimeMetadata || !runtimeData) { error.message = "raster-array runtime field requires fixture, metadata, and field"; return std::nullopt; } const auto runtimeFixtureID = toString(*runtimeFixture); const auto runtimeMetadataURL = toString(*runtimeMetadata); const auto runtimeFieldURL = toString(*runtimeData); if (!runtimeFixtureID || !runtimeMetadataURL || !runtimeFieldURL) { error.message = "raster-array runtime field inputs must be strings"; return std::nullopt; } runtimeFields.push_back({*runtimeFixtureID, *runtimeMetadataURL, *runtimeFieldURL}); } } if (!nextFixture) { return {std::make_unique( id, *fixtureID, *metadataURL, *fieldURL, "", "", "", std::move(runtimeFields))}; } const auto nextFixtureID = toString(*nextFixture); const auto nextMetadataURL = toString(*nextMetadata); const auto nextFieldURL = toString(*nextField); if (!nextFixtureID || !nextMetadataURL || !nextFieldURL) { error.message = "raster-array next fixture inputs must be strings"; return std::nullopt; } return {std::make_unique(id, *fixtureID, *metadataURL, *fieldURL, *nextFixtureID, *nextMetadataURL, *nextFieldURL, std::move(runtimeFields))}; } } // namespace std::optional> Converter>::operator()(const Convertible& value, Error& error, const std::string& id) const { if (!isObject(value)) { error.message = "source must be an object"; return std::nullopt; } auto typeValue = objectMember(value, "type"); if (!typeValue) { error.message = "source must have a type"; return std::nullopt; } std::optional type = toString(*typeValue); if (!type) { error.message = "source type must be a string"; return std::nullopt; } const std::string& tname = type.value(); if (tname == "raster") { return convertRasterSource(id, value, error); } else if (tname == "raster-dem") { return convertRasterDEMSource(id, value, error); } else if (tname == "vector") { return convertVectorSource(id, value, error); } else if (tname == "geojson") { return convertGeoJSONSource(id, value, error); } else if (tname == "image") { return convertImageSource(id, value, error); } else if (tname == "raster-array") { return convertRasterArraySource(id, value, error); } else { error.message = "invalid source type"; return std::nullopt; } } } // namespace conversion } // namespace style } // namespace mbgl