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
  "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
265
  "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
266
  "glutin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
266
  "glutin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
267
  "image 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
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
  "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
269
  "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
269
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
270
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
270
  "osmpbf 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
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
  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
273
  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
272
  "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
274
  "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
273
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
275
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",

+ 3
- 1
Cargo.toml View File

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

+ 1
- 1
src/config.rs View File

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

+ 5
- 1
src/main.rs View File

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

+ 8
- 23
src/tile_source.rs View File

1
 use coord::TileCoord;
1
 use coord::TileCoord;
2
 use std::path::PathBuf;
2
 use std::path::PathBuf;
3
+use url_template::UrlTemplate;
3
 
4
 
4
 
5
 
5
-#[derive(Clone, Debug)]
6
+#[derive(Debug)]
6
 pub struct TileSource {
7
 pub struct TileSource {
7
     id: u32,
8
     id: u32,
8
-    url_template: String,
9
+    url_template: UrlTemplate,
9
     directory: PathBuf,
10
     directory: PathBuf,
10
     extension: String,
11
     extension: String,
11
     min_zoom: u32,
12
     min_zoom: u32,
25
         extension: String,
26
         extension: String,
26
         min_zoom: u32,
27
         min_zoom: u32,
27
         max_zoom: u32,
28
         max_zoom: u32,
28
-    ) -> Self {
29
-        TileSource {
29
+    ) -> Result<Self, String> {
30
+        Ok(TileSource {
30
             id,
31
             id,
31
-            url_template: url_template.into(),
32
+            url_template: UrlTemplate::new(url_template)?,
32
             directory: directory.into(),
33
             directory: directory.into(),
33
             extension,
34
             extension,
34
             min_zoom,
35
             min_zoom,
35
             max_zoom,
36
             max_zoom,
36
-        }
37
+        })
37
     }
38
     }
38
 
39
 
39
     pub fn id(&self) -> TileSourceId {
40
     pub fn id(&self) -> TileSourceId {
52
     }
53
     }
53
 
54
 
54
     pub fn remote_tile_url(&self, tile_coord: TileCoord) -> Option<String> {
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
     pub fn min_tile_zoom(&self) -> u32 {
59
     pub fn min_tile_zoom(&self) -> u32 {
62
     pub fn max_tile_zoom(&self) -> u32 {
63
     pub fn max_tile_zoom(&self) -> u32 {
63
         self.max_zoom
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

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
+}