瀏覽代碼

tile_source, coord: Add support for quadkeys

Johannes Hofmann 8 年之前
父節點
當前提交
cc1ad8f123
共有 3 個檔案被更改,包括 91 行新增34 行删除
  1. 66
    17
      src/coord.rs
  2. 15
    12
      src/tile_loader.rs
  3. 10
    5
      src/tile_source.rs

+ 66
- 17
src/coord.rs 查看文件

@@ -59,23 +59,7 @@ impl MapCoord {
59 59
 
60 60
 }
61 61
 
62
-#[test]
63
-fn test_normalize() {
64
-    {
65
-        let a = MapCoord::new(0.0, 0.0);
66
-        let mut b = a.clone();
67
-        assert_eq!(a, b);
68
-        b.normalize_x();
69
-        assert_eq!(a, b);
70
-    }
71
-    {
72
-        let mut a = MapCoord::new(1.0, 1.0);
73
-        let b = MapCoord::new(0.0, 1.0);
74
-        a.normalize_x();
75
-        assert_eq!(a, b);
76
-    }
77
-}
78
-
62
+/// A position on the screen in pixels. Top-left corner is (0.0, 0.0).
79 63
 #[derive(Copy, Clone, Debug)]
80 64
 pub struct ScreenCoord {
81 65
     pub x: f64,
@@ -95,6 +79,7 @@ impl ScreenCoord {
95 79
     }
96 80
 }
97 81
 
82
+/// A rectangle in screen coordinates.
98 83
 #[derive(Copy, Clone, Debug)]
99 84
 pub struct ScreenRect {
100 85
     pub x: f64,
@@ -117,6 +102,9 @@ impl ScreenRect {
117 102
     }
118 103
 }
119 104
 
105
+/// A rectangle in texture coordinates.
106
+/// Top-left corner is (0.0, 0.0).
107
+/// Bottom-right corner is (1.0, 1.0).
120 108
 #[derive(Copy, Clone, Debug)]
121 109
 pub struct TextureRect {
122 110
     pub x1: f64,
@@ -148,6 +136,7 @@ impl TextureRect {
148 136
     }
149 137
 }
150 138
 
139
+/// A subdivision of a tile. `x` and `y` are in the interval [0, `size` - 1].
151 140
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
152 141
 pub struct SubTileCoord {
153 142
     pub size: u32,
@@ -165,6 +154,10 @@ impl SubTileCoord {
165 154
     }
166 155
 }
167 156
 
157
+/// A tile position in a tile pyramid.
158
+/// Each zoom level has 2^zoom by 2^zoom tiles.
159
+/// `x` and `y` are allowed to be negative or >= 2^zoom but then they will not correspond to a tile
160
+/// and `is_on_planet` will return false.
168 161
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
169 162
 pub struct TileCoord {
170 163
     pub zoom: u32,
@@ -287,6 +280,29 @@ impl TileCoord {
287 280
         //TODO throw error when zoom too big
288 281
         i32::pow(2, zoom)
289 282
     }
283
+
284
+    pub fn to_quadkey(&self) -> Option<String> {
285
+        if self.zoom == 0 || self.zoom > 30 || self.x < 0 || self.y < 0 {
286
+            return None;
287
+        }
288
+
289
+        let mut quadkey = String::with_capacity(self.zoom as usize);
290
+
291
+        let len = self.zoom;
292
+
293
+        for i in (0..len).rev() {
294
+            let mask: u32 = 1 << i;
295
+
296
+            match ((self.x as u32 & mask) != 0, (self.y as u32 & mask) != 0) {
297
+                (false, false) => quadkey.push('0'),
298
+                (true, false) => quadkey.push('1'),
299
+                (false, true) => quadkey.push('2'),
300
+                (true, true) => quadkey.push('3'),
301
+            }
302
+        }
303
+
304
+        Some(quadkey)
305
+    }
290 306
 }
291 307
 
292 308
 //TODO include width and height of view rect to determine visibility
@@ -296,3 +312,36 @@ pub struct View {
296 312
     pub zoom: u32,
297 313
     pub center: MapCoord,
298 314
 }
315
+
316
+#[cfg(test)]
317
+mod tests {
318
+    use coord::*;
319
+
320
+    #[test]
321
+    fn normalize_mapcoord() {
322
+        {
323
+            let a = MapCoord::new(0.0, 0.0);
324
+            let mut b = a.clone();
325
+            assert_eq!(a, b);
326
+            b.normalize_x();
327
+            assert_eq!(a, b);
328
+        }
329
+        {
330
+            let mut a = MapCoord::new(1.0, 1.0);
331
+            let b = MapCoord::new(0.0, 1.0);
332
+            a.normalize_x();
333
+            assert_eq!(a, b);
334
+        }
335
+    }
336
+
337
+    #[test]
338
+    fn quadkey() {
339
+        assert_eq!(TileCoord::new(0, 0, 0).to_quadkey(), None);
340
+        assert_eq!(TileCoord::new(1, 0, 0).to_quadkey(), Some("0".to_string()));
341
+        assert_eq!(TileCoord::new(1, 1, 0).to_quadkey(), Some("1".to_string()));
342
+        assert_eq!(TileCoord::new(1, 0, 1).to_quadkey(), Some("2".to_string()));
343
+        assert_eq!(TileCoord::new(1, 1, 1).to_quadkey(), Some("3".to_string()));
344
+        assert_eq!(TileCoord::new(3, 1, 0).to_quadkey(), Some("001".to_string()));
345
+        assert_eq!(TileCoord::new(30, 0, 1).to_quadkey(), Some("000000000000000000000000000002".to_string()));
346
+    }
347
+}

+ 15
- 12
src/tile_loader.rs 查看文件

@@ -207,17 +207,20 @@ impl TileLoader {
207 207
 
208 208
         let tile = Tile::new(tile_coord, source.id());
209 209
 
210
-        if !self.pending.contains(&tile) &&
211
-            self.request_tx.send(LoaderMessage::GetTile(
212
-                TileRequest {
213
-                    tile: tile,
214
-                    url: source.remote_tile_url(tile_coord),
215
-                    path: source.local_tile_path(tile_coord),
216
-                    write_to_file: write_to_file,
210
+        if !self.pending.contains(&tile) {
211
+            if let Some(url) = source.remote_tile_url(tile_coord) {
212
+                if self.request_tx.send(LoaderMessage::GetTile(
213
+                        TileRequest {
214
+                            tile: tile,
215
+                            url: url,
216
+                            path: source.local_tile_path(tile_coord),
217
+                            write_to_file: write_to_file,
218
+                        }
219
+                    )).is_ok()
220
+                {
221
+                    self.pending.insert(tile);
217 222
                 }
218
-            )).is_ok()
219
-        {
220
-            self.pending.insert(tile);
223
+            }
221 224
         }
222 225
     }
223 226
 
@@ -246,8 +249,8 @@ impl TileLoader {
246 249
                     self.client = Client::builder().build().ok();
247 250
                 }
248 251
 
249
-                if let Some(ref client) = self.client {
250
-                    if let Ok(mut response) = client.get(&source.remote_tile_url(tile)).send() {
252
+                if let (Some(client), Some(url)) = (self.client.as_ref(), source.remote_tile_url(tile)) {
253
+                    if let Ok(mut response) = client.get(&url).send() {
251 254
                         let mut buf: Vec<u8> = vec![];
252 255
                         if response.copy_to(&mut buf).is_ok() {
253 256
                             if let Ok(img) = image::load_from_memory(&buf) {

+ 10
- 5
src/tile_source.rs 查看文件

@@ -48,7 +48,7 @@ impl TileSource {
48 48
         path
49 49
     }
50 50
 
51
-    pub fn remote_tile_url(&self, tile_coord: TileCoord) -> String {
51
+    pub fn remote_tile_url(&self, tile_coord: TileCoord) -> Option<String> {
52 52
         Self::fill_template(&self.url_template, tile_coord)
53 53
     }
54 54
 
@@ -56,14 +56,19 @@ impl TileSource {
56 56
         self.max_zoom
57 57
     }
58 58
 
59
-    fn fill_template(template: &str, tile_coord: TileCoord) -> String {
59
+    fn fill_template(template: &str, tile_coord: TileCoord) -> Option<String> {
60 60
         let x_str = tile_coord.x.to_string();
61 61
         let y_str = tile_coord.y.to_string();
62 62
         let z_str = tile_coord.zoom.to_string();
63 63
 
64 64
         //TODO use the regex crate for templates or some other more elegant method
65
-        template.replacen("{x}", &x_str, 1)
66
-                .replacen("{y}", &y_str, 1)
67
-                .replacen("{z}", &z_str, 1)
65
+        if template.contains("{quadkey}") {
66
+            tile_coord.to_quadkey().map(|qk| template.replacen("{quadkey}", &qk, 1))
67
+        } else {
68
+            Some(template.replacen("{x}", &x_str, 1)
69
+                    .replacen("{y}", &y_str, 1)
70
+                    .replacen("{z}", &z_str, 1)
71
+                )
72
+        }
68 73
     }
69 74
 }