Files
MapLibreParticleLab/src/mbgl/geometry/feature_index.cpp
Ansis Brammanis 0ca53ea5a8 [core] only index features within tile boundaries
Previously we relied on tile buffers for querying features who's
rendered representations cross tile boundaries. Now we query multiple
tiles making it unnecessary to index features that are completely
outside a tile's boundaries.
2018-04-26 18:03:17 -04:00

198 lines
8.2 KiB
C++

#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/renderer/render_layer.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
#include <mbgl/text/collision_index.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/math/minmax.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/tile/tile_id.hpp>
#include <mapbox/geometry/envelope.hpp>
#include <cassert>
#include <string>
namespace mbgl {
FeatureIndex::FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_)
: grid(util::EXTENT, util::EXTENT, util::EXTENT / 16) // 16x16 grid -> 32px cell
, tileData(std::move(tileData_)) {
}
void FeatureIndex::insert(const GeometryCollection& geometries,
std::size_t index,
const std::string& sourceLayerName,
const std::string& bucketName) {
for (const auto& ring : geometries) {
auto envelope = mapbox::geometry::envelope(ring);
if (envelope.min.x < util::EXTENT &&
envelope.min.y < util::EXTENT &&
envelope.max.x >= 0 &&
envelope.max.y >= 0) {
grid.insert(IndexedSubfeature(index, sourceLayerName, bucketName, sortIndex++),
{convertPoint<float>(envelope.min), convertPoint<float>(envelope.max)});
}
}
}
void FeatureIndex::query(
std::unordered_map<std::string, std::vector<Feature>>& result,
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
const mat4& posMatrix,
const double tileSize,
const double scale,
const RenderedQueryOptions& queryOptions,
const UnwrappedTileID& tileID,
const std::vector<const RenderLayer*>& layers,
const float additionalQueryPadding) const {
if (!tileData) {
return;
}
// Determine query radius
const float pixelsToTileUnits = util::EXTENT / tileSize / scale;
const int16_t additionalPadding = std::min<int16_t>(util::EXTENT, additionalQueryPadding * pixelsToTileUnits);
// Query the grid index
mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry);
std::vector<IndexedSubfeature> features = grid.query({ convertPoint<float>(box.min - additionalPadding),
convertPoint<float>(box.max + additionalPadding) });
std::sort(features.begin(), features.end(), [](const IndexedSubfeature& a, const IndexedSubfeature& b) {
return a.sortIndex > b.sortIndex;
});
size_t previousSortIndex = std::numeric_limits<size_t>::max();
for (const auto& indexedFeature : features) {
// If this feature is the same as the previous feature, skip it.
if (indexedFeature.sortIndex == previousSortIndex) continue;
previousSortIndex = indexedFeature.sortIndex;
addFeature(result, indexedFeature, queryOptions, tileID.canonical, layers, queryGeometry, transformState, pixelsToTileUnits, posMatrix);
}
}
std::unordered_map<std::string, std::vector<Feature>> FeatureIndex::lookupSymbolFeatures(const std::vector<IndexedSubfeature>& symbolFeatures,
const RenderedQueryOptions& queryOptions,
const std::vector<const RenderLayer*>& layers,
const OverscaledTileID& tileID,
const std::shared_ptr<std::vector<size_t>>& featureSortOrder) const {
std::unordered_map<std::string, std::vector<Feature>> result;
if (!tileData) {
return result;
}
std::vector<IndexedSubfeature> sortedFeatures(symbolFeatures.begin(), symbolFeatures.end());
std::sort(sortedFeatures.begin(), sortedFeatures.end(), [featureSortOrder](const IndexedSubfeature& a, const IndexedSubfeature& b) {
// Same idea as the non-symbol sort order, but symbol features may have changed their sort order
// since their corresponding IndexedSubfeature was added to the CollisionIndex
// The 'featureSortOrder' is relatively inefficient for querying but cheap to build on every bucket sort
if (featureSortOrder) {
// queryRenderedSymbols documentation says we'll return features in
// "top-to-bottom" rendering order (aka last-to-first).
// Actually there can be multiple symbol instances per feature, so
// we sort each feature based on the first matching symbol instance.
auto sortedA = std::find(featureSortOrder->begin(), featureSortOrder->end(), a.index);
auto sortedB = std::find(featureSortOrder->begin(), featureSortOrder->end(), b.index);
assert(sortedA != featureSortOrder->end());
assert(sortedB != featureSortOrder->end());
return sortedA > sortedB;
} else {
// Bucket hasn't been re-sorted based on angle, so use same "reverse of appearance in source data"
// logic as non-symboles
return a.sortIndex > b.sortIndex;
}
});
for (const auto& symbolFeature : sortedFeatures) {
mat4 unusedMatrix;
addFeature(result, symbolFeature, queryOptions, tileID.canonical, layers, GeometryCoordinates(), {}, 0, unusedMatrix);
}
return result;
}
void FeatureIndex::addFeature(
std::unordered_map<std::string, std::vector<Feature>>& result,
const IndexedSubfeature& indexedFeature,
const RenderedQueryOptions& options,
const CanonicalTileID& tileID,
const std::vector<const RenderLayer*>& layers,
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
const float pixelsToTileUnits,
const mat4& posMatrix) const {
auto getRenderLayer = [&] (const std::string& layerID) -> const RenderLayer* {
for (const auto& layer : layers) {
if (layer->getID() == layerID) {
return layer;
}
}
return nullptr;
};
// Lazily calculated.
std::unique_ptr<GeometryTileLayer> sourceLayer;
std::unique_ptr<GeometryTileFeature> geometryTileFeature;
for (const std::string& layerID : bucketLayerIDs.at(indexedFeature.bucketName)) {
const RenderLayer* renderLayer = getRenderLayer(layerID);
if (!renderLayer) {
continue;
}
if (!geometryTileFeature) {
sourceLayer = tileData->getLayer(indexedFeature.sourceLayerName);
assert(sourceLayer);
geometryTileFeature = sourceLayer->getFeature(indexedFeature.index);
assert(geometryTileFeature);
}
if (!renderLayer->is<RenderSymbolLayer>() &&
!renderLayer->queryIntersectsFeature(queryGeometry, *geometryTileFeature, tileID.z, transformState, pixelsToTileUnits, posMatrix)) {
continue;
}
if (options.filter && !(*options.filter)(style::expression::EvaluationContext { static_cast<float>(tileID.z), geometryTileFeature.get() })) {
continue;
}
result[layerID].push_back(convertFeature(*geometryTileFeature, tileID));
}
}
optional<GeometryCoordinates> FeatureIndex::translateQueryGeometry(
const GeometryCoordinates& queryGeometry,
const std::array<float, 2>& translate,
const style::TranslateAnchorType anchorType,
const float bearing,
const float pixelsToTileUnits) {
if (translate[0] == 0 && translate[1] == 0) {
return {};
}
GeometryCoordinate translateVec(translate[0] * pixelsToTileUnits, translate[1] * pixelsToTileUnits);
if (anchorType == style::TranslateAnchorType::Viewport) {
translateVec = util::rotate(translateVec, -bearing);
}
GeometryCoordinates translated;
for (const auto& p : queryGeometry) {
translated.push_back(p - translateVec);
}
return translated;
}
void FeatureIndex::setBucketLayerIDs(const std::string& bucketName, const std::vector<std::string>& layerIDs) {
bucketLayerIDs[bucketName] = layerIDs;
}
} // namespace mbgl