From 2b9f20461af3b1637d29abdca38188549e21cc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 8 Dec 2015 16:32:15 -0800 Subject: [PATCH 1/7] [core] move SourceInfo to its own file --- src/mbgl/map/source.cpp | 87 ------------------------- src/mbgl/map/source.hpp | 30 +-------- src/mbgl/map/source_info.cpp | 119 +++++++++++++++++++++++++++++++++++ src/mbgl/map/source_info.hpp | 38 +++++++++++ 4 files changed, 158 insertions(+), 116 deletions(-) create mode 100644 src/mbgl/map/source_info.cpp create mode 100644 src/mbgl/map/source_info.hpp diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 16e2630a946..548c837add4 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -33,93 +33,6 @@ namespace mbgl { -void parse(const rapidjson::Value& value, std::vector& target, const char *name) { - if (!value.HasMember(name)) - return; - - const rapidjson::Value& property = value[name]; - if (!property.IsArray()) - return; - - for (rapidjson::SizeType i = 0; i < property.Size(); i++) - if (!property[i].IsString()) - return; - - for (rapidjson::SizeType i = 0; i < property.Size(); i++) - target.emplace_back(std::string(property[i].GetString(), property[i].GetStringLength())); -} - -void parse(const rapidjson::Value& value, std::string& target, const char* name) { - if (!value.HasMember(name)) - return; - - const rapidjson::Value& property = value[name]; - if (!property.IsString()) - return; - - target = { property.GetString(), property.GetStringLength() }; -} - -void parse(const rapidjson::Value& value, uint16_t& target, const char* name) { - if (!value.HasMember(name)) - return; - - const rapidjson::Value& property = value[name]; - if (!property.IsUint()) - return; - - unsigned int uint = property.GetUint(); - if (uint > std::numeric_limits::max()) - return; - - target = uint; -} - -template -void parse(const rapidjson::Value& value, std::array& target, const char* name) { - if (!value.HasMember(name)) - return; - - const rapidjson::Value& property = value[name]; - if (!property.IsArray() || property.Size() != N) - return; - - for (rapidjson::SizeType i = 0; i < property.Size(); i++) - if (!property[i].IsNumber()) - return; - - for (rapidjson::SizeType i = 0; i < property.Size(); i++) - target[i] = property[i].GetDouble(); -} - -void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) { - parse(value, tiles, "tiles"); - parse(value, min_zoom, "minzoom"); - parse(value, max_zoom, "maxzoom"); - parse(value, attribution, "attribution"); - parse(value, center, "center"); - parse(value, bounds, "bounds"); -} - -std::string SourceInfo::tileURL(const TileID& id, float pixelRatio) const { - std::string result = tiles.at(0); - result = util::mapbox::normalizeTileURL(result, url, type); - result = util::replaceTokens(result, [&](const std::string &token) -> std::string { - if (token == "z") return util::toString(std::min(id.z, static_cast(max_zoom))); - if (token == "x") return util::toString(id.x); - if (token == "y") return util::toString(id.y); - if (token == "prefix") { - std::string prefix { 2 }; - prefix[0] = "0123456789abcdef"[id.x % 16]; - prefix[1] = "0123456789abcdef"[id.y % 16]; - return prefix; - } - if (token == "ratio") return pixelRatio > 1.0 ? "@2x" : ""; - return ""; - }); - return result; -} - Source::Source() {} Source::~Source() = default; diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 4d985091f5f..dd94dc1fe2d 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -1,24 +1,13 @@ #ifndef MBGL_MAP_SOURCE #define MBGL_MAP_SOURCE -#include -#include #include -#include +#include -#include #include -#include -#include -#include -#include - -#include #include -#include #include -#include namespace mbgl { @@ -30,23 +19,6 @@ class Tile; struct ClipID; struct box; -class SourceInfo : private util::noncopyable { -public: - SourceType type = SourceType::Vector; - std::string url; - std::vector tiles; - uint16_t tile_size = util::tileSize; - uint16_t min_zoom = 0; - uint16_t max_zoom = 22; - std::string attribution; - std::array center = {{0, 0, 0}}; - std::array bounds = {{-180, -90, 180, 90}}; - std::string source_id = ""; - - void parseTileJSONProperties(const rapidjson::Value&); - std::string tileURL(const TileID& id, float pixelRatio) const; -}; - class Source : private util::noncopyable { public: class Observer { diff --git a/src/mbgl/map/source_info.cpp b/src/mbgl/map/source_info.cpp new file mode 100644 index 00000000000..b8ab84b5500 --- /dev/null +++ b/src/mbgl/map/source_info.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +namespace mbgl { + +namespace { + +void parse(const rapidjson::Value& value, std::vector& target, const char* name) { + if (!value.HasMember(name)) { + return; + } + + const rapidjson::Value& property = value[name]; + if (!property.IsArray()) { + return; + } + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) { + if (!property[i].IsString()) { + return; + } + } + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) { + target.emplace_back(std::string(property[i].GetString(), property[i].GetStringLength())); + } +} + +void parse(const rapidjson::Value& value, std::string& target, const char* name) { + if (!value.HasMember(name)) { + return; + } + + const rapidjson::Value& property = value[name]; + if (!property.IsString()) { + return; + } + + target = { property.GetString(), property.GetStringLength() }; +} + +void parse(const rapidjson::Value& value, uint16_t& target, const char* name) { + if (!value.HasMember(name)) { + return; + } + + const rapidjson::Value& property = value[name]; + if (!property.IsUint()) { + return; + } + + unsigned int uint = property.GetUint(); + if (uint > std::numeric_limits::max()) { + return; + } + + target = uint; +} + +template +void parse(const rapidjson::Value& value, std::array& target, const char* name) { + if (!value.HasMember(name)) { + return; + } + + const rapidjson::Value& property = value[name]; + if (!property.IsArray() || property.Size() != N) { + return; + } + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) { + if (!property[i].IsNumber()) { + return; + } + } + + for (rapidjson::SizeType i = 0; i < property.Size(); i++) { + target[i] = property[i].GetDouble(); + } +} + +} // end namespace + +void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) { + parse(value, tiles, "tiles"); + parse(value, min_zoom, "minzoom"); + parse(value, max_zoom, "maxzoom"); + parse(value, attribution, "attribution"); + parse(value, center, "center"); + parse(value, bounds, "bounds"); +} + +std::string SourceInfo::tileURL(const TileID& id, float pixelRatio) const { + std::string result = tiles.at(0); + result = util::mapbox::normalizeTileURL(result, url, type); + result = util::replaceTokens(result, [&](const std::string& token) -> std::string { + if (token == "z") { + return util::toString(std::min(id.z, static_cast(max_zoom))); + } else if (token == "x") { + return util::toString(id.x); + } else if (token == "y") { + return util::toString(id.y); + } else if (token == "prefix") { + std::string prefix{ 2 }; + prefix[0] = "0123456789abcdef"[id.x % 16]; + prefix[1] = "0123456789abcdef"[id.y % 16]; + return prefix; + } else if (token == "ratio") { + return pixelRatio > 1.0 ? "@2x" : ""; + } else { + return ""; + } + }); + return result; +} + +} // namespace mbgl diff --git a/src/mbgl/map/source_info.hpp b/src/mbgl/map/source_info.hpp new file mode 100644 index 00000000000..725e9f42492 --- /dev/null +++ b/src/mbgl/map/source_info.hpp @@ -0,0 +1,38 @@ +#ifndef MBGL_MAP_SOURCE_INFO +#define MBGL_MAP_SOURCE_INFO + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace mbgl { + +class SourceInfo : private util::noncopyable { +public: + SourceType type = SourceType::Vector; + std::string url; + std::vector tiles; + uint16_t tile_size = util::tileSize; + uint16_t min_zoom = 0; + uint16_t max_zoom = 22; + std::string attribution; + std::array center = { { 0, 0, 0 } }; + std::array bounds = { { -180, -90, 180, 90 } }; + std::string source_id = ""; + + void parseTileJSONProperties(const rapidjson::Value&); + std::string tileURL(const TileID& id, float pixelRatio) const; +}; + +} // namespace mbgl + +#endif // MBGL_MAP_SOURCE_INFO From 132b68e4fc277da10e2fbc457e54931e1c8ffd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 8 Dec 2015 16:36:33 -0800 Subject: [PATCH 2/7] [core] split Source parsing into separate functions --- src/mbgl/style/style_parser.cpp | 84 ++++++++++++++++++++++++--------- src/mbgl/style/style_parser.hpp | 2 + 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index a22615d4356..d029f633af9 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -73,39 +73,81 @@ void StyleParser::parseSources(const JSVal& value) { source->info.type = SourceTypeClass({ typeVal.GetString(), typeVal.GetStringLength() }); - if (sourceVal.HasMember("url")) { - const JSVal& urlVal = sourceVal["url"]; - - if (!urlVal.IsString()) { - Log::Warning(Event::ParseStyle, "source url must be a string"); + switch (source->info.type) { + case SourceType::Vector: + if (!parseVectorSource(*source, sourceVal)) { + continue; + } + break; + case SourceType::Raster: + if (!parseRasterSource(*source, sourceVal)) { continue; } + break; + default: + Log::Warning(Event::ParseStyle, "source type %s is not supported", SourceTypeClass(source->info.type).c_str()); + } - source->info.url = { urlVal.GetString(), urlVal.GetStringLength() }; + sourcesMap.emplace(source->info.source_id, source.get()); + sources.emplace_back(std::move(source)); + } +} + +bool StyleParser::parseVectorSource(Source& source, const JSVal& sourceVal) { + // A vector tile source either specifies the URL of a TileJSON file... + if (sourceVal.HasMember("url")) { + const JSVal& urlVal = sourceVal["url"]; + + if (!urlVal.IsString()) { + Log::Warning(Event::ParseStyle, "source url must be a string"); + return false; } - if (sourceVal.HasMember("tileSize")) { - const JSVal& tileSizeVal = sourceVal["tileSize"]; + source.info.url = { urlVal.GetString(), urlVal.GetStringLength() }; - if (!tileSizeVal.IsUint()) { - Log::Warning(Event::ParseStyle, "source tileSize must be an unsigned integer"); - continue; - } + } else { + // ...or the TileJSON directly. + source.info.parseTileJSONProperties(sourceVal); + } - unsigned int intValue = tileSizeVal.GetUint(); - if (intValue > std::numeric_limits::max()) { - Log::Warning(Event::ParseStyle, "values for tileSize that are larger than %d are not supported", std::numeric_limits::max()); - continue; - } + return true; +} - source->info.tile_size = intValue; +bool StyleParser::parseRasterSource(Source& source, const JSVal& sourceVal) { + if (sourceVal.HasMember("tileSize")) { + const JSVal& tileSizeVal = sourceVal["tileSize"]; + + if (!tileSizeVal.IsUint()) { + Log::Warning(Event::ParseStyle, "source tileSize must be an unsigned integer"); + return false; } - source->info.parseTileJSONProperties(sourceVal); + unsigned int intValue = tileSizeVal.GetUint(); + if (intValue > std::numeric_limits::max()) { + Log::Warning(Event::ParseStyle, "values for tileSize that are larger than %d are not supported", std::numeric_limits::max()); + return false; + } - sourcesMap.emplace(source->info.source_id, source.get()); - sources.emplace_back(std::move(source)); + source.info.tile_size = intValue; + } + + // A raster tile source either specifies the URL of a TileJSON file... + if (sourceVal.HasMember("url")) { + const JSVal& urlVal = sourceVal["url"]; + + if (!urlVal.IsString()) { + Log::Warning(Event::ParseStyle, "source url must be a string"); + return false; + } + + source.info.url = { urlVal.GetString(), urlVal.GetStringLength() }; + + } else { + // ...or the TileJSON directly. + source.info.parseTileJSONProperties(sourceVal); } + + return true; } void StyleParser::parseLayers(const JSVal& value) { diff --git a/src/mbgl/style/style_parser.hpp b/src/mbgl/style/style_parser.hpp index 2c5a43065da..2307517e37d 100644 --- a/src/mbgl/style/style_parser.hpp +++ b/src/mbgl/style/style_parser.hpp @@ -33,6 +33,8 @@ class StyleParser { private: void parseSources(const JSVal&); + bool parseVectorSource(Source&, const JSVal&); + bool parseRasterSource(Source&, const JSVal&); void parseLayers(const JSVal&); void parseLayer(const std::string& id, const JSVal&, std::unique_ptr&); void parseVisibility(StyleLayer&, const JSVal& value); From 2f973d0a7e29bdaaa0fc8f2b13d040ef0c189544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 8 Dec 2015 18:25:04 -0800 Subject: [PATCH 3/7] [core] parse GeoJSON source type --- src/mbgl/map/source.cpp | 29 ++++++++--------- src/mbgl/style/style_parser.cpp | 31 +++++++++++++++++++ src/mbgl/style/style_parser.hpp | 1 + .../geojson-data-inline.info.json | 6 ++++ .../geojson-data-inline.style.json | 9 ++++++ .../style_parser/geojson-data-url.info.json | 6 ++++ .../style_parser/geojson-data-url.style.json | 9 ++++++ .../geojson-invalid-data.info.json | 7 +++++ .../geojson-invalid-data.style.json | 9 ++++++ .../geojson-missing-data.info.json | 7 +++++ .../geojson-missing-data.style.json | 8 +++++ 11 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 test/fixtures/style_parser/geojson-data-inline.info.json create mode 100644 test/fixtures/style_parser/geojson-data-inline.style.json create mode 100644 test/fixtures/style_parser/geojson-data-url.info.json create mode 100644 test/fixtures/style_parser/geojson-data-url.style.json create mode 100644 test/fixtures/style_parser/geojson-invalid-data.info.json create mode 100644 test/fixtures/style_parser/geojson-invalid-data.style.json create mode 100644 test/fixtures/style_parser/geojson-missing-data.info.json create mode 100644 test/fixtures/style_parser/geojson-missing-data.style.json diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 548c837add4..a761d28ab55 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -167,9 +167,7 @@ TileData::State Source::addTile(const TileID& id, const StyleUpdateParameters& p return state; } - auto pos = tiles.emplace(id, std::make_unique(id)); - - Tile& new_tile = *pos.first->second; + auto newTile = std::make_unique(id); // We couldn't find the tile in the list. Create a new one. // Try to find the associated TileData object. @@ -178,19 +176,19 @@ TileData::State Source::addTile(const TileID& id, const StyleUpdateParameters& p auto it = tileDataMap.find(normalized_id); if (it != tileDataMap.end()) { // Create a shared_ptr handle. Note that this might be empty! - new_tile.data = it->second.lock(); + newTile->data = it->second.lock(); } - if (new_tile.data && new_tile.data->getState() == TileData::State::obsolete) { + if (newTile->data && newTile->data->getState() == TileData::State::obsolete) { // Do not consider the tile if it's already obsolete. - new_tile.data.reset(); + newTile->data.reset(); } - if (!new_tile.data) { - new_tile.data = cache.get(normalized_id.to_uint64()); + if (!newTile->data) { + newTile->data = cache.get(normalized_id.to_uint64()); } - if (!new_tile.data) { + if (!newTile->data) { auto callback = std::bind(&Source::tileLoadingCompleteCallback, this, normalized_id, parameters.transformState, parameters.debugOptions & MapDebugOptions::Collision); // If we don't find working tile data, we're just going to load it. @@ -201,7 +199,7 @@ TileData::State Source::addTile(const TileID& id, const StyleUpdateParameters& p parameters.worker); tileData->request(parameters.pixelRatio, callback); - new_tile.data = tileData; + newTile->data = tileData; } else { std::unique_ptr monitor; @@ -210,20 +208,23 @@ TileData::State Source::addTile(const TileID& id, const StyleUpdateParameters& p } else if (info.type == SourceType::Annotations) { monitor = std::make_unique(normalized_id, parameters.data); } else { - throw std::runtime_error("source type not implemented"); + Log::Warning(Event::Style, "Source type '%s' is not implemented", SourceTypeClass(info.type).c_str()); + return TileData::State::invalid; } - new_tile.data = std::make_shared(normalized_id, + newTile->data = std::make_shared(normalized_id, std::move(monitor), info.source_id, parameters.style, callback); } - tileDataMap.emplace(new_tile.data->id, new_tile.data); + tileDataMap.emplace(newTile->data->id, newTile->data); } - return new_tile.data->getState(); + const auto newState = newTile->data->getState(); + tiles.emplace(id, std::move(newTile)); + return newState; } double Source::getZoom(const TransformState& state) const { diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index d029f633af9..483d096a7f1 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -8,6 +8,9 @@ #include +#include +#include + #include namespace mbgl { @@ -84,6 +87,11 @@ void StyleParser::parseSources(const JSVal& value) { continue; } break; + case SourceType::GeoJSON: + if (!parseGeoJSONSource(*source, sourceVal)) { + continue; + } + break; default: Log::Warning(Event::ParseStyle, "source type %s is not supported", SourceTypeClass(source->info.type).c_str()); } @@ -150,6 +158,29 @@ bool StyleParser::parseRasterSource(Source& source, const JSVal& sourceVal) { return true; } +bool StyleParser::parseGeoJSONSource(Source& source, const JSVal& sourceVal) { + if (!sourceVal.HasMember("data")) { + Log::Warning(Event::ParseStyle, "GeoJSON source must have a data value"); + return false; + } + + const JSVal& dataVal = sourceVal["data"]; + if (dataVal.IsString()) { + // We need to load an external GeoJSON file + source.info.url = { dataVal.GetString(), dataVal.GetStringLength() }; + + } else if (dataVal.IsObject()) { + // We need to parse dataVal as a GeoJSON object + auto geojsonvt = std::make_unique(mapbox::geojsonvt::Convert::convert(dataVal, 0)); + // TODO + } else { + Log::Warning(Event::ParseStyle, "GeoJSON data must be a URL or an object"); + return false; + } + + return true; +} + void StyleParser::parseLayers(const JSVal& value) { std::vector ids; diff --git a/src/mbgl/style/style_parser.hpp b/src/mbgl/style/style_parser.hpp index 2307517e37d..898f39bd01d 100644 --- a/src/mbgl/style/style_parser.hpp +++ b/src/mbgl/style/style_parser.hpp @@ -35,6 +35,7 @@ class StyleParser { void parseSources(const JSVal&); bool parseVectorSource(Source&, const JSVal&); bool parseRasterSource(Source&, const JSVal&); + bool parseGeoJSONSource(Source&, const JSVal&); void parseLayers(const JSVal&); void parseLayer(const std::string& id, const JSVal&, std::unique_ptr&); void parseVisibility(StyleLayer&, const JSVal& value); diff --git a/test/fixtures/style_parser/geojson-data-inline.info.json b/test/fixtures/style_parser/geojson-data-inline.info.json new file mode 100644 index 00000000000..9c25a2f4888 --- /dev/null +++ b/test/fixtures/style_parser/geojson-data-inline.info.json @@ -0,0 +1,6 @@ +{ + "default": { + "log": [ + ] + } +} diff --git a/test/fixtures/style_parser/geojson-data-inline.style.json b/test/fixtures/style_parser/geojson-data-inline.style.json new file mode 100644 index 00000000000..fc4fe97c784 --- /dev/null +++ b/test/fixtures/style_parser/geojson-data-inline.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson", + "data": { "type": "Feature", "geometry": { "type": "Point", "coordinates": [100.0, 0.0] } } + } + } +} diff --git a/test/fixtures/style_parser/geojson-data-url.info.json b/test/fixtures/style_parser/geojson-data-url.info.json new file mode 100644 index 00000000000..9c25a2f4888 --- /dev/null +++ b/test/fixtures/style_parser/geojson-data-url.info.json @@ -0,0 +1,6 @@ +{ + "default": { + "log": [ + ] + } +} diff --git a/test/fixtures/style_parser/geojson-data-url.style.json b/test/fixtures/style_parser/geojson-data-url.style.json new file mode 100644 index 00000000000..a5e1c99628f --- /dev/null +++ b/test/fixtures/style_parser/geojson-data-url.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson", + "data": "asset://TEST_DATA/fixtures/geojson/point.json" + } + } +} diff --git a/test/fixtures/style_parser/geojson-invalid-data.info.json b/test/fixtures/style_parser/geojson-invalid-data.info.json new file mode 100644 index 00000000000..ec4a7e2b750 --- /dev/null +++ b/test/fixtures/style_parser/geojson-invalid-data.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "ERROR", "ParseStyle", "GeoJSON data must be a URL or an object"] + ] + } +} diff --git a/test/fixtures/style_parser/geojson-invalid-data.style.json b/test/fixtures/style_parser/geojson-invalid-data.style.json new file mode 100644 index 00000000000..ca1b275967b --- /dev/null +++ b/test/fixtures/style_parser/geojson-invalid-data.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson", + "data": 24 + } + } +} diff --git a/test/fixtures/style_parser/geojson-missing-data.info.json b/test/fixtures/style_parser/geojson-missing-data.info.json new file mode 100644 index 00000000000..594d01d19df --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-data.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "GeoJSON source must have a data value"] + ] + } +} diff --git a/test/fixtures/style_parser/geojson-missing-data.style.json b/test/fixtures/style_parser/geojson-missing-data.style.json new file mode 100644 index 00000000000..8af64b01760 --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-data.style.json @@ -0,0 +1,8 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson" + } + } +} From 0f9922cff59772375d81202a43284b732b6aacb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Wed, 9 Dec 2015 16:24:51 -0800 Subject: [PATCH 4/7] [core] create GeoJSON tiles from inline GeoJSON in sources --- src/mbgl/map/source.cpp | 3 + src/mbgl/map/source_info.cpp | 5 ++ src/mbgl/map/source_info.hpp | 9 +++ src/mbgl/style/style_parser.cpp | 5 +- src/mbgl/tile/geojson_tile.cpp | 134 ++++++++++++++++++++++++++++++++ src/mbgl/tile/geojson_tile.hpp | 78 +++++++++++++++++++ 6 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 src/mbgl/tile/geojson_tile.cpp create mode 100644 src/mbgl/tile/geojson_tile.hpp diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index a761d28ab55..28ea69f0072 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -207,6 +208,8 @@ TileData::State Source::addTile(const TileID& id, const StyleUpdateParameters& p monitor = std::make_unique(info, normalized_id, parameters.pixelRatio); } else if (info.type == SourceType::Annotations) { monitor = std::make_unique(normalized_id, parameters.data); + } else if (info.type == SourceType::GeoJSON) { + monitor = std::make_unique(info.geojsonvt.get(), normalized_id); } else { Log::Warning(Event::Style, "Source type '%s' is not implemented", SourceTypeClass(info.type).c_str()); return TileData::State::invalid; diff --git a/src/mbgl/map/source_info.cpp b/src/mbgl/map/source_info.cpp index b8ab84b5500..3a79cb9dffb 100644 --- a/src/mbgl/map/source_info.cpp +++ b/src/mbgl/map/source_info.cpp @@ -3,6 +3,8 @@ #include #include +#include + namespace mbgl { namespace { @@ -83,6 +85,9 @@ void parse(const rapidjson::Value& value, std::array& target, const ch } // end namespace +// Destructor in implementation file because header only contains forward declarations. +SourceInfo::~SourceInfo() = default; + void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) { parse(value, tiles, "tiles"); parse(value, min_zoom, "minzoom"); diff --git a/src/mbgl/map/source_info.hpp b/src/mbgl/map/source_info.hpp index 725e9f42492..5b4e4e2e152 100644 --- a/src/mbgl/map/source_info.hpp +++ b/src/mbgl/map/source_info.hpp @@ -14,10 +14,18 @@ #include #include +namespace mapbox { +namespace geojsonvt { +class GeoJSONVT; +} // namespace geojsonvt +} // namespace mapbox + namespace mbgl { class SourceInfo : private util::noncopyable { public: + ~SourceInfo(); + SourceType type = SourceType::Vector; std::string url; std::vector tiles; @@ -28,6 +36,7 @@ class SourceInfo : private util::noncopyable { std::array center = { { 0, 0, 0 } }; std::array bounds = { { -180, -90, 180, 90 } }; std::string source_id = ""; + std::unique_ptr geojsonvt; void parseTileJSONProperties(const rapidjson::Value&); std::string tileURL(const TileID& id, float pixelRatio) const; diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index 483d096a7f1..259517b0a55 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -168,11 +168,10 @@ bool StyleParser::parseGeoJSONSource(Source& source, const JSVal& sourceVal) { if (dataVal.IsString()) { // We need to load an external GeoJSON file source.info.url = { dataVal.GetString(), dataVal.GetStringLength() }; - } else if (dataVal.IsObject()) { // We need to parse dataVal as a GeoJSON object - auto geojsonvt = std::make_unique(mapbox::geojsonvt::Convert::convert(dataVal, 0)); - // TODO + using namespace mapbox::geojsonvt; + source.info.geojsonvt = std::make_unique(Convert::convert(dataVal, 0)); } else { Log::Warning(Event::ParseStyle, "GeoJSON data must be a URL or an object"); return false; diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp new file mode 100644 index 00000000000..9f14780ca40 --- /dev/null +++ b/src/mbgl/tile/geojson_tile.cpp @@ -0,0 +1,134 @@ +#include +#include +#include + +namespace mbgl { + +GeoJSONTileFeature::GeoJSONTileFeature(FeatureType type_, + GeometryCollection&& geometries_, + GeoJSONTileFeature::Tags&& tags_) + : type(type_), geometries(std::move(geometries_)), tags(std::move(tags_)) { +} + +FeatureType GeoJSONTileFeature::getType() const { + return type; +} + +mapbox::util::optional GeoJSONTileFeature::getValue(const std::string& key) const { + auto it = tags.find(key); + if (it != tags.end()) { + return mapbox::util::optional(it->second); + } + return mapbox::util::optional(); +} + +GeometryCollection GeoJSONTileFeature::getGeometries() const { + return geometries; +} + +GeoJSONTileLayer::GeoJSONTileLayer(Features&& features_) : features(std::move(features_)) { +} + +std::size_t GeoJSONTileLayer::featureCount() const { + return features.size(); +} + +util::ptr GeoJSONTileLayer::getFeature(std::size_t i) const { + return features[i]; +} + +GeoJSONTile::GeoJSONTile(std::shared_ptr layer_) : layer(std::move(layer_)) { +} + +util::ptr GeoJSONTile::getLayer(const std::string&) const { + // We're ignoring the layer name because GeoJSON tiles only have one layer. + return layer; +} + +// Converts the geojsonvt::Tile to a a GeoJSONTile. They have a differing internal structure. +std::unique_ptr convertTile(const mapbox::geojsonvt::Tile& tile) { + std::shared_ptr layer; + + if (tile) { + std::vector> features; + std::vector line; + + for (auto& feature : tile.features) { + const FeatureType featureType = + (feature.type == mapbox::geojsonvt::TileFeatureType::Point + ? FeatureType::Point + : (feature.type == mapbox::geojsonvt::TileFeatureType::LineString + ? FeatureType::LineString + : (feature.type == mapbox::geojsonvt::TileFeatureType::Polygon + ? FeatureType::Polygon + : FeatureType::Unknown))); + if (featureType == FeatureType::Unknown) { + continue; + } + + GeometryCollection geometry; + + // Flatten the geometry; GeoJSONVT distinguishes between a Points array and Rings array + // (Points = GeoJSON types Point, MultiPoint, LineString) + // (Rings = GeoJSON types MultiLineString, Polygon, MultiPolygon) + // However, in Mapbox GL, we use one structure for both types, and just have one outer + // element for Points. + if (feature.tileGeometry.is()) { + line.clear(); + for (auto& point : feature.tileGeometry.get()) { + line.emplace_back(point.x, point.y); + } + geometry.emplace_back(std::move(line)); + } else if (feature.tileGeometry.is()) { + for (auto& ring : feature.tileGeometry.get()) { + line.clear(); + for (auto& point : ring) { + line.emplace_back(point.x, point.y); + } + geometry.emplace_back(std::move(line)); + } + } + + GeoJSONTileFeature::Tags tags{ feature.tags.begin(), feature.tags.end() }; + + features.emplace_back(std::make_shared( + featureType, std::move(geometry), std::move(tags))); + } + + layer = std::make_unique(std::move(features)); + } + + return std::make_unique(layer); +} + +GeoJSONTileMonitor::GeoJSONTileMonitor(mapbox::geojsonvt::GeoJSONVT* geojsonvt_, const TileID& id) + : tileID(id), geojsonvt(geojsonvt_) { +} + +GeoJSONTileMonitor::~GeoJSONTileMonitor() = default; + +// A monitor can have its GeoJSONVT object swapped out (e.g. when loading a new GeoJSON file). +// In that case, we're sending new notifications to all observers. +void GeoJSONTileMonitor::setGeoJSONVT(mapbox::geojsonvt::GeoJSONVT* vt) { + // Don't duplicate notifications in case of nil changes. + if (geojsonvt != vt) { + geojsonvt = vt; + update(); + } +} + +void GeoJSONTileMonitor::update() { + if (geojsonvt) { + auto tile = convertTile(geojsonvt->getTile(tileID.z, tileID.x, tileID.y)); + callback(nullptr, std::move(tile), Seconds::zero(), Seconds::zero()); + } +} + +std::unique_ptr +GeoJSONTileMonitor::monitorTile(const GeometryTileMonitor::Callback& cb) { + callback = cb; + update(); + return nullptr; +} + +} // namespace mbgl diff --git a/src/mbgl/tile/geojson_tile.hpp b/src/mbgl/tile/geojson_tile.hpp new file mode 100644 index 00000000000..e5e9766a179 --- /dev/null +++ b/src/mbgl/tile/geojson_tile.hpp @@ -0,0 +1,78 @@ +#ifndef MBGL_ANNOTATION_GEOJSON_VT_TILE +#define MBGL_ANNOTATION_GEOJSON_VT_TILE + +#include +#include + +#include + +namespace mapbox { +namespace geojsonvt { +class GeoJSONVT; +} // namespace geojsonvt +} // namespace mapbox + +namespace mbgl { + +// Implements a simple in-memory Tile type that holds GeoJSON values. A GeoJSON tile can only have +// one layer, and it is always returned regardless of which layer is requested. + +class GeoJSONTileFeature : public GeometryTileFeature { +public: + using Tags = std::unordered_map; + + GeoJSONTileFeature(FeatureType, GeometryCollection&&, Tags&& = Tags{}); + FeatureType getType() const override; + mapbox::util::optional getValue(const std::string&) const override; + GeometryCollection getGeometries() const override; + +private: + const FeatureType type; + const GeometryCollection geometries; + const Tags tags; +}; + +class GeoJSONTileLayer : public GeometryTileLayer { +public: + using Features = std::vector>; + + GeoJSONTileLayer(Features&&); + std::size_t featureCount() const override; + util::ptr getFeature(std::size_t) const override; + +private: + const Features features; +}; + +class GeoJSONTile : public GeometryTile { +public: + GeoJSONTile(std::shared_ptr); + util::ptr getLayer(const std::string&) const override; + +private: + const std::shared_ptr layer; +}; + +class GeoJSONTileMonitor : public GeometryTileMonitor { +public: + GeoJSONTileMonitor(mapbox::geojsonvt::GeoJSONVT*, const TileID&); + virtual ~GeoJSONTileMonitor(); + + std::unique_ptr monitorTile(const GeometryTileMonitor::Callback&) override; + + void setGeoJSONVT(mapbox::geojsonvt::GeoJSONVT*); + +private: + void update(); + +public: + const TileID tileID; + +private: + mapbox::geojsonvt::GeoJSONVT* geojsonvt = nullptr; + GeometryTileMonitor::Callback callback; +}; + +} // namespace mbgl + +#endif From b0bec13601ec6ea3892e4896b4e177f3e42c8314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 10 Dec 2015 13:41:00 -0800 Subject: [PATCH 5/7] [build] make sure we're building the config file for node Xcode projects --- scripts/main.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/main.mk b/scripts/main.mk index c65a7aedde1..d2cf13b5244 100644 --- a/scripts/main.mk +++ b/scripts/main.mk @@ -109,7 +109,7 @@ node/configure: $(QUIET)$(ENV) $(NODE_PRE_GYP) configure --clang -- \ $(GYP_FLAGS) -Dlibuv_cflags= -Dlibuv_ldflags= -Dlibuv_static_libs= -node/xproj: +node/xproj: Xcode/__project__ node/configure $(QUIET)$(ENV) $(NODE_PRE_GYP) configure --clang -- \ $(GYP_FLAGS) -f xcode -Dlibuv_cflags= -Dlibuv_ldflags= -Dlibuv_static_libs= $(QUIET)$(ENV) ./scripts/node/create_node_scheme.sh "node test" "`npm bin tape`/tape platform/node/test/js/**/*.test.js" From c4a01d51ea755089401f767ad258126969615c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 10 Dec 2015 14:07:23 -0800 Subject: [PATCH 6/7] [core] don't fail on invalid inline GeoJSON data --- src/mbgl/style/style_parser.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index 259517b0a55..dd89b84e699 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -171,9 +171,16 @@ bool StyleParser::parseGeoJSONSource(Source& source, const JSVal& sourceVal) { } else if (dataVal.IsObject()) { // We need to parse dataVal as a GeoJSON object using namespace mapbox::geojsonvt; - source.info.geojsonvt = std::make_unique(Convert::convert(dataVal, 0)); + try { + source.info.geojsonvt = std::make_unique(Convert::convert(dataVal, 0)); + } catch (const std::exception& ex) { + Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s", ex.what()); + // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for + // tiles to load. + source.info.geojsonvt = std::make_unique(std::vector{}); + } } else { - Log::Warning(Event::ParseStyle, "GeoJSON data must be a URL or an object"); + Log::Error(Event::ParseStyle, "GeoJSON data must be a URL or an object"); return false; } From 8d711e9c80f92b165ef3cfff14be132f70039b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 10 Dec 2015 17:46:13 -0800 Subject: [PATCH 7/7] [test] update test suite to include more comprehensive GeoJSON tests --- package.json | 2 +- scripts/android/configure.sh | 2 +- scripts/ios/configure.sh | 2 +- scripts/linux/configure.sh | 2 +- scripts/osx/configure.sh | 2 +- src/mbgl/map/source.cpp | 8 +++++++- src/mbgl/map/source_info.cpp | 15 +++++++++++++++ src/mbgl/map/source_info.hpp | 1 + src/mbgl/style/style_parser.cpp | 13 +------------ 9 files changed, 29 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 1be43627326..7ebe666fa04 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ ], "devDependencies": { "aws-sdk": "^2.2.21", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#6e2b48155ca651b37826942f2ca082be687b7a42", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#78bde0077848b4af0efd490d124bde3ea9f56ec9", "node-gyp": "^3.2.1", "request": "^2.67.0", "tape": "^4.2.2" diff --git a/scripts/android/configure.sh b/scripts/android/configure.sh index bc19d546a1a..7c5e3e926a2 100644 --- a/scripts/android/configure.sh +++ b/scripts/android/configure.sh @@ -8,7 +8,7 @@ LIBUV_VERSION=1.7.5 ZLIB_VERSION=system NUNICODE_VERSION=1.6 LIBZIP_VERSION=0.11.2 -GEOJSONVT_VERSION=3.0.0 +GEOJSONVT_VERSION=3.0.1 VARIANT_VERSION=1.0 RAPIDJSON_VERSION=1.0.2 diff --git a/scripts/ios/configure.sh b/scripts/ios/configure.sh index 06d66c5850a..3826d935713 100644 --- a/scripts/ios/configure.sh +++ b/scripts/ios/configure.sh @@ -4,6 +4,6 @@ BOOST_VERSION=1.59.0 SQLITE_VERSION=system LIBUV_VERSION=1.7.5 ZLIB_VERSION=system -GEOJSONVT_VERSION=3.0.0 +GEOJSONVT_VERSION=3.0.1 VARIANT_VERSION=1.0 RAPIDJSON_VERSION=1.0.2 diff --git a/scripts/linux/configure.sh b/scripts/linux/configure.sh index 0f7e6d167e6..0da9f8c3cd9 100644 --- a/scripts/linux/configure.sh +++ b/scripts/linux/configure.sh @@ -10,7 +10,7 @@ SQLITE_VERSION=3.9.1 LIBUV_VERSION=1.7.5 ZLIB_VERSION=system NUNICODE_VERSION=1.6 -GEOJSONVT_VERSION=3.0.0 +GEOJSONVT_VERSION=3.0.1 VARIANT_VERSION=1.0 RAPIDJSON_VERSION=1.0.2 GTEST_VERSION=1.7.0 diff --git a/scripts/osx/configure.sh b/scripts/osx/configure.sh index d2eee364791..871b09c9413 100644 --- a/scripts/osx/configure.sh +++ b/scripts/osx/configure.sh @@ -10,7 +10,7 @@ SQLITE_VERSION=3.9.1 LIBUV_VERSION=1.7.5 ZLIB_VERSION=system NUNICODE_VERSION=1.6 -GEOJSONVT_VERSION=3.0.0 +GEOJSONVT_VERSION=3.0.1 VARIANT_VERSION=1.0 RAPIDJSON_VERSION=1.0.2 GTEST_VERSION=1.7.0 diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 28ea69f0072..7dd5709c43f 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -61,6 +61,7 @@ void Source::load() { return; } + // URL may either be a TileJSON file, or a GeoJSON file. FileSource* fs = util::ThreadContext::getFileSource(); req = fs->request({ Resource::Kind::Source, info.url }, [this](Response res) { if (res.stale) { @@ -86,7 +87,12 @@ void Source::load() { return; } - info.parseTileJSONProperties(d); + if (info.type == SourceType::Vector || info.type == SourceType::Raster) { + info.parseTileJSONProperties(d); + } else if (info.type == SourceType::GeoJSON) { + info.parseGeoJSON(d); + } + loaded = true; emitSourceLoaded(); diff --git a/src/mbgl/map/source_info.cpp b/src/mbgl/map/source_info.cpp index 3a79cb9dffb..8f1b42ea934 100644 --- a/src/mbgl/map/source_info.cpp +++ b/src/mbgl/map/source_info.cpp @@ -1,9 +1,11 @@ +#include #include #include #include #include #include +#include namespace mbgl { @@ -97,6 +99,19 @@ void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) { parse(value, bounds, "bounds"); } +void SourceInfo::parseGeoJSON(const rapidjson::Value& value) { + using namespace mapbox::geojsonvt; + + try { + geojsonvt = std::make_unique(Convert::convert(value, 0)); + } catch (const std::exception& ex) { + Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s", ex.what()); + // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for + // tiles to load. + geojsonvt = std::make_unique(std::vector{}); + } +} + std::string SourceInfo::tileURL(const TileID& id, float pixelRatio) const { std::string result = tiles.at(0); result = util::mapbox::normalizeTileURL(result, url, type); diff --git a/src/mbgl/map/source_info.hpp b/src/mbgl/map/source_info.hpp index 5b4e4e2e152..81c79bd8236 100644 --- a/src/mbgl/map/source_info.hpp +++ b/src/mbgl/map/source_info.hpp @@ -39,6 +39,7 @@ class SourceInfo : private util::noncopyable { std::unique_ptr geojsonvt; void parseTileJSONProperties(const rapidjson::Value&); + void parseGeoJSON(const rapidjson::Value&); std::string tileURL(const TileID& id, float pixelRatio) const; }; diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index dd89b84e699..06f81a8ab2a 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -8,9 +8,6 @@ #include -#include -#include - #include namespace mbgl { @@ -170,15 +167,7 @@ bool StyleParser::parseGeoJSONSource(Source& source, const JSVal& sourceVal) { source.info.url = { dataVal.GetString(), dataVal.GetStringLength() }; } else if (dataVal.IsObject()) { // We need to parse dataVal as a GeoJSON object - using namespace mapbox::geojsonvt; - try { - source.info.geojsonvt = std::make_unique(Convert::convert(dataVal, 0)); - } catch (const std::exception& ex) { - Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s", ex.what()); - // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for - // tiles to load. - source.info.geojsonvt = std::make_unique(std::vector{}); - } + source.info.parseGeoJSON(dataVal); } else { Log::Error(Event::ParseStyle, "GeoJSON data must be a URL or an object"); return false;