Browse Source

tile_source, coord: Add support for quadkeys

Johannes Hofmann 8 years ago
parent
commit
cc1ad8f123
3 changed files with 91 additions and 34 deletions
  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 View File

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
 #[derive(Copy, Clone, Debug)]
63
 #[derive(Copy, Clone, Debug)]
80
 pub struct ScreenCoord {
64
 pub struct ScreenCoord {
81
     pub x: f64,
65
     pub x: f64,
95
     }
79
     }
96
 }
80
 }
97
 
81
 
82
+/// A rectangle in screen coordinates.
98
 #[derive(Copy, Clone, Debug)]
83
 #[derive(Copy, Clone, Debug)]
99
 pub struct ScreenRect {
84
 pub struct ScreenRect {
100
     pub x: f64,
85
     pub x: f64,
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
 #[derive(Copy, Clone, Debug)]
108
 #[derive(Copy, Clone, Debug)]
121
 pub struct TextureRect {
109
 pub struct TextureRect {
122
     pub x1: f64,
110
     pub x1: f64,
148
     }
136
     }
149
 }
137
 }
150
 
138
 
139
+/// A subdivision of a tile. `x` and `y` are in the interval [0, `size` - 1].
151
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
140
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
152
 pub struct SubTileCoord {
141
 pub struct SubTileCoord {
153
     pub size: u32,
142
     pub size: u32,
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
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
161
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
169
 pub struct TileCoord {
162
 pub struct TileCoord {
170
     pub zoom: u32,
163
     pub zoom: u32,
287
         //TODO throw error when zoom too big
280
         //TODO throw error when zoom too big
288
         i32::pow(2, zoom)
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
 //TODO include width and height of view rect to determine visibility
308
 //TODO include width and height of view rect to determine visibility
296
     pub zoom: u32,
312
     pub zoom: u32,
297
     pub center: MapCoord,
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 View File

207
 
207
 
208
         let tile = Tile::new(tile_coord, source.id());
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
                     self.client = Client::builder().build().ok();
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
                         let mut buf: Vec<u8> = vec![];
254
                         let mut buf: Vec<u8> = vec![];
252
                         if response.copy_to(&mut buf).is_ok() {
255
                         if response.copy_to(&mut buf).is_ok() {
253
                             if let Ok(img) = image::load_from_memory(&buf) {
256
                             if let Ok(img) = image::load_from_memory(&buf) {

+ 10
- 5
src/tile_source.rs View File

48
         path
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
         Self::fill_template(&self.url_template, tile_coord)
52
         Self::fill_template(&self.url_template, tile_coord)
53
     }
53
     }
54
 
54
 
56
         self.max_zoom
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
         let x_str = tile_coord.x.to_string();
60
         let x_str = tile_coord.x.to_string();
61
         let y_str = tile_coord.y.to_string();
61
         let y_str = tile_coord.y.to_string();
62
         let z_str = tile_coord.zoom.to_string();
62
         let z_str = tile_coord.zoom.to_string();
63
 
63
 
64
         //TODO use the regex crate for templates or some other more elegant method
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
 }