Files
MapLibreParticleLab/src/mbgl/style/source.cpp
2016-06-10 12:42:14 +02:00

445 lines
16 KiB
C++

#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_source.hpp>
#include <mbgl/tile/geojson_tile_source.hpp>
#include <mbgl/tile/annotation_tile_source.hpp>
#include <mbgl/tile/image_tile_source.hpp>
#include <mbgl/tile/geometry_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) {
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<RasterTileData>(overscaledTileID, parameters.texturePool,
parameters.worker, callback);
data->setTileSource(
std::make_unique<ImageTileSource>(*data, resource, parameters.fileSource));
// Need a std::move here to create a std::unique_ptr<TileData> from
// std::unique_ptr<GeometryTileData>.
return std::move(data);
} else {
auto data = std::make_unique<GeometryTileData>(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<VectorTileSource>(*data, resource, parameters.fileSource));
} else if (type == SourceType::Annotations) {
data->setTileSource(std::make_unique<AnnotationTileSource>(
*data, overscaledTileID, parameters.annotationManager));
} else if (type == SourceType::GeoJSON) {
data->setTileSource(
std::make_unique<GeoJSONTileSource>(*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<TileData> from
// std::unique_ptr<GeometryTileData>.
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<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) {
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<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