Struct PitchLodBands
Where the distance-LOD level boundaries sit on screen under pitch: the horizontal lines at which a pixel needs one more coarse level.
public readonly struct PitchLodBands
- Inherited Members
Remarks
The perspective depth W of a ground point depends only on its screen Y. Bearing, pan and zoom cancel out of it, because the mercator-to-view rotation is orthogonal and the W row reads only the view-space Y. Exactly, in exact arithmetic: W = D / (D + (screenY - h/2) sinP) with D = camDist cosP, so 1/W = 1 + (screenY - h/2) tanP / camDist.
In float the residual is a round trip, not an identity, and it scales with
zoom: ScreenToMercator(ScreenPoint) adds a small offset
to an absolute mercator center near 0.5, quantizing it, and
BuildRenderMatrixRows multiplies the recovered offset by
tileSizePx * 2^zoom. Measured worst relative variation of W across the full
screen width: 1.7e-10 up to z22 with 256 px tiles, but 2.0e-8 at z28 with
512 px tiles, both of which the types accept. That moves
BoundaryScreenY(int) by at most 1.3e-5 px over a full bearing
sweep, which is five orders below MinBandPx and cannot change a
selected tile, but it is why the invariance tests carry a tolerance rather
than asserting equality. Probing at screen X = width/2 instead of 0 was
measured and buys only 1.0-1.2x: the center round trip dominates, not the
rotation cancellation.
So the level a pixel needs is a function of its Y alone, and the boundaries are horizontal screen lines that do not move when the map rotates or pans. Choosing a level per mercator row from a depth sampled at the row's X midpoint did not have that property, because a mercator row runs diagonally across the screen at any bearing off the axis: measured 6.32 level changes per degree of rotation at pitch 60, with up to 101 of 576 screen samples flipping level in a single 0.25 degree step.
1/W is affine in screen Y, because the ground plane reaches the screen through a homography and W is therefore linear-fractional in Y, so two probes pin every boundary exactly. Same sweep and the same envelope caveat as above: worst relative error of the two-probe model against a direct probe, 1.3e-10 up to z22 at 256 px tiles. Probing MapViewTransform rather than re-deriving the camera algebra here is deliberate - the camera model stays in one place, so a future terrain or camera change cannot leave this type behind.
Fields
PitchEpsilonDeg
Pitch at or below which distance LOD is off entirely, matching the flat fast path.
public const double PitchEpsilonDeg = 0.01
Field Value
Properties
Pitched
Whether the view is tilted at all, so that depth varies down the screen and the boundary fit is usable. False leaves a single band covering the whole viewport.
public bool Pitched { get; }
Property Value
Remarks
True does not mean a coarser band exists. It is true from just above PitchEpsilonDeg, but under the shipped camera model the drop-1 boundary only enters the viewport past about pitch 45, so at pitch 20 this is true and BoundaryScreenY(int) still returns 0 for every drop. Ask BoundaryScreenY(int) whether a band exists; this only says whether the fit divides by a non-zero slope.
Methods
BoundaryScreenY(int)
Screen Y of the line above which the needed level is at least
drop levels coarser than at the nearest visible
ground, which is the bottom screen edge.
public double BoundaryScreenY(int drop)
Parameters
dropintLevels of coarsening. Zero returns the viewport height, the near edge the ratios are measured from.
Returns
- double
Screen Y in [0, height], decreasing as
droprises, so the bands are always a well-ordered partition of the viewport. A boundary above the viewport clamps to 0 and leaves that band and every coarser one empty; under the shipped camera model that is the case for every drop of 2 or more at any pitch up to the 60 degree limit (measured: the drop-2 line sits at screen Y -26.5 of 1080 at pitch 60).
Build(MapViewTransform, bool)
Fits the bands to a view.
public static PitchLodBands Build(MapViewTransform view, bool distanceLodEnabled = false)
Parameters
viewMapViewTransformThe view to probe. Not retained.
distanceLodEnabledboolFalse collapses onto the flat single band the pitch-0 fast path already returns, so switching distance LOD off reaches a code path that is already covered rather than a second selection algorithm. The runtime layer drives this; Core is told, never asks. Default false. With LOD off the map never changes zoom level unless the user actually zooms. Cost is roughly 4x tiles at pitch 60 (159 vs 89), accepted at the 128-layer mid-mobile tier.