Add Tile LOD controls (#2958)

Co-authored-by: Bart Louwers <bart@emeel.net>
Co-authored-by: Adrian Cojocaru <adrian.cojocaru92@yahoo.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
alasram
2025-06-04 05:49:41 -07:00
committed by GitHub
parent 2e4541c441
commit b11134a301
43 changed files with 1066 additions and 114 deletions

View File

@@ -26,7 +26,7 @@ static void TileCoverPitchedViewport(benchmark::State& state) {
std::size_t length = 0;
while (state.KeepRunning()) {
auto tiles = util::tileCover(transform.getState(), 8);
auto tiles = util::tileCover({transform.getState()}, 8);
length += tiles.size();
}
benchmark::DoNotOptimize(length);

View File

@@ -161,6 +161,38 @@ public:
void setFreeCameraOptions(const FreeCameraOptions& camera);
FreeCameraOptions getFreeCameraOptions() const;
// Tile LOD controls
//
/// The number of map tile requests can be reduced by using a lower level
/// of details (Lower zoom level) away from the camera.
/// This can improve performance, particularly when the camera pitch is high.
/// The LOD calculation uses a heuristic based on the distance to the camera
/// view point. The heuristic behavior is controlled with 3 parameters:
/// - `TileLodMinRadius` is a radius around the view point in unit of tiles
/// in which the fine grained zoom level tiles are always used
/// - `TileLodScale` is a scale factor for the distance to the camera view
/// point. A value larger than 1 increases the distance to the camera view
/// point in which case the LOD is reduced
/// - `TileLodPitchThreshold` is the pitch angle in radians above which LOD
/// calculation is performed.
/// LOD calculation is always performed if `TileLodPitchThreshold` is zero.
/// LOD calculation is never performed if `TileLodPitchThreshold` is pi.
/// - `TileLodZoomShift` shifts the the Zoom level used for LOD calculation
/// A value of zero (default) does not change the Zoom level
/// A positive value increases the Zoom level and a negative value decreases
/// the Zoom level
/// A negative values typically improves performance but reduces quality.
/// For instance, a value of -1 reduces the zoom level by 1 and this
/// reduces the number of tiles by a factor of 4 for the same camera view.
void setTileLodMinRadius(double radius);
double getTileLodMinRadius() const;
void setTileLodScale(double scale);
double getTileLodScale() const;
void setTileLodPitchThreshold(double threshold);
double getTileLodPitchThreshold() const;
void setTileLodZoomShift(double shift);
double getTileLodZoomShift() const;
ClientOptions getClientOptions() const;
const std::unique_ptr<util::ActionJournal>& getActionJournal();

View File

@@ -1269,6 +1269,38 @@ jni::jboolean NativeMapView::getTileCacheEnabled(JNIEnv&) {
return jni::jboolean(rendererFrontend->getTileCacheEnabled());
}
void NativeMapView::setTileLodMinRadius(JNIEnv&, jni::jdouble radius) {
map->setTileLodMinRadius(radius);
}
jni::jdouble NativeMapView::getTileLodMinRadius(JNIEnv&) {
return jni::jdouble(map->getTileLodMinRadius());
}
void NativeMapView::setTileLodScale(JNIEnv&, jni::jdouble scale) {
map->setTileLodScale(scale);
}
jni::jdouble NativeMapView::getTileLodScale(JNIEnv&) {
return jni::jdouble(map->getTileLodScale());
}
void NativeMapView::setTileLodPitchThreshold(JNIEnv&, jni::jdouble threshold) {
map->setTileLodPitchThreshold(threshold);
}
jni::jdouble NativeMapView::getTileLodPitchThreshold(JNIEnv&) {
return jni::jdouble(map->getTileLodPitchThreshold());
}
void NativeMapView::setTileLodZoomShift(JNIEnv&, jni::jdouble shift) {
map->setTileLodZoomShift(shift);
}
jni::jdouble NativeMapView::getTileLodZoomShift(JNIEnv&) {
return jni::jdouble(map->getTileLodZoomShift());
}
mbgl::Map& NativeMapView::getMap() {
return *map;
}
@@ -1400,6 +1432,14 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"),
METHOD(&NativeMapView::setTileCacheEnabled, "nativeSetTileCacheEnabled"),
METHOD(&NativeMapView::getTileCacheEnabled, "nativeGetTileCacheEnabled"),
METHOD(&NativeMapView::setTileLodMinRadius, "nativeSetTileLodMinRadius"),
METHOD(&NativeMapView::getTileLodMinRadius, "nativeGetTileLodMinRadius"),
METHOD(&NativeMapView::setTileLodScale, "nativeSetTileLodScale"),
METHOD(&NativeMapView::getTileLodScale, "nativeGetTileLodScale"),
METHOD(&NativeMapView::setTileLodPitchThreshold, "nativeSetTileLodPitchThreshold"),
METHOD(&NativeMapView::getTileLodPitchThreshold, "nativeGetTileLodPitchThreshold"),
METHOD(&NativeMapView::setTileLodZoomShift, "nativeSetTileLodZoomShift"),
METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"),
METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"),
METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"),
METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"));

View File

@@ -302,6 +302,22 @@ public:
jni::jboolean getTileCacheEnabled(JNIEnv&);
void setTileLodMinRadius(JNIEnv&, jni::jdouble);
jni::jdouble getTileLodMinRadius(JNIEnv&);
void setTileLodScale(JNIEnv&, jni::jdouble);
jni::jdouble getTileLodScale(JNIEnv&);
void setTileLodPitchThreshold(JNIEnv&, jni::jdouble);
jni::jdouble getTileLodPitchThreshold(JNIEnv&);
void setTileLodZoomShift(JNIEnv&, jni::jdouble);
jni::jdouble getTileLodZoomShift(JNIEnv&);
mbgl::Map& getMap();
void triggerRepaint(JNIEnv&);

View File

@@ -358,6 +358,101 @@ public final class MapLibreMap {
return nativeMapView.getTileCacheEnabled();
}
/**
* Camera based tile level of detail controls
*
* @param radius minimum radius around the view point in unit of tiles in which the fine
* grained zoom level tiles are always used when performing LOD
* radius must be greater than 1 (At least 1 fine detailed tile is present)
* A smaller radius value may improve performance at the cost of quality (tiles away from
* camera use lower Zoom levels)
*/
public void setTileLodMinRadius(@FloatRange(from = 1, fromInclusive = true) double radius) {
nativeMapView.setTileLodMinRadius(radius);
}
/**
* Camera based tile level of detail controls
*
* @return minimum radius around the view point in unit of tiles in which the fine grained
* zoom level tiles are always used when performing LOD
* @see MapLibreMap#setTileLodMinRadius(double)
*/
public double getTileLodMinRadius() {
return nativeMapView.getTileLodMinRadius();
}
/**
* Camera based tile level of detail controls
*
* @param scale factor for the distance to the camera view point
* A value larger than 1 increases the distance to the camera view point reducing LOD
* Larger values may improve performance at the cost of quality (tiles away from camera
* use lower Zoom levels)
*/
public void setTileLodScale(@FloatRange(from = 0, fromInclusive = false) double scale) {
nativeMapView.setTileLodScale(scale);
}
/**
* Camera based tile level of detail controls
*
* @return scale factor for the distance to the camera view point
* @see MapLibreMap#setTileLodScale(double)
*/
public double getTileLodScale() {
return nativeMapView.getTileLodScale();
}
/**
* Camera based tile level of detail controls
*
* @param threshold pitch angle in radians above which LOD calculation is performed
* A smaller radius value may improve performance at the cost of quality
*/
public void setTileLodPitchThreshold(@FloatRange(from = 0, to = Math.PI) double threshold) {
nativeMapView.setTileLodPitchThreshold(threshold);
}
/**
* Camera based tile level of detail controls
*
* @return pitch angle threshold in radians above which LOD calculation is performed
* @see MapLibreMap#setTileLodPitchThreshold(double)
*/
public double getTileLodPitchThreshold() {
return nativeMapView.getTileLodPitchThreshold();
}
/**
* Camera based tile level of detail controls
*
* @param shift shift applied to the Zoom level during LOD calculation
* A negative value shifts the Zoom level to a coarser level reducing quality but
* improving performance
* A positive value shifts the Zoom level to a finer level increasing details but
* negatively affecting performance
* A value of zero (default) does not apply any shift to the Zoom level
* It is not recommended to change the default value unless performance is critical
* and the loss of quality is acceptable. A value of -1 reduces the number of
* displayed tiles by a factor of 4 on average
* It is recommended to first configure the pixelRatio before adjusting
* TileLodZoomShift. {@link MapLibreMapOptions#pixelRatio(float)}
*/
public void setTileLodZoomShift(double shift) {
nativeMapView.setTileLodZoomShift(shift);
}
/**
* Camera based tile level of detail controls
*
* @return shift applied to the Zoom level during LOD calculation
* @see MapLibreMap#setTileLodZoomShift(double)
*/
public double getTileLodZoomShift() {
return nativeMapView.getTileLodZoomShift();
}
//
// MinZoom
//

View File

@@ -244,6 +244,22 @@ interface NativeMap {
boolean getTileCacheEnabled();
void setTileLodMinRadius(double radius);
double getTileLodMinRadius();
void setTileLodScale(double scale);
double getTileLodScale();
void setTileLodPitchThreshold(double threshold);
double getTileLodPitchThreshold();
void setTileLodZoomShift(double shift);
double getTileLodZoomShift();
void setGestureInProgress(boolean inProgress);
float getPixelRatio();

View File

@@ -855,6 +855,70 @@ final class NativeMapView implements NativeMap {
}
return nativeGetTileCacheEnabled();
}
@Override
public void setTileLodMinRadius(double radius) {
if (checkState("setTileLodMinRadius")) {
return;
}
nativeSetTileLodMinRadius(radius);
}
@Override
public double getTileLodMinRadius() {
if (checkState("getTileLodMinRadius")) {
return 0;
}
return nativeGetTileLodMinRadius();
}
@Override
public void setTileLodScale(double scale) {
if (checkState("setTileLodScale")) {
return;
}
nativeSetTileLodScale(scale);
}
@Override
public double getTileLodScale() {
if (checkState("getTileLodScale")) {
return 0;
}
return nativeGetTileLodScale();
}
@Override
public void setTileLodPitchThreshold(double threshold) {
if (checkState("setTileLodPitchThreshold")) {
return;
}
nativeSetTileLodPitchThreshold(threshold);
}
@Override
public double getTileLodPitchThreshold() {
if (checkState("getTileLodPitchThreshold")) {
return 0;
}
return nativeGetTileLodPitchThreshold();
}
@Override
public void setTileLodZoomShift(double shift) {
if (checkState("setTileLodZoomShift")) {
return;
}
nativeSetTileLodZoomShift(shift);
}
@Override
public double getTileLodZoomShift() {
if (checkState("getTileLodZoomShift")) {
return 0;
}
return nativeGetTileLodZoomShift();
}
// Runtime style Api
@Override
@@ -1644,6 +1708,30 @@ final class NativeMapView implements NativeMap {
@Keep
private native int nativeGetPrefetchZoomDelta();
@Keep
private native void nativeSetTileLodMinRadius(double radius);
@Keep
private native double nativeGetTileLodMinRadius();
@Keep
private native void nativeSetTileLodScale(double scale);
@Keep
private native double nativeGetTileLodScale();
@Keep
private native void nativeSetTileLodPitchThreshold(double threshold);
@Keep
private native double nativeGetTileLodPitchThreshold();
@Keep
private native void nativeSetTileLodZoomShift(double shift);
@Keep
private native double nativeGetTileLodZoomShift();
@Override
public long getNativePtr() {
return nativePtr;

View File

@@ -9,6 +9,7 @@
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/math/angles.hpp>
#include <mbgl/math/clamp.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/style/expression/dsl.hpp>
#include <mbgl/style/image.hpp>
@@ -68,7 +69,6 @@
using namespace std::numbers;
#ifdef ENABLE_LOCATION_INDICATOR
namespace {
const std::string mbglPuckAssetsPath{MLN_ASSETS_PATH};
@@ -83,7 +83,7 @@ std::array<double, 3> toArray(const mbgl::LatLng &crd) {
return {crd.latitude(), crd.longitude(), 0};
}
} // namespace
#endif
#endif // ENABLE_LOCATION_INDICATOR
class SnapshotObserver final : public mbgl::MapSnapshotterObserver {
public:
@@ -99,6 +99,76 @@ public:
};
namespace {
enum class TileLodMode {
Default, // Default Tile LOD parameters
NoLod, // Disable LOD
Reduced, // Reduce LOD away from camera
Aggressive, // Aggressively reduce LOD away from camera at the detriment of quality
};
constexpr TileLodMode nextTileLodMode(TileLodMode current) {
switch (current) {
case TileLodMode::Default:
return TileLodMode::NoLod;
case TileLodMode::NoLod:
return TileLodMode::Reduced;
case TileLodMode::Reduced:
return TileLodMode::Aggressive;
case TileLodMode::Aggressive:
return TileLodMode::Default;
default:
return TileLodMode::Default;
}
}
void cycleTileLodMode(mbgl::Map &map) {
// TileLodMode::Default parameters
static const auto defaultRadius = map.getTileLodMinRadius();
static const auto defaultScale = map.getTileLodScale();
static const auto defaultTilePitchThreshold = map.getTileLodPitchThreshold();
static TileLodMode mode = TileLodMode::Default;
mode = nextTileLodMode(mode);
switch (mode) {
case TileLodMode::Default:
map.setTileLodMinRadius(defaultRadius);
map.setTileLodScale(defaultScale);
map.setTileLodPitchThreshold(defaultTilePitchThreshold);
mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: default");
break;
case TileLodMode::NoLod:
// When LOD is off we set a maximum PitchThreshold
map.setTileLodPitchThreshold(std::numbers::pi);
mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: disabled");
break;
case TileLodMode::Reduced:
map.setTileLodMinRadius(2);
map.setTileLodScale(1.5);
map.setTileLodPitchThreshold(std::numbers::pi / 4);
mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: reduced");
break;
case TileLodMode::Aggressive:
map.setTileLodMinRadius(1);
map.setTileLodScale(2);
map.setTileLodPitchThreshold(0);
mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: aggressive");
break;
}
map.triggerRepaint();
}
void tileLodZoomShift(mbgl::Map &map, bool positive) {
constexpr auto tileLodZoomShiftStep = 0.25;
auto shift = positive ? tileLodZoomShiftStep : -tileLodZoomShiftStep;
shift = map.getTileLodZoomShift() + shift;
shift = mbgl::util::clamp(shift, -2.5, 2.5);
mbgl::Log::Info(mbgl::Event::OpenGL, "Zoom shift: " + std::to_string(shift));
map.setTileLodZoomShift(shift);
map.triggerRepaint();
}
void addFillExtrusionLayer(mbgl::style::Style &style, bool visible) {
MLN_TRACE_FUNC();
@@ -288,6 +358,9 @@ GLFWView::GLFWView(bool fullscreen_,
printf("- Press `F1` to generate a render test for the current view\n");
printf("\n");
printf("- Press `Tab` to cycle through the map debug options\n");
printf("- Press `F6` to cycle through Tile LOD modes\n");
printf("- Press `F7` to lower the zoom level without changing the camera\n");
printf("- Press `F8` to higher the zoom level without changing the camera\n");
printf("- Press `Esc` to quit\n");
printf("\n");
printf(
@@ -567,6 +640,15 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action,
view->freeCameraDemoStartTime = mbgl::Clock::now();
view->invalidate();
} break;
case GLFW_KEY_F6: {
cycleTileLodMode(*view->map);
} break;
case GLFW_KEY_F7: {
tileLodZoomShift(*view->map, false);
} break;
case GLFW_KEY_F8: {
tileLodZoomShift(*view->map, true);
} break;
}
}

View File

@@ -121,10 +121,19 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
MBXSettingsMiscellaneousShowCustomLocationManager,
MBXSettingsMiscellaneousOrnamentsPlacement,
MBXSettingsMiscellaneousLatLngBoundsWithPadding,
MBXSettingsMiscellaneousCycleTileLOD,
MBXSettingsMiscellaneousPrintLogFile,
MBXSettingsMiscellaneousDeleteLogFile
};
typedef NS_ENUM(NSInteger, MBXTileLodMode) {
MBXTileLodModeNoLod = 0,
MBXTileLodModeDefault,
MBXTileLodModeReduced,
MBXTileLodModeAggressive,
MBXTileLodModeCount
};
// Utility methods
CLLocationCoordinate2D coordinateCentered(CLLocationCoordinate2D origin, CLLocationDegrees bearing, CLLocationDistance distance) {
@@ -245,6 +254,8 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
NSDictionary* _pointFeatures;
NSLock* _loadLock;
MBXTileLodMode _tileLodMode;
}
// MARK: - Setup & Teardown
@@ -474,7 +485,8 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
[NSString stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")],
@"View Route Simulation",
@"Ornaments Placement",
@"Lat Long bounds with padding"
@"Lat Long bounds with padding",
[NSString stringWithFormat:@"Cycle tile LOD mode: %@", [self getTileLodModeName]]
]];
break;
@@ -807,6 +819,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
case MBXSettingsMiscellaneousLatLngBoundsWithPadding:
[self flyToWithLatLngBoundsAndPadding];
break;
case MBXSettingsMiscellaneousCycleTileLOD:
[self cycleTileLodMode];
break;
default:
NSAssert(NO, @"All miscellaneous setting rows should be implemented");
break;
@@ -1992,6 +2007,114 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
}];
}
-(void)cycleTileLodMode
{
// TileLodMode::Default parameters
static const double defaultRadius = self.mapView.tileLodMinRadius;
static const double defaultScale = self.mapView.tileLodScale;
static const double defaultPitchThreshold = self.mapView.tileLodPitchThreshold;
_tileLodMode = static_cast<MBXTileLodMode>((static_cast<int>(_tileLodMode) + 1) % static_cast<int>(MBXTileLodModeCount));
switch (_tileLodMode) {
case MBXTileLodModeDefault:
self.mapView.tileLodMinRadius = defaultRadius;
self.mapView.tileLodScale = defaultScale;
self.mapView.tileLodPitchThreshold = defaultPitchThreshold;
break;
case MBXTileLodModeNoLod:
// When LOD is off we set a maximum PitchThreshold
self.mapView.tileLodPitchThreshold = M_PI;
break;
case MBXTileLodModeReduced:
self.mapView.tileLodMinRadius = 2;
self.mapView.tileLodScale = 1.5;
self.mapView.tileLodPitchThreshold = M_PI / 4;
break;
case MBXTileLodModeAggressive:
self.mapView.tileLodMinRadius = 1;
self.mapView.tileLodScale = 2;
self.mapView.tileLodPitchThreshold = 0;
break;
default:
break;
}
// update UI
static UISlider* zoomSlider = nil;
if (zoomSlider && _tileLodMode == MBXTileLodModeNoLod) {
[zoomSlider removeFromSuperview];
zoomSlider = nil;
}
if (!zoomSlider && _tileLodMode != MBXTileLodModeNoLod) {
const float zoomShiftRange = 5.0f;
zoomSlider = [[UISlider alloc] init];
zoomSlider.minimumValue = -zoomShiftRange / 2.0f;
zoomSlider.maximumValue = zoomShiftRange / 2.0f;
zoomSlider.value = self.mapView.tileLodZoomShift;
zoomSlider.continuous = NO;
[zoomSlider addTarget:self action:@selector(updateTileLodZoom:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:zoomSlider];
zoomSlider.translatesAutoresizingMaskIntoConstraints = NO;
zoomSlider.frame = CGRectMake(0, 0, 100, 100);
NSArray<NSLayoutConstraint*>* layout = @[
[NSLayoutConstraint constraintWithItem:zoomSlider
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0],
[NSLayoutConstraint constraintWithItem:zoomSlider
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0],
[NSLayoutConstraint constraintWithItem:zoomSlider
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0],
[zoomSlider.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]];
[self.view addConstraints:layout];
}
[self.mapView triggerRepaint];
}
-(void)updateTileLodZoom:(id)sender {
UISlider* slider = (UISlider*)sender;
self.mapView.tileLodZoomShift = slider.value;
[self.mapView triggerRepaint];
NSLog(@"Tile LOD zoom shift: %f", self.mapView.tileLodZoomShift);
}
-(NSString*)getTileLodModeName {
switch (_tileLodMode) {
case MBXTileLodModeDefault:
return @"Default";
case MBXTileLodModeNoLod:
return @"Disabled";
case MBXTileLodModeReduced:
return @"Reduced";
case MBXTileLodModeAggressive:
return @"Aggressive";
default:
return @"";
}
}
// MARK: - Random World Tour
- (void)addAnnotations:(NSInteger)numAnnotations aroundCoordinate:(CLLocationCoordinate2D)coordinate radius:(CLLocationDistance)radius {

View File

@@ -463,6 +463,50 @@ MLN_EXPORT
@property (nonatomic, assign) BOOL tileCacheEnabled;
// MARK: Tile LOD controls
/**
Camera based tile level of detail controls
Minimum radius around the view point in unit of tiles in which the fine
grained zoom level tiles are always used when performing LOD
radius must be greater than 1 (At least 1 fine detailed tile is present)
A smaller radius value may improve performance at the cost of quality (tiles away from
camera use lower Zoom levels)
*/
@property (nonatomic, assign) double tileLodMinRadius;
/**
Camera based tile level of detail controls
Factor for the distance to the camera view point
A value larger than 1 increases the distance to the camera view point reducing LOD
Larger values may improve performance at the cost of quality (tiles away from camera
use lower Zoom levels)
*/
@property (nonatomic, assign) double tileLodScale;
/**
Camera based tile level of detail controls
Pitch angle in radians above which LOD calculation is performed
A smaller radius value may improve performance at the cost of quality
*/
@property (nonatomic, assign) double tileLodPitchThreshold;
/**
Camera based tile level of detail controls
Shift applied to the Zoom level during LOD calculation
A negative value shifts the Zoom level to a coarser level reducing quality but improving
performance A positive value shifts the Zoom level to a finer level increasing details but
negatively affecting performance A value of zero (default) does not apply any shift to the Zoom
level It is not recommended to change the default value unless performance is critical and the loss
of quality is acceptable. A value of -1 reduces the number of displayed tiles by a factor of 4 on
average It is recommended to first configure the pixelRatio before adjusting TileLodZoomShift.
*/
@property (nonatomic, assign) double tileLodZoomShift;
// MARK: Displaying the User’s Location
/**

View File

@@ -3231,6 +3231,46 @@ static void *windowScreenContext = &windowScreenContext;
return _rendererFrontend->getTileCacheEnabled();
}
- (void)setTileLodMinRadius:(double)tileLodMinRadius
{
_mbglMap->setTileLodMinRadius(tileLodMinRadius);
}
- (double)tileLodMinRadius
{
return _mbglMap->getTileLodMinRadius();
}
- (void)setTileLodScale:(double)tileLodScale
{
_mbglMap->setTileLodScale(tileLodScale);
}
- (double)tileLodScale
{
return _mbglMap->getTileLodScale();
}
-(void)setTileLodPitchThreshold:(double)tileLodPitchThreshold
{
_mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold);
}
-(double)tileLodPitchThreshold
{
return _mbglMap->getTileLodPitchThreshold();
}
-(void)setTileLodZoomShift:(double)tileLodZoomShift
{
_mbglMap->setTileLodZoomShift(tileLodZoomShift);
}
-(double)tileLodZoomShift
{
return _mbglMap->getTileLodZoomShift();
}
// MARK: - Accessibility -
- (NSString *)accessibilityValue

View File

@@ -202,6 +202,50 @@ MLN_EXPORT IB_DESIGNABLE @interface MLNMapView : NSView<MLNStylable>
*/
@property (nonatomic, assign) BOOL prefetchesTiles;
// MARK: Tile LOD controls
/**
Camera based tile level of detail controls
Minimum radius around the view point in unit of tiles in which the fine
grained zoom level tiles are always used when performing LOD
radius must be greater than 1 (At least 1 fine detailed tile is present)
A smaller radius value may improve performance at the cost of quality (tiles away from
camera use lower Zoom levels)
*/
@property (nonatomic, assign) double tileLodMinRadius;
/**
Camera based tile level of detail controls
Factor for the distance to the camera view point
A value larger than 1 increases the distance to the camera view point reducing LOD
Larger values may improve performance at the cost of quality (tiles away from camera
use lower Zoom levels)
*/
@property (nonatomic, assign) double tileLodScale;
/**
Camera based tile level of detail controls
Pitch angle in radians above which LOD calculation is performed
A smaller radius value may improve performance at the cost of quality
*/
@property (nonatomic, assign) double tileLodPitchThreshold;
/**
Camera based tile level of detail controls
Shift applied to the Zoom level during LOD calculation
A negative value shifts the Zoom level to a coarser level reducing quality but improving
performance A positive value shifts the Zoom level to a finer level increasing details but
negatively affecting performance A value of zero (default) does not apply any shift to the Zoom
level It is not recommended to change the default value unless performance is critical and the loss
of quality is acceptable. A value of -1 reduces the number of displayed tiles by a factor of 4 on
average It is recommended to first configure the pixelRatio before adjusting TileLodZoomShift.
*/
@property (nonatomic, assign) double tileLodZoomShift;
// MARK: Manipulating the Viewpoint
/**

View File

@@ -710,6 +710,46 @@ public:
return _mbglMap->getPrefetchZoomDelta() > 0 ? YES : NO;
}
- (void)setTileLodMinRadius:(double)tileLodMinRadius
{
_mbglMap->setTileLodMinRadius(tileLodMinRadius);
}
- (double)tileLodMinRadius
{
return _mbglMap->getTileLodMinRadius();
}
- (void)setTileLodScale:(double)tileLodScale
{
_mbglMap->setTileLodScale(tileLodScale);
}
- (double)tileLodScale
{
return _mbglMap->getTileLodScale();
}
-(void)setTileLodPitchThreshold:(double)tileLodPitchThreshold
{
_mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold);
}
-(double)tileLodPitchThreshold
{
return _mbglMap->getTileLodPitchThreshold();
}
-(void)setTileLodZoomShift:(double)tileLodZoomShift
{
_mbglMap->setTileLodZoomShift(tileLodZoomShift);
}
-(double)tileLodZoomShift
{
return _mbglMap->getTileLodZoomShift();
}
- (mbgl::Renderer *)renderer {
return _rendererFrontend->getRenderer();
}

View File

@@ -547,6 +547,38 @@ FreeCameraOptions Map::getFreeCameraOptions() const {
return impl->transform.getFreeCameraOptions();
}
void Map::setTileLodMinRadius(double radius) {
impl->tileLodMinRadius = radius;
}
double Map::getTileLodMinRadius() const {
return impl->tileLodMinRadius;
}
void Map::setTileLodScale(double scale) {
impl->tileLodScale = scale;
}
double Map::getTileLodScale() const {
return impl->tileLodScale;
}
void Map::setTileLodPitchThreshold(double threshold) {
impl->tileLodPitchThreshold = threshold;
}
double Map::getTileLodPitchThreshold() const {
return impl->tileLodPitchThreshold;
}
void Map::setTileLodZoomShift(double shift) {
impl->tileLodZoomShift = shift;
}
double Map::getTileLodZoomShift() const {
return impl->tileLodZoomShift;
}
ClientOptions Map::getClientOptions() const {
return impl->fileSource ? impl->fileSource->getClientOptions() : ClientOptions();
}

View File

@@ -130,7 +130,11 @@ void Map::Impl::onUpdate() {
fileSource,
prefetchZoomDelta,
bool(stillImageRequest),
crossSourceCollisions};
crossSourceCollisions,
tileLodMinRadius,
tileLodScale,
tileLodPitchThreshold,
tileLodZoomShift};
rendererFrontend.update(std::make_shared<UpdateParameters>(std::move(params)));
}

View File

@@ -14,6 +14,8 @@
#include <mbgl/util/size.hpp>
#include <mbgl/tile/tile_operation.hpp>
#include <numbers>
namespace mbgl {
class FileSource;
@@ -104,6 +106,11 @@ public:
bool loading = false;
bool rendererFullyLoaded;
std::unique_ptr<StillImageRequest> stillImageRequest;
double tileLodMinRadius = 3;
double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0;
};
// Forward declaration of this method is required for the MapProjection class

View File

@@ -113,11 +113,16 @@ static constexpr std::string_view BackgroundPatternShaderName = "BackgroundPatte
void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders,
gfx::Context& context,
const TransformState& state,
const std::shared_ptr<UpdateParameters>&,
const std::shared_ptr<UpdateParameters>& updateParameters,
[[maybe_unused]] const RenderTree& renderTree,
[[maybe_unused]] UniqueChangeRequestVec& changes) {
assert(updateParameters);
const auto zoom = state.getIntegerZoom();
const auto tileCover = util::tileCover(state, zoom);
const auto tileCover = util::tileCover({state,
updateParameters->tileLodMinRadius,
updateParameters->tileLodScale,
updateParameters->tileLodPitchThreshold},
zoom);
// renderTiles is always empty, we use tileCover instead
if (tileCover.empty()) {

View File

@@ -55,7 +55,10 @@ PaintParameters::PaintParameters(gfx::Context& context_,
RenderStaticData& staticData_,
LineAtlas& lineAtlas_,
PatternAtlas& patternAtlas_,
uint64_t frameCount_)
uint64_t frameCount_,
double tileLodMinRadius_,
double tileLodScale_,
double tileLodPitchThreshold_)
: context(context_),
backend(backend_),
encoder(context.createCommandEncoder()),
@@ -70,7 +73,10 @@ PaintParameters::PaintParameters(gfx::Context& context_,
timePoint(timePoint_),
pixelRatio(pixelRatio_),
shaders(*staticData_.shaders),
frameCount(frameCount_) {
frameCount(frameCount_),
tileLodMinRadius(tileLodMinRadius_),
tileLodScale(tileLodScale_),
tileLodPitchThreshold(tileLodPitchThreshold_) {
pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}};
if (state.getViewportMode() == ViewportMode::FlippedY) {

View File

@@ -57,7 +57,10 @@ public:
RenderStaticData&,
LineAtlas&,
PatternAtlas&,
uint64_t frameCount);
uint64_t frameCount,
double tileLodMinRadius,
double tileLodScale,
double tileLodPitchThreshold);
~PaintParameters();
gfx::Context& context;
@@ -130,6 +133,10 @@ public:
static constexpr float depthEpsilon = 1.0f / (1 << 12);
#endif
static constexpr int maxStencilValue = 255;
double tileLodMinRadius;
double tileLodScale;
double tileLodPitchThreshold;
};
} // namespace mbgl

View File

@@ -194,6 +194,10 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
.glyphManager = glyphManager,
.prefetchZoomDelta = updateParameters->prefetchZoomDelta,
.threadPool = threadPool,
.tileLodMinRadius = updateParameters->tileLodMinRadius,
.tileLodScale = updateParameters->tileLodScale,
.tileLodPitchThreshold = updateParameters->tileLodPitchThreshold,
.tileLodZoomShift = updateParameters->tileLodZoomShift,
.dynamicTextureAtlas = dynamicTextureAtlas};
glyphManager->setURL(updateParameters->glyphURL);

View File

@@ -13,6 +13,7 @@
#include <mbgl/renderer/renderer_observer.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_tree.hpp>
#include <mbgl/renderer/update_parameters.hpp>
#include <mbgl/shaders/program_parameters.hpp>
#include <mbgl/util/convert.hpp>
#include <mbgl/util/string.hpp>
@@ -84,12 +85,13 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) {
observer = observer_ ? observer_ : &nullObserver();
}
void Renderer::Impl::render(const RenderTree& renderTree,
[[maybe_unused]] const std::shared_ptr<UpdateParameters>& updateParameters) {
void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<UpdateParameters>& updateParameters) {
MLN_TRACE_FUNC();
auto& context = backend.getContext();
context.setObserver(this);
assert(updateParameters);
#if MLN_RENDER_BACKEND_METAL
if constexpr (EnableMetalCapture) {
const auto& mtlBackend = static_cast<mtl::RendererBackend&>(backend);
@@ -189,7 +191,10 @@ void Renderer::Impl::render(const RenderTree& renderTree,
*staticData,
renderTree.getLineAtlas(),
renderTree.getPatternAtlas(),
frameCount};
frameCount,
updateParameters->tileLodMinRadius,
updateParameters->tileLodScale,
updateParameters->tileLodPitchThreshold};
parameters.symbolFadeChange = renderTreeParameters.symbolFadeChange;
parameters.opaquePassCutoff = renderTreeParameters.opaquePassCutOff;

View File

@@ -133,7 +133,9 @@ void RenderImageSource::update(Immutable<style::Source::Impl> baseImpl_,
bool hasVisibleTile = false;
// Add additional wrapped tile ids if neccessary
auto idealTiles = util::tileCover(transformState, static_cast<uint8_t>(transformState.getZoom()));
auto idealTiles = util::tileCover(
{transformState, parameters.tileLodMinRadius, parameters.tileLodScale, parameters.tileLodPitchThreshold},
static_cast<uint8_t>(transformState.getZoom()));
for (auto tile : idealTiles) {
if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) {
tileIds.emplace_back(tile.wrap, tileCover[0].canonical);

View File

@@ -4,6 +4,7 @@
#include <mbgl/actor/scheduler.hpp>
#include <memory>
#include <numbers>
#include <mapbox/std/weak.hpp>
@@ -32,6 +33,10 @@ public:
std::shared_ptr<GlyphManager> glyphManager;
const uint8_t prefetchZoomDelta;
TaggedScheduler threadPool;
double tileLodMinRadius = 3;
double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0;
gfx::DynamicTextureAtlasPtr dynamicTextureAtlas;
};

View File

@@ -89,9 +89,14 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
handleWrapJump(static_cast<float>(parameters.transformState.getLatLng().longitude()));
// Optionally shift the zoom level
double zoom = util::clamp<double>(parameters.transformState.getZoom() + parameters.tileLodZoomShift,
parameters.transformState.getMinZoom(),
parameters.transformState.getMaxZoom());
const auto type = sourceImpl.type;
// Determine the overzooming/underzooming amounts and required tiles.
int32_t overscaledZoom = util::coveringZoomLevel(parameters.transformState.getZoom(), type, tileSize);
int32_t overscaledZoom = util::coveringZoomLevel(zoom, type, tileSize);
int32_t tileZoom = overscaledZoom;
int32_t panZoom = zoomRange.max;
@@ -103,6 +108,11 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
std::vector<OverscaledTileID> idealTiles;
std::vector<OverscaledTileID> panTiles;
util::TileCoverParameters tileCoverParameters = {parameters.transformState,
parameters.tileLodMinRadius,
parameters.tileLodScale,
parameters.tileLodPitchThreshold};
if (overscaledZoom >= zoomRange.min) {
int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom);
@@ -123,11 +133,11 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
}
if (panZoom < idealZoom) {
panTiles = util::tileCover(parameters.transformState, panZoom);
panTiles = util::tileCover(tileCoverParameters, panZoom);
}
}
idealTiles = util::tileCover(parameters.transformState, idealZoom, tileZoom);
idealTiles = util::tileCover(tileCoverParameters, idealZoom, tileZoom);
if (parameters.mode == MapMode::Tile && type != SourceType::Raster && type != SourceType::RasterDEM &&
idealTiles.size() > 1) {
mbgl::Log::Warning(mbgl::Event::General,

View File

@@ -9,6 +9,7 @@
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/immutable.hpp>
#include <numbers>
#include <vector>
#include <mapbox/std/weak.hpp>
@@ -44,6 +45,11 @@ public:
const bool stillImageRequest;
const bool crossSourceCollisions;
double tileLodMinRadius = 3;
double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0;
};
} // namespace mbgl

View File

@@ -1,4 +1,3 @@
#include <mbgl/map/transform_state.hpp>
#include <mbgl/math/log2.hpp>
#include <mbgl/util/bounding_volumes.hpp>
#include <mbgl/util/constants.hpp>
@@ -9,7 +8,6 @@
#include <functional>
#include <list>
#include <numbers>
using namespace std::numbers;
@@ -157,7 +155,7 @@ int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) no
}
}
std::vector<OverscaledTileID> tileCover(const TransformState& state,
std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
uint8_t z,
const std::optional<uint8_t>& overscaledZ) {
struct Node {
@@ -173,23 +171,26 @@ std::vector<OverscaledTileID> tileCover(const TransformState& state,
double sqrDist;
};
const auto& transform = state.transformState;
const double numTiles = std::pow(2.0, z);
const double worldSize = Projection::worldSize(state.getScale());
const uint8_t minZoom = state.getPitch() <= (60.0 / 180.0) * pi ? z : 0;
const double worldSize = Projection::worldSize(transform.getScale());
const uint8_t minZoom = transform.getPitch() <= state.tileLodPitchThreshold ? z : 0;
const uint8_t maxZoom = z;
const uint8_t overscaledZoom = overscaledZ.value_or(z);
const bool flippedY = state.getViewportMode() == ViewportMode::FlippedY;
const uint8_t overscaledZoom = std::max(overscaledZ.value_or(z), z);
const bool flippedY = transform.getViewportMode() == ViewportMode::FlippedY;
const auto centerPoint =
TileCoordinate::fromScreenCoordinate(state, z, {state.getSize().width / 2.0, state.getSize().height / 2.0}).p;
const auto centerPoint = TileCoordinate::fromScreenCoordinate(
transform, z, {transform.getSize().width / 2.0, transform.getSize().height / 2.0})
.p;
const vec3 centerCoord = {{centerPoint.x, centerPoint.y, 0.0}};
const Frustum frustum = Frustum::fromInvProjMatrix(state.getInvProjectionMatrix(), worldSize, z, flippedY);
const Frustum frustum = Frustum::fromInvProjMatrix(transform.getInvProjectionMatrix(), worldSize, z, flippedY);
// There should always be a certain number of maximum zoom level tiles
// surrounding the center location
const double radiusOfMaxLvlLodInTiles = 3;
assert(state.tileLodMinRadius >= 1);
const double radiusOfMaxLvlLodInTiles = std::max(1.0, state.tileLodMinRadius);
const auto newRootTile = [&](int16_t wrap) -> Node {
return {AABB({{wrap * numTiles, 0.0, 0.0}}, {{(wrap + 1) * numTiles, numTiles, 0.0}}),
@@ -235,12 +236,14 @@ std::vector<OverscaledTileID> tileCover(const TransformState& state,
// there's always a certain number of maxLevel tiles next to the map
// center. Using the fact that a parent node in quadtree is twice the
// size of its children (per dimension) we can define distance
// thresholds for each relative level: f(k) = offset + 2 + 4 + 8 + 16 +
// ... + 2^k. This is the same as "offset+2^(k+1)-2"
// thresholds for each relative level:
// f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k
// This is the same as:
// f(k) = offset + 2^(k+1)-2
const double distToSplit = radiusOfMaxLvlLodInTiles + (1 << (maxZoom - node.zoom)) - 2;
// Have we reached the target depth or is the tile too far away to be any split further?
if (node.zoom == maxZoom || (*longestDim > distToSplit && node.zoom >= minZoom)) {
if (node.zoom == maxZoom || (*longestDim * state.tileLodScale > distToSplit && node.zoom >= minZoom)) {
// Perform precise intersection test between the frustum and aabb.
// This will cull < 1% false positives missed by the original test
if (node.fullyVisible || frustum.intersectsPrecise(node.aabb, true) != IntersectionResult::Separate) {

View File

@@ -1,16 +1,18 @@
#pragma once
#include <mbgl/map/transform_state.hpp>
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/util/range.hpp>
#include <vector>
#include <memory>
#include <numbers>
#include <optional>
namespace mbgl {
class TransformState;
class LatLngBounds;
namespace util {
@@ -31,9 +33,16 @@ private:
std::unique_ptr<Impl> impl;
};
struct TileCoverParameters {
TransformState transformState;
double tileLodMinRadius = 3;
double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
};
int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize) noexcept;
std::vector<OverscaledTileID> tileCover(const TransformState&,
std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
uint8_t z,
const std::optional<uint8_t>& overscaledZ = std::nullopt);
std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, uint8_t z);

View File

@@ -84,6 +84,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/tile/tile_cache.test.cpp
${PROJECT_SOURCE_DIR}/test/tile/tile_coordinate.test.cpp
${PROJECT_SOURCE_DIR}/test/tile/tile_id.test.cpp
${PROJECT_SOURCE_DIR}/test/tile/tile_lod.test.cpp
${PROJECT_SOURCE_DIR}/test/tile/vector_tile.test.cpp
${PROJECT_SOURCE_DIR}/test/util/action_journal.test.cpp
${PROJECT_SOURCE_DIR}/test/util/async_task.test.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -74,5 +74,12 @@ void checkImage(const std::string& base,
double imageThreshold = 0,
double pixelThreshold = 0);
void checkImages(const std::vector<std::string>& possibleExpected,
const PremultipliedImage& actual,
double imageThreshold = 0,
double pixelThreshold = 0);
double getImageDiff(const std::string& base, const PremultipliedImage& actual, double pixelThreshold = 0);
} // namespace test
} // namespace mbgl

View File

@@ -34,17 +34,17 @@ public:
VectorTileTest()
: threadPool(Scheduler::GetBackground(), uniqueID),
tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
threadPool,
dynamicTextureAtlas},
tileParameters{.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = MapMode::Continuous,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = threadPool,
.dynamicTextureAtlas = dynamicTextureAtlas},
style{fileSource, 1, threadPool} {}
~VectorTileTest() {

View File

@@ -9,12 +9,16 @@
#pragma warning(disable : 4244)
#endif
#include <gmock/gmock-matchers.h>
#include <gmock/gmock-more-matchers.h>
#include <mapbox/pixelmatch.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
using namespace ::testing;
namespace mbgl {
namespace test {
@@ -22,10 +26,27 @@ void checkImage(const std::string& base,
const PremultipliedImage& actual,
double imageThreshold,
double pixelThreshold) {
EXPECT_LE(getImageDiff(base, actual, pixelThreshold), imageThreshold);
}
void checkImages(const std::vector<std::string>& possibleExpected,
const PremultipliedImage& actual,
double imageThreshold,
double pixelThreshold) {
std::vector<double> diffs(0.0, possibleExpected.size());
for (const auto& expected : possibleExpected) {
diffs.push_back(getImageDiff(expected, actual, pixelThreshold));
}
EXPECT_THAT(diffs, Contains(Le(imageThreshold)));
}
double getImageDiff(const std::string& base, const PremultipliedImage& actual, double pixelThreshold) {
#if !TEST_READ_ONLY
if (getenv("UPDATE")) {
util::write_file(base + "/expected.png", encodePNG(actual));
return;
return 0.0;
}
#endif
@@ -44,7 +65,9 @@ void checkImage(const std::string& base,
util::write_file(base + "/actual.png", encodePNG(actual));
#endif
ASSERT_EQ(expected.size, actual.size);
if (expected.size != actual.size) {
return 1.0;
}
uint64_t pixels = mapbox::pixelmatch(actual.data.get(),
expected.data.get(),
@@ -53,11 +76,11 @@ void checkImage(const std::string& base,
diff.data.get(),
pixelThreshold);
EXPECT_LE(static_cast<double>(pixels) / (expected.size.width * expected.size.height), imageThreshold);
#if !TEST_READ_ONLY
util::write_file(base + "/diff.png", encodePNG(diff));
#endif
return static_cast<double>(pixels) / (expected.size.width * expected.size.height);
}
} // namespace test

View File

@@ -70,17 +70,17 @@ public:
Style style;
TileParameters tileParameters(MapMode mapMode = MapMode::Continuous) {
return {1.0,
MapDebugOptions(),
transformState,
fileSource,
mapMode,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
threadPool,
dynamicTextureAtlas};
return {.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = mapMode,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = threadPool,
.dynamicTextureAtlas = dynamicTextureAtlas};
};
SourceTest()

View File

@@ -36,17 +36,17 @@ public:
style::Style style;
CustomTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID},
dynamicTextureAtlas},
: tileParameters{.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = MapMode::Continuous,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = {Scheduler::GetBackground(), uniqueID},
.dynamicTextureAtlas = dynamicTextureAtlas},
style{fileSource, 1, tileParameters.threadPool} {}
};

View File

@@ -37,17 +37,17 @@ public:
style::Style style;
GeoJSONTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID},
dynamicTextureAtlas},
: tileParameters{.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = MapMode::Continuous,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = {Scheduler::GetBackground(), uniqueID},
.dynamicTextureAtlas = dynamicTextureAtlas},
style{fileSource, 1, tileParameters.threadPool} {}
};

View File

@@ -31,17 +31,17 @@ public:
style::Style style;
RasterDEMTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID},
dynamicTextureAtlas},
: tileParameters{.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = MapMode::Continuous,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = {Scheduler::GetBackground(), uniqueID},
.dynamicTextureAtlas = dynamicTextureAtlas},
style{fileSource, 1, tileParameters.threadPool} {}
};

View File

@@ -31,17 +31,17 @@ public:
style::Style style;
RasterTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID},
dynamicTextureAtlas},
: tileParameters{.pixelRatio = 1.0,
.debugOptions = MapDebugOptions(),
.transformState = transformState,
.fileSource = fileSource,
.mode = MapMode::Continuous,
.annotationManager = annotationManager.makeWeakPtr(),
.imageManager = imageManager,
.glyphManager = glyphManager,
.prefetchZoomDelta = 0,
.threadPool = {Scheduler::GetBackground(), uniqueID},
.dynamicTextureAtlas = dynamicTextureAtlas},
style{fileSource, 1, tileParameters.threadPool} {}
};

156
test/tile/tile_lod.test.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include <gmock/gmock.h>
#include <mbgl/map/map.hpp>
#include <mbgl/map/map_observer.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp>
#include <mbgl/test/util.hpp>
#include <filesystem>
using namespace mbgl;
using namespace mbgl::util;
using namespace ::testing;
class TileLODTest {
public:
util::RunLoop loop;
HeadlessFrontend frontend;
Map map;
uint32_t tileCount = 0;
public:
TileLODTest(const CameraOptions& cameraOptions = CameraOptions())
: frontend(1.0f),
map(frontend,
MapObserver::nullObserver(),
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()),
ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")) {
map.jumpTo(cameraOptions);
map.setDebug(MapDebugOptions::TileBorders);
map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
style::CustomGeometrySource::Options options;
options.fetchTileFunction = [&](const mbgl::CanonicalTileID& tileID) {
auto source = static_cast<style::CustomGeometrySource*>(map.getStyle().getSource("custom"));
if (!source) {
return;
}
const LatLngBounds bounds(tileID);
const LatLng& center = bounds.center();
mapbox::geojson::feature feature;
feature.geometry = mapbox::geometry::geometry<double>(Point<double>{center.longitude(), center.latitude()});
FeatureCollection features;
features.emplace_back(feature);
source->setTileData(tileID, features);
++tileCount;
};
map.getStyle().addSource(std::make_unique<style::CustomGeometrySource>("custom", options));
auto layer = std::make_unique<style::CircleLayer>("circles", "custom");
layer->setCircleColor(Color{0.0, 0.0, 1.0, 0.0});
map.getStyle().addLayer(std::move(layer));
}
void checkImage(std::string image, uint32_t expectedTileCount, double imageThreshold = 0.0002) {
checkImages(std::vector<std::string>{image}, expectedTileCount, imageThreshold);
}
void checkImages(std::vector<std::string> images, uint32_t expectedTileCount, double imageThreshold) {
const auto result = frontend.render(map);
// check this after frontend.render()
EXPECT_EQ(tileCount, expectedTileCount);
std::transform(images.begin(), images.end(), images.begin(), [](const auto& img) {
return "test/fixtures/tile_lod/" + img;
});
test::checkImages(images, result.image, imageThreshold, 0.01);
}
};
TEST(TileLOD, disabled) {
TileLODTest test(CameraOptions().withZoom(10.0));
test.map.setTileLodPitchThreshold(std::numbers::pi);
test.checkImage("disabled", 4);
}
TEST(TileLOD, zoomShift) {
TileLODTest test(CameraOptions().withZoom(12.0));
test.map.setTileLodZoomShift(4.0);
test.frontend.render(test.map);
EXPECT_EQ(test.tileCount, 64);
test.tileCount = 0;
test.map.setTileLodZoomShift(-4.0);
test.frontend.render(test.map);
EXPECT_EQ(test.tileCount, 4);
}
TEST(TileLOD, pitchThreshold) {
TileLODTest test(CameraOptions().withZoom(10.0));
constexpr double pitch = 45.0;
test.map.setTileLodZoomShift(4.0);
test.map.setTileLodMinRadius(1.0);
test.map.setTileLodScale(1.0);
test.map.setTileLodPitchThreshold(pitch / 180.0 * std::numbers::pi);
test.map.jumpTo(CameraOptions().withPitch(pitch - 1.0));
test.frontend.render(test.map);
EXPECT_EQ(test.tileCount, 140);
test.tileCount = 0;
test.map.jumpTo(CameraOptions().withPitch(pitch + 1.0));
test.checkImages(
{
"pitchThreshold-line",
"pitchThreshold-polyline",
},
22,
0.0015);
}
TEST(TileLOD, scale) {
TileLODTest test(CameraOptions().withZoom(0.0));
test.map.setTileLodMinRadius(1.0);
test.map.setTileLodScale(0.4);
test.map.setTileLodPitchThreshold(-1.0);
test.map.setTileLodZoomShift(5.0);
test.checkImage("scale", 112);
}
TEST(TileLOD, radius) {
TileLODTest test(CameraOptions().withZoom(0.0));
test.map.setTileLodMinRadius(5.0);
test.map.setTileLodScale(1.0);
test.map.setTileLodPitchThreshold(-1.0);
test.map.setTileLodZoomShift(5.0);
test.checkImage("radius", 172);
}

View File

@@ -44,7 +44,7 @@ TEST(TileCover, Pitch) {
.withPitch(40.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}),
util::tileCover(transform.getState(), 2));
util::tileCover({transform.getState()}, 2));
}
TEST(TileCover, PitchIssue15442) {
@@ -62,7 +62,7 @@ TEST(TileCover, PitchIssue15442) {
EXPECT_EQ((std::vector<OverscaledTileID>{
{2, 3, 1}, {2, 2, 1}, {2, 3, 0}, {2, 2, 0}, {2, 1, {2, 0, 0}}, {2, 1, {2, 1, 0}}}),
util::tileCover(transform.getState(), 2));
util::tileCover({transform.getState()}, 2));
}
TEST(TileCover, PitchOverAllowedByContentInsets) {
@@ -80,7 +80,7 @@ TEST(TileCover, PitchOverAllowedByContentInsets) {
EXPECT_EQ(
(std::vector<OverscaledTileID>{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}, {3, 4, 2}, {3, 5, 3}, {3, 5, 2}}),
util::tileCover(transform.getState(), 3));
util::tileCover({transform.getState()}, 3));
}
TEST(TileCover, PitchWithLargerResultSet) {
@@ -96,7 +96,7 @@ TEST(TileCover, PitchWithLargerResultSet) {
.withBearing(-142.2630000003529176)
.withPitch(60.0));
auto cover = util::tileCover(transform.getState(), 5);
auto cover = util::tileCover({transform.getState()}, 5);
// Returned vector has above 100 elements, we check first 16 as there is a
// plan to return lower LOD for distant tiles.
EXPECT_EQ((std::vector<OverscaledTileID>{{5, 15, 16},
@@ -148,7 +148,7 @@ TEST(TileCover, CoordinatesAreUnwrapped) {
EXPECT_EQ(
(std::vector<OverscaledTileID>{{1, 0, {1, 1, 0}}, {1, 1, {1, 0, 0}}, {1, 0, {1, 1, 1}}, {1, 1, {1, 0, 1}}}),
util::tileCover(transform.getState(), 1));
util::tileCover({transform.getState()}, 1));
}
TEST(TileCover, DifferentOverscaledZ) {
@@ -165,7 +165,7 @@ TEST(TileCover, DifferentOverscaledZ) {
EXPECT_EQ(
(std::vector<OverscaledTileID>{{3, 0, {2, 1, 1}}, {3, 0, {2, 2, 1}}, {3, 0, {2, 1, 2}}, {3, 0, {2, 2, 2}}}),
util::tileCover(transform.getState(), 2, 3));
util::tileCover({transform.getState()}, 2, 3));
}
TEST(TileCover, DifferentOverscaledZWithPitch) {
@@ -186,7 +186,7 @@ TEST(TileCover, DifferentOverscaledZWithPitch) {
{5, 0, {3, 3, 1}},
{5, 0, {3, 4, 1}},
{5, 0, {3, 2, 1}}}),
util::tileCover(transform.getState(), 3, 5));
util::tileCover({transform.getState()}, 3, 5));
}
TEST(TileCover, DifferentOverscaledZWrapped) {
@@ -201,7 +201,7 @@ TEST(TileCover, DifferentOverscaledZWrapped) {
EXPECT_EQ(
(std::vector<OverscaledTileID>{{2, 0, {1, 1, 0}}, {2, 1, {1, 0, 0}}, {2, 0, {1, 1, 1}}, {2, 1, {1, 0, 1}}}),
util::tileCover(transform.getState(), 1, 2));
util::tileCover({transform.getState()}, 1, 2));
}
TEST(TileCover, FlippedY) {
@@ -216,7 +216,7 @@ TEST(TileCover, FlippedY) {
.withZoom(1.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}),
util::tileCover(transform.getState(), 1));
util::tileCover({transform.getState()}, 1));
}
TEST(TileCover, FlippedYPitch) {
@@ -233,7 +233,7 @@ TEST(TileCover, FlippedYPitch) {
.withPitch(40.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}),
util::tileCover(transform.getState(), 2));
util::tileCover({transform.getState()}, 2));
}
TEST(TileCover, FlippedYHelsinki) {
@@ -248,7 +248,7 @@ TEST(TileCover, FlippedYHelsinki) {
.withZoom(11.447425));
EXPECT_EQ((std::vector<OverscaledTileID>{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}),
util::tileCover(transform.getState(), 11));
util::tileCover({transform.getState()}, 11));
}
TEST(TileCoverStream, Arctic) {