344 lines
14 KiB
C++
344 lines
14 KiB
C++
#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/source_options.hpp>
|
|
#include <mbgl/style/conversion/tileset.hpp>
|
|
#include <mbgl/style/conversion_impl.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/style/sources/raster_array_source.hpp>
|
|
#include <mbgl/util/geo.hpp>
|
|
|
|
namespace mbgl {
|
|
namespace style {
|
|
namespace conversion {
|
|
|
|
namespace {
|
|
// A tile source can either specify a URL to TileJSON, or inline TileJSON.
|
|
std::optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) {
|
|
auto urlVal = objectMember(value, "url");
|
|
if (!urlVal) {
|
|
std::optional<Tileset> tileset = convert<Tileset>(value, error);
|
|
if (!tileset) {
|
|
return std::nullopt;
|
|
}
|
|
return {std::move(*tileset)};
|
|
}
|
|
|
|
std::optional<std::string> url = toString(*urlVal);
|
|
if (!url) {
|
|
error.message = "source url must be a string";
|
|
return std::nullopt;
|
|
}
|
|
|
|
return {std::move(*url)};
|
|
}
|
|
|
|
std::optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id,
|
|
const Convertible& value,
|
|
Error& error) {
|
|
std::optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
|
|
if (!urlOrTileset) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint16_t tileSize = util::tileSize_I;
|
|
auto tileSizeValue = objectMember(value, "tileSize");
|
|
if (tileSizeValue) {
|
|
std::optional<float> size = toNumber(*tileSizeValue);
|
|
if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
|
|
error.message = "invalid tileSize";
|
|
return std::nullopt;
|
|
}
|
|
tileSize = static_cast<uint16_t>(*size);
|
|
}
|
|
|
|
return {std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize)};
|
|
}
|
|
|
|
std::optional<std::unique_ptr<Source>> convertRasterDEMSource(const std::string& id,
|
|
const Convertible& value,
|
|
Error& error) {
|
|
std::optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
|
|
if (!urlOrTileset) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint16_t tileSize = util::tileSize_I;
|
|
auto tileSizeValue = objectMember(value, "tileSize");
|
|
if (tileSizeValue) {
|
|
std::optional<float> size = toNumber(*tileSizeValue);
|
|
if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
|
|
error.message = "invalid tileSize";
|
|
return std::nullopt;
|
|
}
|
|
tileSize = static_cast<uint16_t>(*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<SourceOptions>(value, error);
|
|
|
|
return {std::make_unique<RasterDEMSource>(id, std::move(*urlOrTileset), tileSize, options)};
|
|
}
|
|
|
|
std::optional<std::unique_ptr<Source>> 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<float> maxzoom;
|
|
if (maxzoomValue) {
|
|
maxzoom = toNumber(*maxzoomValue);
|
|
if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits<uint8_t>::max()) {
|
|
error.message = "invalid maxzoom";
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
const auto minzoomValue = objectMember(value, "minzoom");
|
|
std::optional<float> minzoom;
|
|
if (minzoomValue) {
|
|
minzoom = toNumber(*minzoomValue);
|
|
if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits<uint8_t>::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<VectorSource>(id, std::move(*urlOrTileset), maxzoom, minzoom, encoding)};
|
|
}
|
|
|
|
std::optional<std::unique_ptr<Source>> 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<GeoJSONOptions> options = GeoJSONOptions::defaultOptions();
|
|
if (std::optional<GeoJSONOptions> converted = convert<GeoJSONOptions>(value, error)) {
|
|
options = makeMutable<GeoJSONOptions>(std::move(*converted));
|
|
}
|
|
|
|
auto result = std::make_unique<GeoJSONSource>(id, std::move(options));
|
|
|
|
if (isObject(*dataValue)) {
|
|
auto geoJSON = convert<GeoJSON>(*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<std::unique_ptr<Source>> 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<LatLng, 4> coordinates;
|
|
for (std::size_t i = 0; i < 4; i++) {
|
|
auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue, i), error);
|
|
if (!latLng) {
|
|
return std::nullopt;
|
|
}
|
|
coordinates[i] = *latLng;
|
|
}
|
|
auto result = std::make_unique<ImageSource>(id, coordinates);
|
|
result->setURL(*urlString);
|
|
|
|
return {std::move(result)};
|
|
}
|
|
|
|
std::optional<std::unique_ptr<Source>> 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<RasterArrayLocalFieldDescriptor> 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<RasterArraySource>(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<bool>(nextFixture) != static_cast<bool>(nextMetadata) ||
|
|
static_cast<bool>(nextFixture) != static_cast<bool>(nextField)) {
|
|
error.message = "raster-array next fixture requires next-fixture, next-metadata, and next-field";
|
|
return std::nullopt;
|
|
}
|
|
std::vector<RasterArrayRuntimeFieldDescriptor> 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<RasterArraySource>(
|
|
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<RasterArraySource>(id,
|
|
*fixtureID,
|
|
*metadataURL,
|
|
*fieldURL,
|
|
*nextFixtureID,
|
|
*nextMetadataURL,
|
|
*nextFieldURL,
|
|
std::move(runtimeFields))};
|
|
}
|
|
} // namespace
|
|
|
|
std::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.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<std::string> 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
|