summaryrefslogtreecommitdiff
path: root/src/mbgl/style/source.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/style/source.cpp')
-rw-r--r--src/mbgl/style/source.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/src/mbgl/style/source.cpp b/src/mbgl/style/source.cpp
new file mode 100644
index 0000000000..8e5973f412
--- /dev/null
+++ b/src/mbgl/style/source.cpp
@@ -0,0 +1,428 @@
+#include <mbgl/style/source.hpp>
+#include <mbgl/style/source_observer.hpp>
+#include <mbgl/map/transform.hpp>
+#include <mbgl/tile/tile.hpp>
+#include <mbgl/tile/vector_tile.hpp>
+#include <mbgl/annotation/annotation_tile.hpp>
+#include <mbgl/tile/geojson_tile.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/util/exception.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/storage/resource.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/style/layer.hpp>
+#include <mbgl/style/update_parameters.hpp>
+#include <mbgl/style/query_parameters.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/math/minmax.hpp>
+#include <mbgl/math/clamp.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/token.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/tile_cover.hpp>
+
+#include <mbgl/tile/vector_tile_data.hpp>
+#include <mbgl/tile/raster_tile_data.hpp>
+#include <mbgl/style/parser.hpp>
+#include <mbgl/gl/debugging.hpp>
+
+#include <mbgl/algorithm/update_renderables.hpp>
+
+#include <mapbox/geojsonvt.hpp>
+#include <mapbox/geojsonvt/convert.hpp>
+#include <mapbox/geometry/envelope.hpp>
+
+#include <rapidjson/error/en.h>
+
+#include <algorithm>
+#include <sstream>
+
+namespace mbgl {
+namespace style {
+
+static SourceObserver nullObserver;
+
+Source::Source(SourceType type_,
+ const std::string& id_,
+ const std::string& url_,
+ uint16_t tileSize_,
+ std::unique_ptr<Tileset>&& tileset_,
+ std::unique_ptr<mapbox::geojsonvt::GeoJSONVT>&& geojsonvt_)
+ : type(type_),
+ id(id_),
+ url(url_),
+ tileSize(tileSize_),
+ tileset(std::move(tileset_)),
+ geojsonvt(std::move(geojsonvt_)),
+ observer(&nullObserver) {
+}
+
+Source::~Source() = default;
+
+bool Source::isLoaded() const {
+ if (!loaded) return false;
+
+ for (const auto& pair : tileDataMap) {
+ if (!pair.second->isComplete()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Source::isLoading() const {
+ return !loaded && req.operator bool();
+}
+
+void Source::load(FileSource& fileSource) {
+ if (url.empty()) {
+ // In case there is no URL set, we assume that we already have all of the data because the
+ // TileJSON was specified inline in the stylesheet.
+ loaded = true;
+ return;
+ }
+
+ if (req) {
+ // We don't have a Tileset object yet, but there's already a request underway to load
+ // the data.
+ return;
+ }
+
+ // URL may either be a TileJSON file, or a GeoJSON file.
+ req = fileSource.request(Resource::source(url), [this](Response res) {
+ if (res.error) {
+ observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(res.error->message)));
+ } else if (res.notModified) {
+ return;
+ } else if (res.noContent) {
+ observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error("unexpectedly empty source")));
+ } else {
+ bool reloadTiles = false;
+
+ if (type == SourceType::Vector || type == SourceType::Raster) {
+ std::unique_ptr<Tileset> newTileset;
+
+ // Create a new copy of the Tileset object that holds the base values we've parsed
+ // from the stylesheet. Then merge in the values parsed from the TileJSON we retrieved
+ // via the URL.
+ try {
+ newTileset = style::parseTileJSON(*res.data, url, type, tileSize);
+ } catch (...) {
+ observer->onSourceError(*this, std::current_exception());
+ return;
+ }
+
+ // Check whether previous information specifies different tile
+ if (tileset && tileset->tiles != newTileset->tiles) {
+ reloadTiles = true;
+
+ // Tile size changed: We need to recalculate the tiles we need to load because we
+ // might have to load tiles for a different zoom level
+ // This is done automatically when we trigger the onSourceLoaded observer below.
+
+ // Min/Max zoom changed: We need to recalculate what tiles to load, if we have tiles
+ // loaded that are outside the new zoom range
+ // This is done automatically when we trigger the onSourceLoaded observer below.
+
+ // Attribution changed: We need to notify the embedding application that this
+ // changed. See https://github.com/mapbox/mapbox-gl-native/issues/2723
+ // This is not yet implemented.
+
+ // Center/bounds changed: We're not using these values currently
+ }
+
+ tileset = std::move(newTileset);
+ } else if (type == SourceType::GeoJSON) {
+ std::unique_ptr<Tileset> newTileset = std::make_unique<Tileset>();
+
+ rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> d;
+ d.Parse<0>(res.data->c_str());
+
+ if (d.HasParseError()) {
+ std::stringstream message;
+ message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError());
+ observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(message.str())));
+ return;
+ }
+
+ geojsonvt = style::parseGeoJSON(d);
+ reloadTiles = true;
+
+ newTileset->maxZoom = geojsonvt->options.maxZoom;
+ tileset = std::move(newTileset);
+ }
+
+ if (reloadTiles) {
+ // Tile information changed because we got new GeoJSON data, or a new tile URL.
+ tileDataMap.clear();
+ tiles.clear();
+ cache.clear();
+ }
+
+ loaded = true;
+ observer->onSourceLoaded(*this);
+ }
+ });
+}
+
+void Source::updateMatrices(const mat4 &projMatrix, const TransformState &transform) {
+ for (auto& pair : tiles) {
+ auto& tile = pair.second;
+ transform.matrixFor(tile.matrix, tile.id);
+ matrix::multiply(tile.matrix, projMatrix, tile.matrix);
+ }
+}
+
+void Source::finishRender(Painter &painter) {
+ for (auto& pair : tiles) {
+ auto& tile = pair.second;
+ painter.renderTileDebug(tile);
+ }
+}
+
+const std::map<UnwrappedTileID, Tile>& Source::getTiles() const {
+ return tiles;
+}
+
+std::unique_ptr<TileData> Source::createTile(const OverscaledTileID& overscaledTileID,
+ const UpdateParameters& parameters) {
+ std::unique_ptr<TileData> data = cache.get(overscaledTileID);
+ if (data) {
+ return data;
+ }
+
+ auto callback = std::bind(&Source::tileLoadingCallback, this, overscaledTileID,
+ std::placeholders::_1, true);
+
+ // If we don't find working tile data, we're just going to load it.
+ if (type == SourceType::Raster) {
+ data = std::make_unique<RasterTileData>(overscaledTileID, parameters.pixelRatio,
+ tileset->tiles.at(0), parameters.texturePool,
+ parameters.worker, parameters.fileSource, callback);
+ } else {
+ std::unique_ptr<GeometryTileMonitor> monitor;
+
+ if (type == SourceType::Vector) {
+ monitor = std::make_unique<VectorTileMonitor>(overscaledTileID, parameters.pixelRatio, tileset->tiles.at(0), parameters.fileSource);
+ } else if (type == SourceType::Annotations) {
+ monitor = std::make_unique<AnnotationTileMonitor>(overscaledTileID, parameters.annotationManager);
+ } else if (type == SourceType::GeoJSON) {
+ monitor = std::make_unique<GeoJSONTileMonitor>(geojsonvt.get(), overscaledTileID);
+ } else {
+ Log::Warning(Event::Style, "Source type '%s' is not implemented", SourceTypeClass(type).c_str());
+ return nullptr;
+ }
+
+ data = std::make_unique<VectorTileData>(overscaledTileID, std::move(monitor), id,
+ parameters.style, parameters.mode, callback);
+ }
+
+ return data;
+}
+
+TileData* Source::getTileData(const OverscaledTileID& overscaledTileID) const {
+ auto it = tileDataMap.find(overscaledTileID);
+ if (it != tileDataMap.end()) {
+ return it->second.get();
+ } else {
+ return nullptr;
+ }
+}
+
+bool Source::update(const UpdateParameters& parameters) {
+ bool allTilesUpdated = true;
+
+ if (!loaded || parameters.animationTime <= updated) {
+ return allTilesUpdated;
+ }
+
+ // Determine the overzooming/underzooming amounts and required tiles.
+ int32_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize);
+ int32_t dataTileZoom = overscaledZoom;
+
+ std::vector<UnwrappedTileID> idealTiles;
+ if (overscaledZoom >= tileset->minZoom) {
+ int32_t idealZoom = std::min<int32_t>(tileset->maxZoom, overscaledZoom);
+
+ // Make sure we're not reparsing overzoomed raster tiles.
+ if (type == SourceType::Raster) {
+ dataTileZoom = idealZoom;
+ }
+
+ idealTiles = util::tileCover(parameters.transformState, idealZoom);
+ }
+
+ // Stores a list of all the data tiles that we're definitely going to retain. There are two
+ // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in
+ // use because they're still loading. In addition to that, we also need to retain all tiles that
+ // we're actively using, e.g. as a replacement for tile that aren't loaded yet.
+ std::set<OverscaledTileID> retain;
+
+ auto retainTileDataFn = [&retain](const TileData& tileData) -> void {
+ retain.emplace(tileData.id);
+ };
+ auto getTileDataFn = [this](const OverscaledTileID& dataTileID) -> TileData* {
+ return getTileData(dataTileID);
+ };
+ auto createTileDataFn = [this, &parameters](const OverscaledTileID& dataTileID) -> TileData* {
+ if (auto data = createTile(dataTileID, parameters)) {
+ return tileDataMap.emplace(dataTileID, std::move(data)).first->second.get();
+ } else {
+ return nullptr;
+ }
+ };
+ auto renderTileFn = [this](const UnwrappedTileID& renderTileID, TileData& tileData) {
+ tiles.emplace(renderTileID, Tile{ renderTileID, tileData });
+ };
+
+ tiles.clear();
+ algorithm::updateRenderables(getTileDataFn, createTileDataFn, retainTileDataFn, renderTileFn,
+ idealTiles, *tileset, dataTileZoom);
+
+ if (type != SourceType::Raster && type != SourceType::Annotations && cache.getSize() == 0) {
+ size_t conservativeCacheSize =
+ ((float)parameters.transformState.getWidth() / util::tileSize) *
+ ((float)parameters.transformState.getHeight() / util::tileSize) *
+ (parameters.transformState.getMaxZoom() - parameters.transformState.getMinZoom() + 1) *
+ 0.5;
+ cache.setSize(conservativeCacheSize);
+ }
+
+ // Remove stale data tiles from the active set of tiles.
+ // This goes through the (sorted!) tileDataMap and retain set in lockstep and removes items from
+ // tileDataMap that don't have the corresponding key in the retain set.
+ auto dataIt = tileDataMap.begin();
+ auto retainIt = retain.begin();
+ while (dataIt != tileDataMap.end()) {
+ if (retainIt == retain.end() || dataIt->first < *retainIt) {
+ cache.add(dataIt->first, std::move(dataIt->second));
+ tileDataMap.erase(dataIt++);
+ } else {
+ if (!(*retainIt < dataIt->first)) {
+ ++dataIt;
+ }
+ ++retainIt;
+ }
+ }
+
+ for (auto& pair : tileDataMap) {
+ const auto& dataTileID = pair.first;
+ auto tileData = pair.second.get();
+ if (parameters.shouldReparsePartialTiles && tileData->isIncomplete()) {
+ auto callback = std::bind(&Source::tileLoadingCallback, this, dataTileID,
+ std::placeholders::_1, false);
+
+ if (!tileData->parsePending(callback)) {
+ allTilesUpdated = false;
+ }
+ } else {
+ tileData->redoPlacement({ parameters.transformState.getAngle(),
+ parameters.transformState.getPitch(),
+ parameters.debugOptions & MapDebugOptions::Collision },
+ [this]() { observer->onPlacementRedone(); });
+ }
+ }
+
+ updated = parameters.animationTime;
+
+ return allTilesUpdated;
+}
+
+static Point<int16_t> coordinateToTilePoint(const UnwrappedTileID& tileID, const Point<double>& p) {
+ auto zoomedCoord = TileCoordinate { p, 0 }.zoomTo(tileID.canonical.z);
+ return {
+ int16_t(util::clamp<int64_t>((zoomedCoord.p.x - tileID.canonical.x - tileID.wrap * std::pow(2, tileID.canonical.z)) * util::EXTENT,
+ std::numeric_limits<int16_t>::min(),
+ std::numeric_limits<int16_t>::max())),
+ int16_t(util::clamp<int64_t>((zoomedCoord.p.y - tileID.canonical.y) * util::EXTENT,
+ std::numeric_limits<int16_t>::min(),
+ std::numeric_limits<int16_t>::max()))
+ };
+}
+
+std::unordered_map<std::string, std::vector<Feature>> Source::queryRenderedFeatures(const QueryParameters& parameters) const {
+ LineString<double> queryGeometry;
+
+ for (const auto& p : parameters.geometry) {
+ queryGeometry.push_back(TileCoordinate::fromScreenCoordinate(
+ parameters.transformState, 0, { p.x, parameters.transformState.getHeight() - p.y }).p);
+ }
+
+ mapbox::geometry::box<double> box = mapbox::geometry::envelope(queryGeometry);
+
+ std::unordered_map<std::string, std::vector<Feature>> result;
+
+ for (const auto& tilePtr : tiles) {
+ const Tile& tile = tilePtr.second;
+
+ Point<int16_t> tileSpaceBoundsMin = coordinateToTilePoint(tile.id, box.min);
+ Point<int16_t> tileSpaceBoundsMax = coordinateToTilePoint(tile.id, box.max);
+
+ if (tileSpaceBoundsMin.x >= util::EXTENT || tileSpaceBoundsMin.y >= util::EXTENT ||
+ tileSpaceBoundsMax.x < 0 || tileSpaceBoundsMax.y < 0) continue;
+
+ GeometryCoordinates tileSpaceQueryGeometry;
+
+ for (const auto& c : queryGeometry) {
+ tileSpaceQueryGeometry.push_back(coordinateToTilePoint(tile.id, c));
+ }
+
+ tile.data.queryRenderedFeatures(result,
+ tileSpaceQueryGeometry,
+ parameters.transformState,
+ parameters.layerIDs);
+ }
+
+ return result;
+}
+
+void Source::setCacheSize(size_t size) {
+ cache.setSize(size);
+}
+
+void Source::onLowMemory() {
+ cache.clear();
+}
+
+void Source::setObserver(SourceObserver* observer_) {
+ observer = observer_;
+}
+
+void Source::tileLoadingCallback(const OverscaledTileID& tileID,
+ std::exception_ptr error,
+ bool isNewTile) {
+ auto it = tileDataMap.find(tileID);
+ if (it == tileDataMap.end()) {
+ return;
+ }
+
+ auto& tileData = it->second;
+ if (!tileData) {
+ return;
+ }
+
+ if (error) {
+ observer->onTileError(*this, tileID, error);
+ return;
+ }
+
+ tileData->redoPlacement([this]() {
+ observer->onPlacementRedone();
+ });
+ observer->onTileLoaded(*this, tileID, isNewTile);
+}
+
+void Source::dumpDebugLogs() const {
+ Log::Info(Event::General, "Source::id: %s", id.c_str());
+ Log::Info(Event::General, "Source::loaded: %d", loaded);
+
+ for (const auto& pair : tileDataMap) {
+ auto& tileData = pair.second;
+ tileData->dumpDebugLogs();
+ }
+}
+
+} // namespace style
+} // namespace mbgl