[core] account for property functions in query rendered features
This commit is contained in:
committed by
Ivo van Dongen
parent
d752235ec7
commit
7ddca3b611
Submodule mapbox-gl-js updated: 1bc8caa7c6...cfb4525fc9
@@ -10,6 +10,7 @@
|
||||
#include <mbgl/map/query.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/filter_evaluator.hpp>
|
||||
#include <mbgl/tile/geometry_tile.hpp>
|
||||
|
||||
#include <mapbox/geometry/envelope.hpp>
|
||||
|
||||
@@ -53,6 +54,36 @@ static bool topDownSymbols(const IndexedSubfeature& a, const IndexedSubfeature&
|
||||
return a.sortIndex < b.sortIndex;
|
||||
}
|
||||
|
||||
static int16_t getAdditionalQueryRadius(const RenderedQueryOptions& queryOptions,
|
||||
const style::Style& style,
|
||||
const GeometryTile& tile,
|
||||
const float pixelsToTileUnits) {
|
||||
|
||||
// Determine the additional radius needed factoring in property functions
|
||||
float additionalRadius = 0;
|
||||
auto getQueryRadius = [&](const style::Layer& layer) {
|
||||
auto bucket = tile.getBucket(layer);
|
||||
if (bucket) {
|
||||
additionalRadius = std::max(additionalRadius, bucket->getQueryRadius(layer) * pixelsToTileUnits);
|
||||
}
|
||||
};
|
||||
|
||||
if (queryOptions.layerIDs) {
|
||||
for (const auto& layerID : *queryOptions.layerIDs) {
|
||||
style::Layer* layer = style.getLayer(layerID);
|
||||
if (layer) {
|
||||
getQueryRadius(*layer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const style::Layer* layer : style.getLayers()) {
|
||||
getQueryRadius(*layer);
|
||||
}
|
||||
}
|
||||
|
||||
return std::min<int16_t>(util::EXTENT, additionalRadius);
|
||||
}
|
||||
|
||||
void FeatureIndex::query(
|
||||
std::unordered_map<std::string, std::vector<Feature>>& result,
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
@@ -63,14 +94,18 @@ void FeatureIndex::query(
|
||||
const GeometryTileData& geometryTileData,
|
||||
const CanonicalTileID& tileID,
|
||||
const style::Style& style,
|
||||
const CollisionTile* collisionTile) const {
|
||||
|
||||
mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry);
|
||||
const CollisionTile* collisionTile,
|
||||
const GeometryTile& tile) const {
|
||||
|
||||
// Determine query radius
|
||||
const float pixelsToTileUnits = util::EXTENT / tileSize / scale;
|
||||
const int16_t additionalRadius = std::min<int16_t>(util::EXTENT, std::ceil(style.getQueryRadius() * pixelsToTileUnits));
|
||||
const int16_t additionalRadius = getAdditionalQueryRadius(queryOptions, style, tile, pixelsToTileUnits);
|
||||
|
||||
// Query the grid index
|
||||
mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry);
|
||||
std::vector<IndexedSubfeature> features = grid.query({ box.min - additionalRadius, box.max + additionalRadius });
|
||||
|
||||
|
||||
std::sort(features.begin(), features.end(), topDown);
|
||||
size_t previousSortIndex = std::numeric_limits<size_t>::max();
|
||||
for (const auto& indexedFeature : features) {
|
||||
@@ -124,7 +159,7 @@ void FeatureIndex::addFeature(
|
||||
auto styleLayer = style.getLayer(layerID);
|
||||
if (!styleLayer ||
|
||||
(!styleLayer->is<style::SymbolLayer>() &&
|
||||
!styleLayer->baseImpl->queryIntersectsGeometry(queryGeometry, geometryTileFeature->getGeometries(), bearing, pixelsToTileUnits))) {
|
||||
!styleLayer->baseImpl->queryIntersectsFeature(queryGeometry, *geometryTileFeature, tileID.z, bearing, pixelsToTileUnits))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class GeometryTile;
|
||||
class RenderedQueryOptions;
|
||||
|
||||
namespace style {
|
||||
@@ -45,7 +46,8 @@ public:
|
||||
const GeometryTileData&,
|
||||
const CanonicalTileID&,
|
||||
const style::Style&,
|
||||
const CollisionTile*) const;
|
||||
const CollisionTile*,
|
||||
const GeometryTile& tile) const;
|
||||
|
||||
static optional<GeometryCoordinates> translateQueryGeometry(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <mbgl/tile/geometry_tile_data.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -38,6 +40,10 @@ public:
|
||||
|
||||
virtual bool hasData() const = 0;
|
||||
|
||||
virtual float getQueryRadius(const style::Layer&) const {
|
||||
return 0;
|
||||
};
|
||||
|
||||
bool needsUpload() const {
|
||||
return !uploaded;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layers/circle_layer.hpp>
|
||||
#include <mbgl/style/layers/circle_layer_impl.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -97,4 +98,26 @@ void CircleBucket::addFeature(const GeometryTileFeature& feature,
|
||||
}
|
||||
}
|
||||
|
||||
template <class Property>
|
||||
static float get(const CircleLayer& layer, const std::map<std::string, CircleProgram::PaintPropertyBinders>& paintPropertyBinders) {
|
||||
auto it = paintPropertyBinders.find(layer.getID());
|
||||
if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) {
|
||||
return layer.impl->paint.evaluated.get<Property>().constantOr(Property::defaultValue());
|
||||
} else {
|
||||
return *it->second.statistics<Property>().max();
|
||||
}
|
||||
}
|
||||
|
||||
float CircleBucket::getQueryRadius(const style::Layer& layer) const {
|
||||
if (!layer.is<CircleLayer>()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto circleLayer = layer.as<CircleLayer>();
|
||||
|
||||
float radius = get<CircleRadius>(*circleLayer, paintPropertyBinders);
|
||||
auto translate = circleLayer->impl->paint.evaluated.get<CircleTranslate>();
|
||||
return radius + util::length(translate[0], translate[1]);
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -26,6 +26,8 @@ public:
|
||||
void upload(gl::Context&) override;
|
||||
void render(Painter&, PaintParameters&, const style::Layer&, const RenderTile&) override;
|
||||
|
||||
float getQueryRadius(const style::Layer&) const override;
|
||||
|
||||
gl::VertexVector<CircleLayoutVertex> vertices;
|
||||
gl::IndexVector<gl::Triangles> triangles;
|
||||
gl::SegmentVector<CircleAttributes> segments;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/style/bucket_parameters.hpp>
|
||||
#include <mbgl/style/layers/fill_layer.hpp>
|
||||
#include <mbgl/style/layers/fill_layer_impl.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
|
||||
#include <mapbox/earcut.hpp>
|
||||
|
||||
@@ -131,4 +132,14 @@ bool FillBucket::hasData() const {
|
||||
return !triangleSegments.empty() || !lineSegments.empty();
|
||||
}
|
||||
|
||||
float FillBucket::getQueryRadius(const style::Layer& layer) const {
|
||||
if (!layer.is<FillLayer>()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::array<float, 2>& translate = layer.as<FillLayer>()->impl->paint.evaluated.get<FillTranslate>();
|
||||
return util::length(translate[0], translate[1]);
|
||||
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -27,6 +27,8 @@ public:
|
||||
void upload(gl::Context&) override;
|
||||
void render(Painter&, PaintParameters&, const style::Layer&, const RenderTile&) override;
|
||||
|
||||
float getQueryRadius(const style::Layer&) const override;
|
||||
|
||||
gl::VertexVector<FillLayoutVertex> vertices;
|
||||
gl::IndexVector<gl::Lines> lines;
|
||||
gl::IndexVector<gl::Triangles> triangles;
|
||||
|
||||
@@ -460,4 +460,39 @@ bool LineBucket::hasData() const {
|
||||
return !segments.empty();
|
||||
}
|
||||
|
||||
template <class Property>
|
||||
static float get(const LineLayer& layer, const std::map<std::string, LineProgram::PaintPropertyBinders>& paintPropertyBinders) {
|
||||
auto it = paintPropertyBinders.find(layer.getID());
|
||||
if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) {
|
||||
return layer.impl->paint.evaluated.get<Property>().constantOr(Property::defaultValue());
|
||||
} else {
|
||||
return *it->second.statistics<Property>().max();
|
||||
}
|
||||
}
|
||||
|
||||
float LineBucket::getLineWidth(const style::LineLayer& layer) const {
|
||||
float lineWidth = layer.impl->paint.evaluated.get<LineWidth>();
|
||||
float gapWidth = get<LineGapWidth>(layer, paintPropertyBinders);
|
||||
|
||||
if (gapWidth) {
|
||||
return gapWidth + 2 * lineWidth;
|
||||
} else {
|
||||
return lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
float LineBucket::getQueryRadius(const style::Layer& layer) const {
|
||||
if (!layer.is<LineLayer>()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto lineLayer = layer.as<LineLayer>();
|
||||
auto paint = lineLayer->impl->paint;
|
||||
|
||||
const std::array<float, 2>& translate = paint.evaluated.get<LineTranslate>();
|
||||
float offset = get<LineOffset>(*lineLayer, paintPropertyBinders);
|
||||
return getLineWidth(*lineLayer) / 2.0 + std::abs(offset) + util::length(translate[0], translate[1]);
|
||||
}
|
||||
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace mbgl {
|
||||
|
||||
namespace style {
|
||||
class BucketParameters;
|
||||
class LineLayer;
|
||||
} // namespace style
|
||||
|
||||
class LineBucket : public Bucket {
|
||||
@@ -29,6 +30,8 @@ public:
|
||||
void upload(gl::Context&) override;
|
||||
void render(Painter&, PaintParameters&, const style::Layer&, const RenderTile&) override;
|
||||
|
||||
float getQueryRadius(const style::Layer&) const override;
|
||||
|
||||
style::LineLayoutProperties::PossiblyEvaluated layout;
|
||||
|
||||
gl::VertexVector<LineLayoutVertex> vertices;
|
||||
@@ -59,6 +62,8 @@ private:
|
||||
std::ptrdiff_t e3;
|
||||
|
||||
const uint32_t overscaling;
|
||||
|
||||
float getLineWidth(const style::LineLayer& layer) const;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -69,10 +69,10 @@ public:
|
||||
// Checks whether this layer can be rendered.
|
||||
bool needsRendering(float zoom) const;
|
||||
|
||||
virtual float getQueryRadius() const { return 0; }
|
||||
virtual bool queryIntersectsGeometry(
|
||||
virtual bool queryIntersectsFeature(
|
||||
const GeometryCoordinates&,
|
||||
const GeometryCollection&,
|
||||
const GeometryTileFeature&,
|
||||
const float,
|
||||
const float,
|
||||
const float) const { return false; };
|
||||
|
||||
|
||||
@@ -29,25 +29,24 @@ std::unique_ptr<Bucket> CircleLayer::Impl::createBucket(const BucketParameters&
|
||||
return std::make_unique<CircleBucket>(parameters, layers);
|
||||
}
|
||||
|
||||
float CircleLayer::Impl::getQueryRadius() const {
|
||||
const std::array<float, 2>& translate = paint.evaluated.get<CircleTranslate>();
|
||||
return paint.evaluated.get<CircleRadius>().constantOr(CircleRadius::defaultValue())
|
||||
+ util::length(translate[0], translate[1]);
|
||||
}
|
||||
|
||||
bool CircleLayer::Impl::queryIntersectsGeometry(
|
||||
bool CircleLayer::Impl::queryIntersectsFeature(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const GeometryTileFeature& feature,
|
||||
const float zoom,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const {
|
||||
|
||||
// Translate query geometry
|
||||
auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry(
|
||||
queryGeometry, paint.evaluated.get<CircleTranslate>(), paint.evaluated.get<CircleTranslateAnchor>(), bearing, pixelsToTileUnits);
|
||||
|
||||
auto circleRadius = paint.evaluated.get<CircleRadius>().constantOr(CircleRadius::defaultValue()) * pixelsToTileUnits;
|
||||
// Evaluate function
|
||||
auto circleRadius = paint.evaluated.get<CircleRadius>()
|
||||
.evaluate(feature, zoom, CircleRadius::defaultValue())
|
||||
* pixelsToTileUnits;
|
||||
|
||||
return util::polygonIntersectsBufferedMultiPoint(
|
||||
translatedQueryGeometry.value_or(queryGeometry), geometry, circleRadius);
|
||||
// Test intersection
|
||||
return util::polygonIntersectsBufferedMultiPoint(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries(), circleRadius);
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -18,12 +18,12 @@ public:
|
||||
|
||||
std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const Layer*>&) const override;
|
||||
|
||||
float getQueryRadius() const override;
|
||||
bool queryIntersectsGeometry(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const override;
|
||||
bool queryIntersectsFeature(
|
||||
const GeometryCoordinates&,
|
||||
const GeometryTileFeature&,
|
||||
const float,
|
||||
const float,
|
||||
const float) const override;
|
||||
|
||||
CirclePaintProperties paint;
|
||||
};
|
||||
|
||||
@@ -39,21 +39,17 @@ std::unique_ptr<Bucket> FillLayer::Impl::createBucket(const BucketParameters& pa
|
||||
return std::make_unique<FillBucket>(parameters, layers);
|
||||
}
|
||||
|
||||
float FillLayer::Impl::getQueryRadius() const {
|
||||
const std::array<float, 2>& translate = paint.evaluated.get<FillTranslate>();
|
||||
return util::length(translate[0], translate[1]);
|
||||
}
|
||||
|
||||
bool FillLayer::Impl::queryIntersectsGeometry(
|
||||
bool FillLayer::Impl::queryIntersectsFeature(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const GeometryTileFeature& feature,
|
||||
const float,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const {
|
||||
|
||||
auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry(
|
||||
queryGeometry, paint.evaluated.get<FillTranslate>(), paint.evaluated.get<FillTranslateAnchor>(), bearing, pixelsToTileUnits);
|
||||
|
||||
return util::polygonIntersectsMultiPolygon(translatedQueryGeometry.value_or(queryGeometry), geometry);
|
||||
return util::polygonIntersectsMultiPolygon(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries());
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -18,12 +18,12 @@ public:
|
||||
|
||||
std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const Layer*>&) const override;
|
||||
|
||||
float getQueryRadius() const override;
|
||||
bool queryIntersectsGeometry(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const override;
|
||||
bool queryIntersectsFeature(
|
||||
const GeometryCoordinates&,
|
||||
const GeometryTileFeature&,
|
||||
const float,
|
||||
const float,
|
||||
const float) const override;
|
||||
|
||||
FillPaintProperties paint;
|
||||
};
|
||||
|
||||
@@ -32,9 +32,10 @@ std::unique_ptr<Bucket> LineLayer::Impl::createBucket(const BucketParameters& pa
|
||||
return std::make_unique<LineBucket>(parameters, layers, layout);
|
||||
}
|
||||
|
||||
float LineLayer::Impl::getLineWidth() const {
|
||||
float LineLayer::Impl::getLineWidth(const GeometryTileFeature& feature, const float zoom) const {
|
||||
float lineWidth = paint.evaluated.get<LineWidth>();
|
||||
float gapWidth = paint.evaluated.get<LineGapWidth>().constantOr(0);
|
||||
float gapWidth = paint.evaluated.get<LineGapWidth>()
|
||||
.evaluate(feature, zoom, LineGapWidth::defaultValue());
|
||||
if (gapWidth) {
|
||||
return gapWidth + 2 * lineWidth;
|
||||
} else {
|
||||
@@ -72,29 +73,29 @@ optional<GeometryCollection> offsetLine(const GeometryCollection& rings, const d
|
||||
return newRings;
|
||||
}
|
||||
|
||||
float LineLayer::Impl::getQueryRadius() const {
|
||||
const std::array<float, 2>& translate = paint.evaluated.get<LineTranslate>();
|
||||
auto offset = paint.evaluated.get<LineOffset>().constantOr(LineOffset::defaultValue());
|
||||
return getLineWidth() / 2.0 + std::abs(offset) + util::length(translate[0], translate[1]);
|
||||
}
|
||||
|
||||
bool LineLayer::Impl::queryIntersectsGeometry(
|
||||
bool LineLayer::Impl::queryIntersectsFeature(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const GeometryTileFeature& feature,
|
||||
const float zoom,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const {
|
||||
|
||||
const float halfWidth = getLineWidth() / 2.0 * pixelsToTileUnits;
|
||||
|
||||
// Translate query geometry
|
||||
auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry(
|
||||
queryGeometry, paint.evaluated.get<LineTranslate>(), paint.evaluated.get<LineTranslateAnchor>(), bearing, pixelsToTileUnits);
|
||||
|
||||
auto offset = paint.evaluated.get<LineOffset>().constantOr(LineOffset::defaultValue());
|
||||
auto offsetGeometry = offsetLine(geometry, offset * pixelsToTileUnits);
|
||||
|
||||
// Evaluate function
|
||||
auto offset = paint.evaluated.get<LineOffset>().evaluate(feature, zoom, LineOffset::defaultValue()) * pixelsToTileUnits;
|
||||
|
||||
// Apply offset to geometry
|
||||
auto offsetGeometry = offsetLine(feature.getGeometries(), offset);
|
||||
|
||||
// Test intersection
|
||||
const float halfWidth = getLineWidth(feature, zoom) / 2.0 * pixelsToTileUnits;
|
||||
return util::polygonIntersectsBufferedMultiLine(
|
||||
translatedQueryGeometry.value_or(queryGeometry),
|
||||
offsetGeometry.value_or(geometry),
|
||||
offsetGeometry.value_or(feature.getGeometries()),
|
||||
halfWidth);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ public:
|
||||
|
||||
std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const Layer*>&) const override;
|
||||
|
||||
float getQueryRadius() const override;
|
||||
bool queryIntersectsGeometry(
|
||||
const GeometryCoordinates& queryGeometry,
|
||||
const GeometryCollection& geometry,
|
||||
const float bearing,
|
||||
const float pixelsToTileUnits) const override;
|
||||
bool queryIntersectsFeature(
|
||||
const GeometryCoordinates&,
|
||||
const GeometryTileFeature&,
|
||||
const float,
|
||||
const float,
|
||||
const float) const override;
|
||||
|
||||
LineLayoutProperties layout;
|
||||
LinePaintProperties paint;
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
float dashLineWidth = 1;
|
||||
|
||||
private:
|
||||
float getLineWidth() const;
|
||||
float getLineWidth(const GeometryTileFeature&, const float) const;
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class GeometryTileFeature;
|
||||
|
||||
namespace style {
|
||||
|
||||
template <class T>
|
||||
@@ -43,6 +41,19 @@ public:
|
||||
auto match(Ts&&... ts) const {
|
||||
return value.match(std::forward<Ts>(ts)...);
|
||||
}
|
||||
|
||||
template <class Feature>
|
||||
T evaluate(const Feature& feature, float zoom, T defaultValue) const {
|
||||
return this->match(
|
||||
[&] (const T& constant) { return constant; },
|
||||
[&] (const SourceFunction<T>& function) {
|
||||
return function.evaluate(feature, defaultValue);
|
||||
},
|
||||
[&] (const CompositeFunction<T>& function) {
|
||||
return function.evaluate(zoom, feature, defaultValue);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -541,18 +541,6 @@ std::vector<Feature> Style::queryRenderedFeatures(const ScreenLineString& geomet
|
||||
return result;
|
||||
}
|
||||
|
||||
float Style::getQueryRadius() const {
|
||||
float additionalRadius = 0;
|
||||
for (auto& layer : layers) {
|
||||
if (!layer->baseImpl->needsRendering(zoomHistory.lastZoom)) {
|
||||
continue;
|
||||
}
|
||||
additionalRadius = util::max(additionalRadius, layer->baseImpl->getQueryRadius());
|
||||
}
|
||||
return additionalRadius;
|
||||
}
|
||||
|
||||
|
||||
void Style::setSourceTileCacheSize(size_t size) {
|
||||
for (const auto& source : sources) {
|
||||
source->baseImpl->setCacheSize(size);
|
||||
|
||||
@@ -101,8 +101,6 @@ public:
|
||||
const TransformState& transformState,
|
||||
const RenderedQueryOptions& options) const;
|
||||
|
||||
float getQueryRadius() const;
|
||||
|
||||
void setSourceTileCacheSize(size_t);
|
||||
void onLowMemory();
|
||||
|
||||
|
||||
@@ -186,7 +186,8 @@ void GeometryTile::queryRenderedFeatures(
|
||||
*data,
|
||||
id.canonical,
|
||||
style,
|
||||
collisionTile.get());
|
||||
collisionTile.get(),
|
||||
*this);
|
||||
}
|
||||
|
||||
void GeometryTile::querySourceFeatures(
|
||||
|
||||
Reference in New Issue
Block a user