tile_grid/wmts/
grid.rs

1//
2// Copyright (c) Pirmin Kalberer. All rights reserved.
3// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4//
5
6//!Tile grids
7
8pub use crate::transform::lonlat_to_merc;
9use std::f64::consts;
10
11/// Geographic extent
12#[derive(PartialEq, Clone, Debug)]
13pub struct Extent {
14    pub minx: f64,
15    pub miny: f64,
16    pub maxx: f64,
17    pub maxy: f64,
18}
19
20/// Min and max grid cell numbers
21#[derive(PartialEq, Clone, Debug)]
22pub struct ExtentInt {
23    pub minx: u32,
24    pub miny: u32,
25    pub maxx: u32,
26    pub maxy: u32,
27}
28
29// Max grid cell numbers
30type CellIndex = (u32, u32);
31
32/// Grid origin
33#[derive(PartialEq, Clone, Debug)]
34pub enum Origin {
35    TopLeft,
36    BottomLeft, //TopRight, BottomRight
37}
38
39/// Grid units
40#[derive(PartialEq, Clone, Debug)]
41pub enum Unit {
42    Meters,
43    Degrees,
44    Feet,
45}
46
47/// Tile grid
48#[derive(Clone, Debug)]
49pub struct Grid {
50    /// The width of an individual tile, in pixels.
51    width: u16,
52    /// The height of an individual tile, in pixels.
53    height: u16,
54    /// The geographical extent covered by the grid, in ground units (e.g. meters, degrees, feet, etc.).
55    /// Must be specified as 4 floating point numbers ordered as minx, miny, maxx, maxy.
56    /// The (minx,miny) point defines the origin of the grid, i.e. the pixel at the bottom left of the
57    /// bottom-left most tile is always placed on the (minx,miny) geographical point.
58    /// The (maxx,maxy) point is used to determine how many tiles there are for each zoom level.
59    pub extent: Extent,
60    /// Spatial reference system (PostGIS SRID).
61    pub srid: i32,
62    /// Grid units
63    pub units: Unit,
64    /// This is a list of resolutions for each of the zoom levels defined by the grid.
65    /// This must be supplied as a list of positive floating point values, ordered from largest to smallest.
66    /// The largest value will correspond to the grid’s zoom level 0. Resolutions
67    /// are expressed in “units-per-pixel”,
68    /// depending on the unit used by the grid (e.g. resolutions are in meters per
69    /// pixel for most grids used in webmapping).
70    resolutions: Vec<f64>,
71    /// maxx/maxy for each resolution
72    level_max: Vec<CellIndex>,
73    /// Grid origin
74    pub origin: Origin,
75}
76
77impl Grid {
78    /// WGS84 grid
79    pub fn wgs84() -> Grid {
80        Grid::new(
81            256,
82            256,
83            Extent {
84                minx: -180.0,
85                miny: -90.0,
86                maxx: 180.0,
87                maxy: 90.0,
88            },
89            4326,
90            Unit::Degrees,
91            vec![
92                0.703125000000000,
93                0.351562500000000,
94                0.175781250000000,
95                8.78906250000000e-2,
96                4.39453125000000e-2,
97                2.19726562500000e-2,
98                1.09863281250000e-2,
99                5.49316406250000e-3,
100                2.74658203125000e-3,
101                1.37329101562500e-3,
102                6.86645507812500e-4,
103                3.43322753906250e-4,
104                1.71661376953125e-4,
105                8.58306884765625e-5,
106                4.29153442382812e-5,
107                2.14576721191406e-5,
108                1.07288360595703e-5,
109                5.36441802978516e-6,
110            ],
111            Origin::BottomLeft,
112        )
113    }
114
115    /// Web Mercator grid (Google maps compatible)
116    #[allow(clippy::excessive_precision)]
117    pub fn web_mercator() -> Grid {
118        Grid::new(
119            256,
120            256,
121            Extent {
122                minx: -20037508.3427892480,
123                miny: -20037508.3427892480,
124                maxx: 20037508.3427892480,
125                maxy: 20037508.3427892480,
126            },
127            3857,
128            Unit::Meters,
129            // for calculation see fn test_resolutions
130            vec![
131                156543.0339280410,
132                78271.5169640205,
133                39135.75848201025,
134                19567.879241005125,
135                9783.939620502562,
136                4891.969810251281,
137                2445.9849051256406,
138                1222.9924525628203,
139                611.4962262814101,
140                305.7481131407051,
141                152.87405657035254,
142                76.43702828517627,
143                38.218514142588134,
144                19.109257071294067,
145                9.554628535647034,
146                4.777314267823517,
147                2.3886571339117584,
148                1.1943285669558792,
149                0.5971642834779396,
150                0.2985821417389698,
151                0.1492910708694849,
152                0.07464553543474245,
153                0.037322767717371225,
154            ],
155            Origin::BottomLeft,
156        )
157    }
158
159    pub fn new(
160        width: u16,
161        height: u16,
162        extent: Extent,
163        srid: i32,
164        units: Unit,
165        resolutions: Vec<f64>,
166        origin: Origin,
167    ) -> Grid {
168        let mut grid = Grid {
169            width,
170            height,
171            extent,
172            srid,
173            units,
174            resolutions,
175            origin,
176            level_max: Vec::new(),
177        };
178        grid.level_max = grid.level_max();
179        grid
180    }
181    pub fn nlevels(&self) -> u8 {
182        self.resolutions.len() as u8
183    }
184    pub fn maxzoom(&self) -> u8 {
185        self.nlevels() - 1
186    }
187    /// Pixel width for 256x256 tile
188    pub fn pixel_width(&self, zoom: u8) -> f64 {
189        const METERS_PER_DEGREE: f64 = 6378137.0 * 2.0 * consts::PI / 360.0;
190        match self.units {
191            Unit::Meters => self.resolutions[zoom as usize],
192            Unit::Degrees => self.resolutions[zoom as usize] * METERS_PER_DEGREE,
193            Unit::Feet => self.resolutions[zoom as usize] * 0.3048,
194        }
195    }
196    /// Scale denominator based on standardized pixel size (<https://www.ogc.org/standards/se>)
197    pub fn scale_denominator(&self, zoom: u8) -> f64 {
198        // Standardized rendering pixel size according to OGC Symbology Encoding standard
199        const PIXEL_SCREEN_WIDTH: f64 = 0.00028;
200        self.pixel_width(zoom) / PIXEL_SCREEN_WIDTH
201    }
202    /// Extent of a given tile in the grid given its x, y, and z in TMS adressing scheme
203    pub fn tile_extent(&self, xtile: u32, ytile: u32, zoom: u8) -> Extent {
204        // based on mapcache_grid_get_tile_extent
205        let res = self.resolutions[zoom as usize];
206        let tile_sx = self.width as f64;
207        let tile_sy = self.height as f64;
208        match self.origin {
209            Origin::BottomLeft => Extent {
210                minx: self.extent.minx + (res * xtile as f64 * tile_sx),
211                miny: self.extent.miny + (res * ytile as f64 * tile_sy),
212                maxx: self.extent.minx + (res * (xtile + 1) as f64 * tile_sx),
213                maxy: self.extent.miny + (res * (ytile + 1) as f64 * tile_sy),
214            },
215            Origin::TopLeft => Extent {
216                minx: self.extent.minx + (res * xtile as f64 * tile_sx),
217                miny: self.extent.maxy - (res * (ytile + 1) as f64 * tile_sy),
218                maxx: self.extent.minx + (res * (xtile + 1) as f64 * tile_sx),
219                maxy: self.extent.maxy - (res * ytile as f64 * tile_sy),
220            },
221        }
222    }
223    /// reverse y tile for XYZ adressing scheme
224    pub fn ytile_from_xyz(&self, ytile: u32, zoom: u8) -> u32 {
225        // y = maxy-ytile-1
226        let maxy = self.level_max[zoom as usize].1;
227
228        maxy.saturating_sub(ytile).saturating_sub(1)
229    }
230    /// Extent of a given tile in XYZ adressing scheme
231    pub fn tile_extent_xyz(&self, xtile: u32, ytile: u32, zoom: u8) -> Extent {
232        let y = self.ytile_from_xyz(ytile, zoom);
233        self.tile_extent(xtile, y, zoom)
234    }
235    /// (maxx, maxy) of grid level
236    pub(crate) fn level_limit(&self, zoom: u8) -> CellIndex {
237        let res = self.resolutions[zoom as usize];
238        let unitheight = self.height as f64 * res;
239        let unitwidth = self.width as f64 * res;
240
241        let maxy =
242            ((self.extent.maxy - self.extent.miny - 0.01 * unitheight) / unitheight).ceil() as u32;
243        let maxx =
244            ((self.extent.maxx - self.extent.minx - 0.01 * unitwidth) / unitwidth).ceil() as u32;
245        (maxx, maxy)
246    }
247    /// (maxx, maxy) of all grid levels
248    fn level_max(&self) -> Vec<CellIndex> {
249        (0..self.nlevels())
250            .map(|zoom| self.level_limit(zoom))
251            .collect()
252    }
253    /// Tile index limits covering extent
254    pub fn tile_limits(&self, extent: Extent, tolerance: i32) -> Vec<ExtentInt> {
255        // Based on mapcache_grid_compute_limits
256        const EPSILON: f64 = 0.0000001;
257        (0..self.nlevels())
258            .map(|i| {
259                let res = self.resolutions[i as usize];
260                let unitheight = self.height as f64 * res;
261                let unitwidth = self.width as f64 * res;
262                let (level_maxx, level_maxy) = self.level_max[i as usize];
263
264                let (mut minx, mut maxx, mut miny, mut maxy) = match self.origin {
265                    Origin::BottomLeft => (
266                        (((extent.minx - self.extent.minx) / unitwidth + EPSILON).floor() as i32)
267                            - tolerance,
268                        (((extent.maxx - self.extent.minx) / unitwidth - EPSILON).ceil() as i32)
269                            + tolerance,
270                        (((extent.miny - self.extent.miny) / unitheight + EPSILON).floor() as i32)
271                            - tolerance,
272                        (((extent.maxy - self.extent.miny) / unitheight - EPSILON).ceil() as i32)
273                            + tolerance,
274                    ),
275                    Origin::TopLeft => (
276                        (((extent.minx - self.extent.minx) / unitwidth + EPSILON).floor() as i32)
277                            - tolerance,
278                        (((extent.maxx - self.extent.minx) / unitwidth - EPSILON).ceil() as i32)
279                            + tolerance,
280                        (((self.extent.maxy - extent.maxy) / unitheight + EPSILON).floor() as i32)
281                            - tolerance,
282                        (((self.extent.maxy - extent.miny) / unitheight - EPSILON).ceil() as i32)
283                            + tolerance,
284                    ),
285                };
286
287                // to avoid requesting out-of-range tiles
288                if minx < 0 {
289                    minx = 0;
290                }
291                if maxx > level_maxx as i32 {
292                    maxx = level_maxx as i32
293                };
294                if miny < 0 {
295                    miny = 0
296                };
297                if maxy > level_maxy as i32 {
298                    maxy = level_maxy as i32
299                };
300
301                ExtentInt {
302                    minx: minx as u32,
303                    maxx: maxx as u32,
304                    miny: miny as u32,
305                    maxy: maxy as u32,
306                }
307            })
308            .collect()
309    }
310}
311
312/// Projected extent
313pub fn extent_wgs84_to_merc(extent: &Extent) -> Extent {
314    let (minx, miny) = lonlat_to_merc(extent.minx, extent.miny);
315    let (maxx, maxy) = lonlat_to_merc(extent.maxx, extent.maxy);
316    Extent {
317        minx,
318        miny,
319        maxx,
320        maxy,
321    }
322}