Browse Source

Add module url_template

Johannes Hofmann 7 years ago
parent
commit
639c9f945f
6 changed files with 179 additions and 26 deletions
  1. 2
    0
      Cargo.lock
  2. 3
    1
      Cargo.toml
  3. 1
    1
      src/config.rs
  4. 5
    1
      src/main.rs
  5. 8
    23
      src/tile_source.rs
  6. 160
    0
      src/url_template.rs

+ 2
- 0
Cargo.lock View File

@@ -265,9 +265,11 @@ dependencies = [
265 265
  "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
266 266
  "glutin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
267 267
  "image 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
268
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
268 269
  "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
269 270
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
270 271
  "osmpbf 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
272
+ "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
271 273
  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
272 274
  "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
273 275
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",

+ 3
- 1
Cargo.toml View File

@@ -14,12 +14,14 @@ license = "GPL-3.0"
14 14
 clap = "2.29"
15 15
 env_logger = "0.5.0-rc.2"
16 16
 gl = "0.10"
17
+glutin = "0.16"
17 18
 image = "0.19"
19
+lazy_static = "1.0"
18 20
 linked-hash-map = "0.5.0"
19 21
 log = "0.4"
20 22
 osmpbf = "0.1"
23
+regex = "1.0"
21 24
 reqwest = "0.8"
22
-glutin = "0.16"
23 25
 toml = "0.4"
24 26
 xdg = "2.1"
25 27
 

+ 1
- 1
src/config.rs View File

@@ -251,7 +251,7 @@ impl Config {
251 251
                             extension.to_string(),
252 252
                             min_zoom as u32,
253 253
                             max_zoom as u32,
254
-                        ),
254
+                        )?,
255 255
                     ));
256 256
                 }
257 257
                 Ok(())

+ 5
- 1
src/main.rs View File

@@ -3,9 +3,12 @@ extern crate clap;
3 3
 extern crate env_logger;
4 4
 extern crate glutin;
5 5
 extern crate image;
6
+#[macro_use]
7
+extern crate lazy_static;
6 8
 extern crate linked_hash_map;
7 9
 #[macro_use]
8 10
 extern crate log;
11
+extern crate regex;
9 12
 extern crate reqwest;
10 13
 extern crate toml;
11 14
 extern crate xdg;
@@ -21,10 +24,11 @@ pub mod map_view_gl;
21 24
 pub mod program;
22 25
 pub mod texture;
23 26
 pub mod tile;
24
-pub mod tile_cache;
25 27
 pub mod tile_atlas;
28
+pub mod tile_cache;
26 29
 pub mod tile_loader;
27 30
 pub mod tile_source;
31
+pub mod url_template;
28 32
 
29 33
 use coord::ScreenCoord;
30 34
 use glutin::{ControlFlow, ElementState, Event, GlContext, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent};

+ 8
- 23
src/tile_source.rs View File

@@ -1,11 +1,12 @@
1 1
 use coord::TileCoord;
2 2
 use std::path::PathBuf;
3
+use url_template::UrlTemplate;
3 4
 
4 5
 
5
-#[derive(Clone, Debug)]
6
+#[derive(Debug)]
6 7
 pub struct TileSource {
7 8
     id: u32,
8
-    url_template: String,
9
+    url_template: UrlTemplate,
9 10
     directory: PathBuf,
10 11
     extension: String,
11 12
     min_zoom: u32,
@@ -25,15 +26,15 @@ impl TileSource {
25 26
         extension: String,
26 27
         min_zoom: u32,
27 28
         max_zoom: u32,
28
-    ) -> Self {
29
-        TileSource {
29
+    ) -> Result<Self, String> {
30
+        Ok(TileSource {
30 31
             id,
31
-            url_template: url_template.into(),
32
+            url_template: UrlTemplate::new(url_template)?,
32 33
             directory: directory.into(),
33 34
             extension,
34 35
             min_zoom,
35 36
             max_zoom,
36
-        }
37
+        })
37 38
     }
38 39
 
39 40
     pub fn id(&self) -> TileSourceId {
@@ -52,7 +53,7 @@ impl TileSource {
52 53
     }
53 54
 
54 55
     pub fn remote_tile_url(&self, tile_coord: TileCoord) -> Option<String> {
55
-        Self::fill_template(&self.url_template, tile_coord)
56
+        self.url_template.fill(tile_coord)
56 57
     }
57 58
 
58 59
     pub fn min_tile_zoom(&self) -> u32 {
@@ -62,20 +63,4 @@ impl TileSource {
62 63
     pub fn max_tile_zoom(&self) -> u32 {
63 64
         self.max_zoom
64 65
     }
65
-
66
-    fn fill_template(template: &str, tile_coord: TileCoord) -> Option<String> {
67
-        let x_str = tile_coord.x.to_string();
68
-        let y_str = tile_coord.y.to_string();
69
-        let z_str = tile_coord.zoom.to_string();
70
-
71
-        //TODO use the regex crate for templates or some other more elegant method
72
-        if template.contains("{quadkey}") {
73
-            tile_coord.to_quadkey().map(|qk| template.replacen("{quadkey}", &qk, 1))
74
-        } else {
75
-            Some(template.replacen("{x}", &x_str, 1)
76
-                    .replacen("{y}", &y_str, 1)
77
-                    .replacen("{z}", &z_str, 1)
78
-                )
79
-        }
80
-    }
81 66
 }

+ 160
- 0
src/url_template.rs View File

@@ -0,0 +1,160 @@
1
+use coord::TileCoord;
2
+use regex::Regex;
3
+
4
+
5
+/// Kinds of placeholders for a `UrlTemplate`
6
+#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
7
+enum Placeholder {
8
+    /// Tile x coordinate
9
+    X,
10
+    /// Tile y coordinate
11
+    Y,
12
+    /// Tile zoom
13
+    Z,
14
+    /// Quadkey encoded coord
15
+    Quadkey
16
+}
17
+
18
+impl Placeholder {
19
+    /// Returns maximum number of bytes that the value for a placeholder with occupy.
20
+    fn max_size(&self) -> usize {
21
+        match *self {
22
+            Placeholder::X | Placeholder::Y | Placeholder::Z => 11,
23
+            Placeholder::Quadkey => 30,
24
+        }
25
+    }
26
+}
27
+
28
+#[derive(Debug)]
29
+pub struct UrlTemplate {
30
+    /// The template string that includes placeholders between static parts
31
+    template_string: String,
32
+    /// Ranges into `template_string` for static parts
33
+    static_parts: Vec<::std::ops::Range<usize>>,
34
+    /// Kinds of placeholders between the static parts
35
+    placeholders: Vec<Placeholder>,
36
+    /// Maximum length in bytes of a filled template
37
+    max_size: usize,
38
+}
39
+
40
+impl UrlTemplate {
41
+    pub fn new<S: Into<String>>(template_str: S) -> Result<UrlTemplate, String> {
42
+        let template_string = template_str.into();
43
+        let mut static_parts = vec![];
44
+        let mut placeholders = vec![];
45
+        let mut max_size = 0;
46
+
47
+        lazy_static! {
48
+            static ref RE: Regex = Regex::new(r"\{([a-z]+)\}").unwrap();
49
+        }
50
+
51
+        let mut offset = 0;
52
+        for cap in RE.captures_iter(&template_string) {
53
+            let cap0 = cap.get(0).unwrap();
54
+            static_parts.push(offset..cap0.start());
55
+            max_size += cap0.start() - offset;
56
+
57
+            {
58
+                let ph = match cap.get(1).unwrap().as_str() {
59
+                    "x" => Placeholder::X,
60
+                    "y" => Placeholder::Y,
61
+                    "z" => Placeholder::Z,
62
+                    "quadkey" => Placeholder::Quadkey,
63
+                    s => return Err(format!("Invalid placeholder in url template: {:?}", s)),
64
+                };
65
+                max_size += ph.max_size();
66
+                placeholders.push(ph);
67
+            }
68
+
69
+            offset = cap0.end();
70
+        }
71
+
72
+        static_parts.push(offset..template_string.len());
73
+        max_size += template_string.len() - offset;
74
+
75
+        let template_valid =
76
+            placeholders.contains(&Placeholder::Quadkey) ||
77
+            (placeholders.contains(&Placeholder::X) &&
78
+             placeholders.contains(&Placeholder::Y) &&
79
+             placeholders.contains(&Placeholder::Z));
80
+
81
+        if !template_valid {
82
+            return Err(format!(
83
+                "template is not valid because one or multiple placeholders are missing: {:?}",
84
+                template_string)
85
+            );
86
+        }
87
+
88
+        Ok(UrlTemplate {
89
+            template_string,
90
+            static_parts,
91
+            placeholders,
92
+            max_size,
93
+        })
94
+    }
95
+
96
+    pub fn fill(&self, tile_coord: TileCoord) -> Option<String> {
97
+        let mut ret = String::with_capacity(self.max_size);
98
+
99
+        if let Some(prefix) = self.static_parts.first() {
100
+            ret += &self.template_string[prefix.start..prefix.end];
101
+        }
102
+
103
+        for (i, static_part) in self.static_parts.iter().skip(1).enumerate() {
104
+            let dyn_part = match self.placeholders[i] {
105
+                Placeholder::X => tile_coord.x.to_string(),
106
+                Placeholder::Y => tile_coord.y.to_string(),
107
+                Placeholder::Z => tile_coord.zoom.to_string(),
108
+                Placeholder::Quadkey => {
109
+                    match tile_coord.to_quadkey() {
110
+                        Some(q) => q,
111
+                        None => return None,
112
+                    }
113
+                }
114
+            };
115
+            ret += &dyn_part;
116
+            ret += &self.template_string[static_part.start..static_part.end];;
117
+        }
118
+        Some(ret)
119
+    }
120
+}
121
+
122
+#[cfg(test)]
123
+mod tests {
124
+    use url_template::*;
125
+
126
+    fn check_templ(templ_str: &str, coord: TileCoord, result: &str) {
127
+        let t = UrlTemplate::new(templ_str).unwrap();
128
+        assert_eq!(t.fill(coord), Some(result.to_string()));
129
+    }
130
+
131
+    #[test]
132
+    fn check_new() {
133
+        assert!(UrlTemplate::new("").is_err());
134
+        assert!(UrlTemplate::new("abc").is_err());
135
+        assert!(UrlTemplate::new("{x}").is_err());
136
+        assert!(UrlTemplate::new("{z}{y}").is_err());
137
+        assert!(UrlTemplate::new("{x}{z}{y}").is_ok());
138
+        assert!(UrlTemplate::new("{quadkey}").is_ok());
139
+        assert!(UrlTemplate::new("{x}{quadkey}").is_ok());
140
+    }
141
+
142
+    #[test]
143
+    fn check_fill() {
144
+        check_templ("https://tiles.example.com/{z}/{x}/{y}.png",
145
+                    TileCoord::new(2, 1, 0),
146
+                    "https://tiles.example.com/2/1/0.png");
147
+        check_templ("{z}{x}{y}",
148
+                    TileCoord::new(2, 1, 0),
149
+                    "210");
150
+        check_templ("{quadkey}",
151
+                    TileCoord::new(3, 1, 0),
152
+                    "001");
153
+        check_templ("{x}{x}{y}{z}",
154
+                    TileCoord::new(2, 1, 0),
155
+                    "1102");
156
+        check_templ("a{quadkey}b{z}c",
157
+                    TileCoord::new(1, 0, 0),
158
+                    "a0b1c");
159
+    }
160
+}