gistools/geometry/s2/metrics.rs
1use crate::geometry::K_MAX_CELL_LEVEL;
2use core::f64::consts::{PI, SQRT_2};
3use libm::{ilogb, ldexp, sqrt};
4
5/// sqrt(2/3)
6#[cfg(feature = "tan")]
7const SQRT_2_3: f64 = 0.816496580927726;
8
9/// The following are various constants that describe the shapes and sizes of
10/// S2Cells (see s2coords.h and s2cell_id.h). They are useful for deciding
11/// which cell level to use in order to satisfy a given condition (e.g. that
12/// cell vertices must be no further than "x" apart). All of the raw constants
13/// are differential quantities; you can use the getValue(level) method to
14/// compute the corresponding length or area on the unit sphere for cells at a
15/// given level. The minimum and maximum bounds are valid for cells at all
16/// levels, but they may be somewhat conservative for very large cells
17/// (e.g. face cells).
18///
19/// All of the values below were obtained by a combination of hand analysis and
20/// Mathematica. In general, S2_TAN_PROJECTION produces the most uniform
21/// shapes and sizes of cells, S2_LINEAR_PROJECTION is considerably worse, and
22/// S2_QUADRATIC_PROJECTION is somewhere in between (but generally closer to
23/// the tangent projection than the linear one).
24///
25/// Note that S2_LINEAR_PROJECTION can be useful for analysis even when another
26/// projection is being used, since it allows many cell metrics to be bounded
27/// in terms of (u,v) coordinates rather than (s,t) coordinates. (With the
28/// linear projection, u = 2 * s - 1 and similarly for v.) Similarly,
29/// S2_TAN_PROJECTION allows cell metrics to be bounded in terms of (u,v)
30/// coordinate changes when they are measured as distances on the unit sphere.
31///
32/// Defines a cell metric of the given dimension (1 == length, 2 == area).
33#[derive(Debug, Copy, Clone)]
34pub struct Metric<const DIM: i32> {
35 /// The "deriv" value of a metric is a derivative, and must be multiplied by
36 /// a length or area in (s,t)-space to get a useful value.
37 pub deriv: f64,
38}
39impl<const DIM: i32> Metric<DIM> {
40 /// Return the value of a metric for cells at the given level. The value is
41 /// either a length or an area on the unit sphere, depending on the
42 /// particular metric.
43 pub fn get_value(&self, level: i32) -> f64 {
44 self.deriv * ldexp(2.0, -DIM * level)
45 }
46
47 /// Return the level at which the metric has approximately the given value.
48 /// For example, K_AVG_EDGE.getClosestLevel(0.1) returns the level at which
49 /// the average cell edge length is approximately 0.1. The return value is
50 pub fn get_closest_level(&self, value: f64) -> i32 {
51 self.get_level_for_max_value((if DIM == 1 { sqrt(2.) } else { 2. }) * value)
52 }
53
54 /// Return the minimum level such that the metric is at most the given value,
55 /// or S2CellId::kMaxLevel if there is no such level. For example,
56 /// K_MAX_DIAG.get_level_for_max_value(0.1) returns the minimum level such
57 /// that all cell diagonal lengths are 0.1 or smaller. The return value
58 /// is always a valid level.
59 pub fn get_level_for_max_value(&self, value: f64) -> i32 {
60 let k_max_cell_level: i32 = K_MAX_CELL_LEVEL.into();
61 if value <= 0. {
62 return k_max_cell_level;
63 }
64 let mut level = ilogb(value / self.deriv);
65 level = (-(level >> (DIM - 1))).clamp(0, k_max_cell_level);
66
67 level
68 }
69
70 /// Return the maximum level such that the metric is at least the given value,
71 /// or 0 if there is no such level. For example,
72 /// K_MAX_DIAG.getLevelForMinValue(0.1) returns the maximum level such that
73 /// all cells have a minimum width of 0.1 or larger. The return value is
74 /// always a valid level.
75 pub fn get_level_for_min_value(&self, value: f64) -> i32 {
76 let k_max_cell_level: i32 = K_MAX_CELL_LEVEL.into();
77 if value <= 0. {
78 return k_max_cell_level;
79 }
80 let mut level = ilogb(self.deriv / value);
81 level = (level >> (DIM - 1)).clamp(0, k_max_cell_level);
82
83 level
84 }
85}
86
87/// Length based metrics
88pub type LengthMetric = Metric<1>;
89/// Area based metrics
90pub type AreaMetric = Metric<2>;
91
92/// Each cell is bounded by four planes passing through its four edges and
93/// the center of the sphere. These metrics relate to the angle between each
94/// pair of opposite bounding planes, or equivalently, between the planes
95/// corresponding to two different s-values or two different t-values. For
96/// example, the maximum angle between opposite bounding planes for a cell at
97/// level k is kMaxAngleSpan.GetValue(k), and the average angle span for all
98/// cells at level k is approximately kAvgAngleSpan.GetValue(k).
99#[cfg(any(feature = "quadratic", feature = "tan", feature = "linear"))]
100pub const K_MIN_ANGLE_SPAN: LengthMetric = LengthMetric {
101 deriv: match () {
102 #[cfg(feature = "quadratic")]
103 _ => 4. / 3.,
104 #[cfg(feature = "tan")]
105 _ => PI / 2.,
106 #[cfg(feature = "linear")]
107 _ => 1.,
108 },
109};
110/// Each cell is bounded by four planes passing through its four edges and
111/// the center of the sphere. These metrics relate to the angle between each
112/// pair of opposite bounding planes, or equivalently, between the planes
113/// corresponding to two different s-values or two different t-values. For
114/// example, the maximum angle between opposite bounding planes for a cell at
115/// level k is kMaxAngleSpan.GetValue(k), and the average angle span for all
116/// cells at level k is approximately kAvgAngleSpan.GetValue(k).
117pub const K_MAX_ANGLE_SPAN: LengthMetric = LengthMetric {
118 deriv: match () {
119 #[cfg(feature = "quadratic")]
120 _ => 1.704_897_179_199_218_5, // 1.705
121 #[cfg(feature = "tan")]
122 _ => PI / 2., // 1.571
123 #[cfg(feature = "linear")]
124 _ => 2., // 2
125 },
126};
127/// Each cell is bounded by four planes passing through its four edges and
128/// the center of the sphere. These metrics relate to the angle between each
129/// pair of opposite bounding planes, or equivalently, between the planes
130/// corresponding to two different s-values or two different t-values. For
131/// example, the maximum angle between opposite bounding planes for a cell at
132/// level k is kMaxAngleSpan.GetValue(k), and the average angle span for all
133/// cells at level k is approximately kAvgAngleSpan.GetValue(k).
134pub const K_AVG_ANGLE_SPAN: LengthMetric = LengthMetric { deriv: PI / 2. }; // 1.571
135
136/// The width of geometric figure is defined as the distance between two
137/// parallel bounding lines in a given direction. For cells, the minimum
138/// width is always attained between two opposite edges, and the maximum
139/// width is attained between two opposite vertices. However, for our
140/// purposes we redefine the width of a cell as the perpendicular distance
141/// between a pair of opposite edges. A cell therefore has two widths, one
142/// in each direction. The minimum width according to this definition agrees
143/// with the classic geometric one, but the maximum width is different. (The
144/// maximum geometric width corresponds to kMaxDiag defined below.)
145///
146/// For a cell at level k, the distance between opposite edges is at least
147/// kMinWidth.getValue(k) and at most kMaxWidth.getValue(k). The average
148/// width in both directions for all cells at level k is approximately
149/// kAvgWidth.getValue(k).
150///
151/// The width is useful for bounding the minimum or maximum distance from a
152/// point on one edge of a cell to the closest point on the opposite edge.
153/// For example, this is useful when "growing" regions by a fixed distance.
154///
155/// Note that because S2Cells are not usually rectangles, the minimum width of
156/// a cell is generally smaller than its minimum edge length. (The interior
157/// angles of an S2Cell range from 60 to 120 degrees.)
158///
159/// Linear -> sqrt(2.0 / 3.0) (0.816)
160/// Tan -> pi / (2.0 * @sqrt(2.0)) (1.111)
161/// Quadratic -> 2.0 * @sqrt(2.0) / 3.0 (0.943) [Default]
162pub const K_MIN_WIDTH: LengthMetric = LengthMetric {
163 deriv: match () {
164 #[cfg(feature = "quadratic")]
165 _ => 2. * SQRT_2 / 3., // 0.943
166 #[cfg(feature = "tan")]
167 _ => PI / (2. * SQRT_2), // 1.111
168 #[cfg(feature = "linear")]
169 _ => sqrt(2. / 3.), // 0.816
170 },
171};
172
173/// The same as kMaxAngleSpan's derivative.
174pub const K_MAX_WIDTH: LengthMetric = LengthMetric { deriv: K_MAX_ANGLE_SPAN.deriv };
175
176/// - Linear -> 1.411459345844456965
177/// - Tan -> 1.437318638925160885
178/// - Quadratic -> 1.434523672886099389
179pub const K_AVG_WIDTH: LengthMetric = LengthMetric {
180 deriv: match () {
181 #[cfg(feature = "quadratic")]
182 _ => 1.434_523_672_886_099_5, // 1.435
183 #[cfg(feature = "tan")]
184 _ => 1.437318638925160885, // 1.437
185 #[cfg(feature = "linear")]
186 _ => 1.411459345844456965, // 1.411
187 },
188};
189
190/// The minimum edge length of any cell at level k is at least
191/// kMinEdge.getValue(k), and the maximum is at most kMaxEdge.getValue(k).
192/// The average edge length is approximately kAvgEdge.getValue(k).
193///
194/// The edge length metrics can also be used to bound the minimum, maximum,
195/// or average distance from the center of one cell to the center of one of
196/// its edge neighbors. In particular, it can be used to bound the distance
197/// between adjacent cell centers along the space-filling Hilbert curve for
198/// cells at any given level.
199///
200/// - linear -> 2.0 * sqrt(2.0) / 3.0 (0.943)
201/// - tan -> pi / (2.0 * sqrt(2.0)) (1.111)
202/// - quadratic -> 2.0 * sqrt(2.0) / 3.0 (0.943) [Default]
203pub const K_MIN_EDGE: LengthMetric = LengthMetric {
204 deriv: match () {
205 #[cfg(feature = "quadratic")]
206 _ => 2. * SQRT_2 / 3., // 0.943
207 #[cfg(feature = "tan")]
208 _ => PI / (2. * SQRT_2), // 1.111
209 #[cfg(feature = "linear")]
210 _ => 2. * SQRT_2 / 3., // 0.943
211 },
212};
213
214/// The same as kMaxAngleSpan's derivative.
215pub const K_MAX_EDGE: LengthMetric = LengthMetric { deriv: K_MAX_ANGLE_SPAN.deriv };
216
217/// Average edge length
218pub const K_AVG_EDGE: LengthMetric = LengthMetric {
219 deriv: match () {
220 #[cfg(feature = "quadratic")]
221 _ => 1.459_213_746_386_106_1, // 1.459
222 #[cfg(feature = "tan")]
223 _ => 1.461667032546739266, // 1.462
224 #[cfg(feature = "linear")]
225 _ => 1.440034192955603643, // 1.440
226 },
227};
228
229/// The minimum diagonal length of any cell at level k is at least
230/// kMinDiag.getValue(k), and the maximum is at most kMaxDiag.getValue(k).
231/// The average diagonal length is approximately kAvgDiag.getValue(k).
232///
233/// The maximum diagonal also happens to be the maximum diameter of any cell,
234/// and also the maximum geometric width (see the discussion above). So for
235/// example, the distance from an arbitrary point to the closest cell center
236/// at a given level is at most half the maximum diagonal length.
237///
238/// - Linear -> 2.0 * @sqrt(2.0) / 3.0 (0.943)
239/// - Tan -> pi * @sqrt(2.0) / 3.0 (1.481)
240/// - Quadratic -> 8.0 * @sqrt(2.0) / 9.0 (1.257) [Default]
241pub const K_MIN_DIAG: LengthMetric = LengthMetric {
242 deriv: match () {
243 #[cfg(feature = "quadratic")]
244 _ => 8. * SQRT_2 / 9., // 1.257
245 #[cfg(feature = "tan")]
246 _ => PI * SQRT_2 / 3., // 1.481
247 #[cfg(feature = "linear")]
248 _ => 2. * SQRT_2 / 3., // 0.943
249 },
250};
251
252/// - Linear -> 2.0 * @sqrt(2.0) (2.828)
253/// - Tan -> pi * @sqrt(2.0 / 3.0) (2.565)
254/// - Quadratic -> 2.438654594434021032 [Default]
255pub const K_MAX_DIAG: LengthMetric = LengthMetric {
256 deriv: match () {
257 #[cfg(feature = "quadratic")]
258 _ => 2.438_654_594_434_021, // 2.439
259 #[cfg(feature = "tan")]
260 _ => PI * SQRT_2_3, // 2.565
261 #[cfg(feature = "linear")]
262 _ => 2. * SQRT_2, // 2.828
263 },
264};
265
266/// - Linear -> 2.031817866418812674
267/// - Tan -> 2.063623197195635753
268/// - Quadratic -> 2.060422738998471683 [Default]
269pub const K_AVG_DIAG: LengthMetric = LengthMetric {
270 deriv: match () {
271 #[cfg(feature = "quadratic")]
272 _ => 2.060_422_738_998_471_7, // 2.060
273 #[cfg(feature = "tan")]
274 _ => 2.063623197195635753, // 2.064
275 #[cfg(feature = "linear")]
276 _ => 2.031817866418812674, // 2.032
277 },
278};
279
280/// The minimum area of any cell at level k is at least kMinArea.getValue(k),
281/// and the maximum is at most kMaxArea.getValue(k). The average area of all
282/// cells at level k is exactly kAvgArea.getValue(k).
283///
284/// - Linear -> 4.0 / (3.0 * @sqrt(3.0)) (0.770)
285/// - Tan -> pi * pi / (4.0 * @sqrt(2.0)) (1.745)
286/// - Quadratic -> 8.0 * @sqrt(2.0) / 9.0 (1.257) [Default]
287pub const K_MIN_AREA: AreaMetric = AreaMetric {
288 deriv: match () {
289 #[cfg(feature = "quadratic")]
290 _ => 8. * SQRT_2 / 9., // 1.257
291 #[cfg(feature = "tan")]
292 _ => (PI * PI) / (4. * SQRT_2), // 1.745
293 #[cfg(feature = "linear")]
294 _ => 4. / (3. * SQRT_3), // 0.770
295 },
296};
297
298/// Max Area Metric
299/// - Linear -> 4.0
300/// - Tan -> pi * pi / 4.0 (2.467)
301/// - Quadratic -> 2.635799256963161491 [Default]
302pub const K_MAX_AREA: AreaMetric = AreaMetric {
303 deriv: match () {
304 #[cfg(feature = "quadratic")]
305 _ => 2.635_799_256_963_161_4, // 2.636
306 #[cfg(feature = "tan")]
307 _ => (PI * PI) / 4., // 2.467
308 #[cfg(feature = "linear")]
309 _ => 4., // 4.000
310 },
311};
312
313/// returns the AreaMetric of 4 * Math.PI / 6) (true for all projections)
314pub const K_AVG_AREA: AreaMetric = AreaMetric { deriv: 4. * PI / 6. };
315
316/// This is the maximum edge aspect ratio over all cells at any level, where
317/// the edge aspect ratio of a cell is defined as the ratio of its longest
318/// edge length to its shortest edge length.
319///
320/// - linear -> sqrt(2)
321/// - tan -> sqrt(2)
322/// - quadratic -> 1.4426 (default)
323#[cfg(feature = "quadratic")]
324pub const K_MAX_EDGE_ASPECT: f64 = SQRT_2; // 1.414
325#[cfg(feature = "tan")]
326pub const K_MAX_EDGE_ASPECT: f64 = SQRT_2; // 1.414
327#[cfg(feature = "linear")]
328pub const K_MAX_EDGE_ASPECT: f64 = 1.442615274452682920;
329
330/// The maximum aspect ratio of a diagonal of a cell.
331/// NOTE: This is SQRT_3, but currently its behind a flag in rust
332#[allow(clippy::excessive_precision)]
333pub const K_MAX_DIAG_ASPECT: f64 = 1.732050807568877293527446341505872367_f64;