#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_, std::unique_ptr&& 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 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 newTileset = std::make_unique(); rapidjson::GenericDocument, 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& Source::getTiles() const { return tiles; } std::unique_ptr Source::createTile(const OverscaledTileID& overscaledTileID, const UpdateParameters& parameters) { if (auto data = cache.get(overscaledTileID)) { 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) { assert(!tileset->tiles.empty()); const auto resource = Resource::tile( tileset->tiles.at(0), parameters.pixelRatio, overscaledTileID.canonical.x, overscaledTileID.canonical.y, overscaledTileID.canonical.z); auto data = std::make_unique(overscaledTileID, parameters.texturePool, parameters.worker, callback); data->setTileSource( std::make_unique(*data, resource, parameters.fileSource)); // Need a std::move here to create a std::unique_ptr from // std::unique_ptr. return std::move(data); } else { auto data = std::make_unique(overscaledTileID, id, parameters.style, parameters.mode, callback); if (type == SourceType::Vector) { assert(!tileset->tiles.empty()); const auto resource = Resource::tile( tileset->tiles.at(0), parameters.pixelRatio, overscaledTileID.canonical.x, overscaledTileID.canonical.y, overscaledTileID.canonical.z); data->setTileSource( std::make_unique(*data, resource, parameters.fileSource)); } else if (type == SourceType::Annotations) { data->setTileSource(std::make_unique( *data, overscaledTileID, parameters.annotationManager)); } else if (type == SourceType::GeoJSON) { data->setTileSource( std::make_unique(*data, geojsonvt.get(), overscaledTileID)); } else { Log::Warning(Event::Style, "Source type '%s' is not implemented", SourceTypeClass(type).c_str()); return nullptr; } // Need a std::move here to create a std::unique_ptr from // std::unique_ptr. return std::move(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 idealTiles; if (overscaledZoom >= tileset->minZoom) { int32_t idealZoom = std::min(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 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, ¶meters](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) { auto tileData = pair.second.get(); if (parameters.shouldReparsePartialTiles && tileData->isIncomplete()) { if (!tileData->parsePending()) { 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 coordinateToTilePoint(const UnwrappedTileID& tileID, const Point& p) { auto zoomedCoord = TileCoordinate { p, 0 }.zoomTo(tileID.canonical.z); return { int16_t(util::clamp((zoomedCoord.p.x - tileID.canonical.x - tileID.wrap * std::pow(2, tileID.canonical.z)) * util::EXTENT, std::numeric_limits::min(), std::numeric_limits::max())), int16_t(util::clamp((zoomedCoord.p.y - tileID.canonical.y) * util::EXTENT, std::numeric_limits::min(), std::numeric_limits::max())) }; } std::unordered_map> Source::queryRenderedFeatures(const QueryParameters& parameters) const { LineString 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 box = mapbox::geometry::envelope(queryGeometry); std::unordered_map> result; for (const auto& tilePtr : tiles) { const Tile& tile = tilePtr.second; Point tileSpaceBoundsMin = coordinateToTilePoint(tile.id, box.min); Point 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