Selaa lähdekoodia

Initial commit :>

Started 2015
Johannes Hofmann 8 vuotta sitten
commit
16ffdb5baa
24 muutettua tiedostoa jossa 3466 lisäystä ja 0 poistoa
  1. 2
    0
      .gitignore
  2. 1436
    0
      Cargo.lock
  3. 18
    0
      Cargo.toml
  4. 3
    0
      README.md
  5. 16
    0
      build.rs
  6. 5
    0
      deltamap.toml
  7. BIN
      no_tile.png
  8. 10
    0
      shader/map.frag
  9. 15
    0
      shader/map.vert
  10. 82
    0
      src/buffer.rs
  11. 58
    0
      src/config.rs
  12. 83
    0
      src/context.rs
  13. 104
    0
      src/coord.rs
  14. 244
    0
      src/main.rs
  15. 137
    0
      src/map_view.rs
  16. 186
    0
      src/map_view_gl.rs
  17. 203
    0
      src/program.rs
  18. 121
    0
      src/texture.rs
  19. 70
    0
      src/tile.rs
  20. 98
    0
      src/tile_cache.rs
  21. 145
    0
      src/tile_cache_async.rs
  22. 194
    0
      src/tile_cache_gl.rs
  23. 165
    0
      src/tile_loader.rs
  24. 71
    0
      src/tile_source.rs

+ 2
- 0
.gitignore Näytä tiedosto

@@ -0,0 +1,2 @@
1
+target
2
+.*.swp

+ 1436
- 0
Cargo.lock
File diff suppressed because it is too large
Näytä tiedosto


+ 18
- 0
Cargo.toml Näytä tiedosto

@@ -0,0 +1,18 @@
1
+[package]
2
+name = "deltamap"
3
+version = "0.1.0"
4
+authors = ["Johannes Hofmann <mail@b-r-u.org>"]
5
+
6
+[dependencies]
7
+gl = "0.7"
8
+image = "0.18"
9
+linked-hash-map = "0.5.0"
10
+osmpbf = "0.1"
11
+servo-glutin = "0.13"
12
+serde = "1.0"
13
+serde_derive = "1.0"
14
+reqwest = "0.8"
15
+toml = "0.4"
16
+
17
+[build-dependencies]
18
+gl_generator = "0.7"

+ 3
- 0
README.md Näytä tiedosto

@@ -0,0 +1,3 @@
1
+deltamap
2
+========
3
+A simple map viewer.

+ 16
- 0
build.rs Näytä tiedosto

@@ -0,0 +1,16 @@
1
+extern crate gl_generator;
2
+
3
+use gl_generator::{Registry, Api, Profile, Fallbacks};
4
+use std::env;
5
+use std::fs::File;
6
+use std::path::PathBuf;
7
+
8
+fn main() {
9
+    let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
10
+
11
+    println!("cargo:rerun-if-changed=build.rs");
12
+
13
+    let mut file = File::create(&dest.join("gles_bindings.rs")).unwrap();
14
+    Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
15
+            .write_bindings(gl_generator::StructGenerator, &mut file).unwrap();
16
+}

+ 5
- 0
deltamap.toml Näytä tiedosto

@@ -0,0 +1,5 @@
1
+tile_cache_dir = "/tmp"
2
+
3
+[sources.OSM]
4
+max_zoom = 19
5
+url_template = "http://a.tile.openstreetmap.org/{z}/{x}/{y}.png"

BIN
no_tile.png Näytä tiedosto


+ 10
- 0
shader/map.frag Näytä tiedosto

@@ -0,0 +1,10 @@
1
+#version 100
2
+precision mediump float;
3
+
4
+varying vec2 v_tex;
5
+varying vec4 v_tex_minmax;
6
+uniform sampler2D tex_map;
7
+
8
+void main() {
9
+    gl_FragColor = vec4(texture2D(tex_map, clamp(v_tex.xy, v_tex_minmax.xy, v_tex_minmax.zw)).rgb, 1.0);
10
+}

+ 15
- 0
shader/map.vert Näytä tiedosto

@@ -0,0 +1,15 @@
1
+#version 100
2
+precision mediump float;
3
+
4
+attribute vec2 position;
5
+attribute vec2 tex_coord;
6
+attribute vec4 tex_minmax;
7
+
8
+varying vec2 v_tex;
9
+varying vec4 v_tex_minmax;
10
+
11
+void main() {
12
+    gl_Position = vec4(position, 0.0, 1.0);
13
+    v_tex = tex_coord;
14
+    v_tex_minmax = tex_minmax;
15
+}

+ 82
- 0
src/buffer.rs Näytä tiedosto

@@ -0,0 +1,82 @@
1
+use ::context;
2
+use context::Context;
3
+use std::mem;
4
+
5
+
6
+#[derive(Clone, Debug)]
7
+pub struct Buffer<'a> {
8
+    cx: &'a Context,
9
+    buffer_obj: u32,
10
+    num_elements: usize,
11
+}
12
+
13
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
14
+pub enum DrawMode {
15
+    Triangles,
16
+    TriangleStrip,
17
+    TriangleFan,
18
+}
19
+
20
+impl DrawMode {
21
+    pub fn to_gl_enum(self) -> u32 {
22
+        match self {
23
+            DrawMode::Triangles => context::gl::TRIANGLES,
24
+            DrawMode::TriangleStrip => context::gl::TRIANGLE_STRIP,
25
+            DrawMode::TriangleFan => context::gl::TRIANGLE_FAN,
26
+        }
27
+    }
28
+}
29
+
30
+impl<'a> Buffer<'a> {
31
+    pub fn new(cx: &'a Context, vertex_data: &[f32], num_elements: usize) -> Buffer<'a> {
32
+        let mut buffer_obj = 0_u32;
33
+
34
+        unsafe {
35
+            cx.gl.GenBuffers(1, &mut buffer_obj);
36
+            cx.gl.BindBuffer(context::gl::ARRAY_BUFFER, buffer_obj);
37
+            cx.gl.BufferData(context::gl::ARRAY_BUFFER,
38
+                             (vertex_data.len() * mem::size_of::<f32>()) as context::gl::types::GLsizeiptr,
39
+                             vertex_data.as_ptr() as *const _,
40
+                             context::gl::STATIC_DRAW);
41
+
42
+            //TODO call this only once
43
+            // VAOs are not OpenGL ES 2.0 compatible, but are required for rendering with a core context.
44
+            if cx.gl.BindVertexArray.is_loaded() {
45
+                let mut vao = mem::uninitialized();
46
+                cx.gl.GenVertexArrays(1, &mut vao);
47
+                cx.gl.BindVertexArray(vao);
48
+            }
49
+        }
50
+
51
+        Buffer {
52
+            cx: cx,
53
+            buffer_obj: buffer_obj,
54
+            num_elements: num_elements,
55
+        }
56
+    }
57
+
58
+    pub fn set_data(&mut self, vertex_data: &[f32], num_elements: usize) {
59
+        unsafe {
60
+            self.cx.gl.BufferData(context::gl::ARRAY_BUFFER,
61
+                                  (vertex_data.len() * mem::size_of::<f32>()) as context::gl::types::GLsizeiptr,
62
+                                  vertex_data.as_ptr() as *const _,
63
+                                  context::gl::DYNAMIC_DRAW);
64
+        }
65
+        self.num_elements = num_elements;
66
+    }
67
+
68
+    pub fn bind(&self) {
69
+        unsafe {
70
+            self.cx.gl.BindBuffer(context::gl::ARRAY_BUFFER, self.buffer_obj);
71
+        }
72
+    }
73
+
74
+    pub fn draw(&self, mode: DrawMode) {
75
+        unsafe {
76
+            self.cx.gl.DrawArrays(
77
+                mode.to_gl_enum(),
78
+                0,
79
+                self.num_elements as context::gl::types::GLsizei);
80
+        }
81
+    }
82
+}

+ 58
- 0
src/config.rs Näytä tiedosto

@@ -0,0 +1,58 @@
1
+use serde_derive;
2
+use std::collections::BTreeMap;
3
+use std::fs::File;
4
+use std::io::Read;
5
+use std::path::{Path, PathBuf};
6
+use tile_source::TileSource;
7
+use toml;
8
+
9
+
10
+#[derive(Deserialize, Clone, Debug)]
11
+pub struct Config {
12
+    tile_cache_dir: String,
13
+    sources: BTreeMap<String, Source>,
14
+}
15
+
16
+#[derive(Deserialize, Clone, Debug)]
17
+struct Source {
18
+    max_zoom: u32,
19
+    url_template: String,
20
+}
21
+
22
+impl Config {
23
+    pub fn from_toml<P: AsRef<Path>>(path: P) -> Option<Config> {
24
+        let mut file = match File::open(path) {
25
+            Ok(file) => file,
26
+            Err(_) => return None,
27
+        };
28
+
29
+        let mut content = String::new();
30
+        if file.read_to_string(&mut content).is_err() {
31
+            return None;
32
+        }
33
+
34
+        toml::from_str(&content).ok()
35
+    }
36
+
37
+    pub fn tile_sources(&self) -> BTreeMap<String, TileSource> {
38
+        let mut map = BTreeMap::new();
39
+
40
+        for (id, (name, source)) in self.sources.iter().enumerate() {
41
+            let mut path = PathBuf::from(&self.tile_cache_dir);
42
+            //TODO escape name (no slashes or dots)
43
+            path.push(name);
44
+
45
+            map.insert(
46
+                name.clone(),
47
+                TileSource::new(
48
+                    id as u32,
49
+                    source.url_template.clone(),
50
+                    path,
51
+                    source.max_zoom,
52
+                ),
53
+            );
54
+        }
55
+
56
+        return map;
57
+    }
58
+}

+ 83
- 0
src/context.rs Näytä tiedosto

@@ -0,0 +1,83 @@
1
+use glutin;
2
+
3
+pub(crate) mod gl {
4
+    pub use self::Gles2 as Gl;
5
+    include!(concat!(env!("OUT_DIR"), "/gles_bindings.rs"));
6
+}
7
+
8
+#[derive(Clone)]
9
+pub struct Context {
10
+    pub(crate) gl: gl::Gl,
11
+}
12
+
13
+impl ::std::fmt::Debug for Context {
14
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
15
+        let version = unsafe {
16
+            let data = ::std::ffi::CStr::from_ptr(self.gl.GetString(gl::VERSION) as *const _).to_bytes().to_vec();
17
+            String::from_utf8(data).unwrap_or_else(|_| "".into())
18
+        };
19
+        write!(f, "Context {{ version: {:?} }}", version)
20
+    }
21
+}
22
+
23
+macro_rules! check_gl_errors {
24
+    ($cx:expr) => (
25
+        $cx.check_errors(file!(), line!());
26
+    )
27
+}
28
+
29
+impl Context {
30
+    pub fn from_window(window: &glutin::Window) -> Context {
31
+        let gl = gl::Gl::load_with(|ptr| window.get_proc_address(ptr) as *const _);
32
+
33
+        Context {gl: gl}
34
+    }
35
+
36
+    pub fn gl_version(&self) -> String {
37
+        unsafe {
38
+            let data = ::std::ffi::CStr::from_ptr(self.gl.GetString(gl::VERSION) as *const _).to_bytes().to_vec();
39
+            String::from_utf8(data).unwrap_or_else(|_| "".into())
40
+        }
41
+    }
42
+
43
+    pub fn max_texture_size(&self) -> i32 {
44
+        unsafe {
45
+            let mut size = 0;
46
+            self.gl.GetIntegerv(gl::MAX_TEXTURE_SIZE, &mut size as *mut _);
47
+            size
48
+        }
49
+    }
50
+
51
+    pub unsafe fn check_errors(&self, file: &str, line: u32) {
52
+        loop {
53
+            match self.gl.GetError() {
54
+                gl::NO_ERROR => break,
55
+                gl::INVALID_VALUE => {
56
+                    println!("{}:{}, invalid value error", file, line);
57
+                },
58
+                gl::INVALID_ENUM => {
59
+                    println!("{}:{}, invalid enum error", file, line);
60
+                },
61
+                gl::INVALID_OPERATION => {
62
+                    println!("{}:{}, invalid operation error", file, line);
63
+                },
64
+                gl::INVALID_FRAMEBUFFER_OPERATION => {
65
+                    println!("{}:{}, invalid framebuffer operation error", file, line);
66
+                },
67
+                gl::OUT_OF_MEMORY => {
68
+                    println!("{}:{}, out of memory error", file, line);
69
+                },
70
+                x => {
71
+                    println!("{}:{}, unknown error {}", file, line, x);
72
+                },
73
+            }
74
+        }
75
+    }
76
+
77
+    pub fn clear_color(&self, color: (f32, f32, f32, f32)) {
78
+        unsafe {
79
+            self.gl.ClearColor(color.0, color.1, color.2, color.3);
80
+            self.gl.Clear(gl::COLOR_BUFFER_BIT);
81
+        }
82
+    }
83
+}

+ 104
- 0
src/coord.rs Näytä tiedosto

@@ -0,0 +1,104 @@
1
+use std::f64::consts::PI;
2
+use tile::Tile;
3
+
4
+/// A position in map coordinates.
5
+/// Valid values for x and y lie in the interval [0.0, 1.0].
6
+#[derive(Copy, Debug, PartialEq, Clone)]
7
+pub struct MapCoord {
8
+    pub x: f64,
9
+    pub y: f64,
10
+}
11
+
12
+impl MapCoord {
13
+    pub fn new(x: f64, y: f64) -> MapCoord {
14
+        MapCoord {
15
+            x: x,
16
+            y: y,
17
+        }
18
+    }
19
+
20
+    pub fn from_latlon(latitude: f64, longitude: f64) -> MapCoord {
21
+        let x = longitude * (1.0 / 360.0) + 0.5;
22
+        let pi_lat = latitude * (PI / 180.0);
23
+        let y = f64::ln(f64::tan(pi_lat) + 1.0 / f64::cos(pi_lat)) * (-0.5 / PI) + 0.5;
24
+
25
+        MapCoord {
26
+            x: x,
27
+            y: y,
28
+        }
29
+    }
30
+
31
+    //TODO differ between normalized and not normalized tiles
32
+    pub fn on_tile_at_zoom(&self, zoom: u32) -> Tile {
33
+        let zoom_factor = f64::powi(2.0, zoom as i32);
34
+        let ix = (self.x * zoom_factor).floor() as i32;
35
+        let iy = (self.y * zoom_factor).floor() as i32;
36
+
37
+        let x = ix;
38
+        let y = iy;
39
+
40
+        Tile {
41
+            zoom: zoom,
42
+            tile_x: x,
43
+            tile_y: y,
44
+        }
45
+    }
46
+
47
+    pub fn normalize_x(&mut self) {
48
+        // Wrap around in x-direction.
49
+        // Do not wrap around in y-direction. The poles don't touch.
50
+        self.x = (self.x.fract() + 1.0).fract();
51
+    }
52
+
53
+    pub fn normalize_xy(&mut self) {
54
+        // Wrap around in x-direction.
55
+        // Restrict y coordinates to interval [0.0, 1.0]
56
+        self.x = (self.x.fract() + 1.0).fract();
57
+        self.y = 0.0f64.max(1.0f64.min(self.y));
58
+    }
59
+
60
+}
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
+
79
+#[derive(Copy, Clone, Debug)]
80
+pub struct ScreenCoord {
81
+    pub x: f64,
82
+    pub y: f64,
83
+}
84
+
85
+impl ScreenCoord {
86
+    pub fn new(x: f64, y: f64) -> Self {
87
+        ScreenCoord {
88
+            x: x,
89
+            y: y,
90
+        }
91
+    }
92
+    pub fn snap_to_pixel(&mut self) {
93
+        self.x = self.x.floor();
94
+        self.y = self.y.floor();
95
+    }
96
+}
97
+
98
+#[derive(Copy, Clone, Debug)]
99
+pub struct ScreenRect {
100
+    pub x: f64,
101
+    pub y: f64,
102
+    pub width: f64,
103
+    pub height: f64,
104
+}

+ 244
- 0
src/main.rs Näytä tiedosto

@@ -0,0 +1,244 @@
1
+#[cfg(target_os = "android")]
2
+#[macro_use]
3
+extern crate android_glue;
4
+
5
+#[macro_use]
6
+extern crate serde_derive;
7
+
8
+extern crate glutin;
9
+extern crate image;
10
+extern crate linked_hash_map;
11
+extern crate reqwest;
12
+extern crate serde;
13
+extern crate toml;
14
+
15
+
16
+#[macro_use]
17
+mod context;
18
+
19
+mod buffer;
20
+mod config;
21
+mod coord;
22
+mod map_view;
23
+mod map_view_gl;
24
+mod program;
25
+mod texture;
26
+mod tile;
27
+mod tile_cache;
28
+mod tile_cache_gl;
29
+mod tile_loader;
30
+mod tile_source;
31
+
32
+use coord::ScreenCoord;
33
+use glutin::{ElementState, Event, MouseButton, MouseScrollDelta, VirtualKeyCode};
34
+use map_view_gl::MapViewGl;
35
+use tile_source::TileSource;
36
+use std::time::{Duration, Instant};
37
+
38
+#[cfg(target_os = "android")]
39
+android_start!(main);
40
+
41
+fn resize_callback(width: u32, height: u32) {
42
+    println!("Window resized to {}x{}", width, height);
43
+}
44
+
45
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
46
+enum Action {
47
+    Nothing,
48
+    Redraw,
49
+    Close,
50
+}
51
+
52
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53
+struct InputState {
54
+    mouse_position: (i32, i32),
55
+    mouse_pressed: bool,
56
+}
57
+
58
+fn handle_event(event: Event, map: &mut MapViewGl, input_state: &mut InputState) -> Action {
59
+    match event {
60
+        Event::Closed => Action::Close,
61
+        Event::Awakened => Action::Redraw,
62
+        Event::MouseInput(ElementState::Pressed, MouseButton::Left, position) => {
63
+            input_state.mouse_pressed = true;
64
+            if let Some(p) = position {
65
+                input_state.mouse_position = p;
66
+            }
67
+            Action::Nothing
68
+        },
69
+        Event::MouseInput(ElementState::Released, MouseButton::Left, position) => {
70
+            input_state.mouse_pressed = false;
71
+            if let Some(p) = position {
72
+                input_state.mouse_position = p;
73
+            }
74
+            Action::Nothing
75
+        },
76
+        Event::MouseMoved(x, y) => {
77
+            if input_state.mouse_pressed {
78
+                map.move_pixel(
79
+                    f64::from(input_state.mouse_position.0 - x),
80
+                    f64::from(input_state.mouse_position.1 - y),
81
+                );
82
+                input_state.mouse_position = (x, y);
83
+                Action::Redraw
84
+            } else {
85
+                input_state.mouse_position = (x, y);
86
+                Action::Nothing
87
+            }
88
+        },
89
+        Event::MouseWheel(delta, _, position) => {
90
+            let (dx, dy) = match delta {
91
+                MouseScrollDelta::LineDelta(dx, dy) => {
92
+                    // filter strange wheel events with huge values.
93
+                    // (maybe this is just a personal touchpad driver issue)
94
+                    if dx.abs() < 16.0 && dy.abs() < 16.0 {
95
+                        (dx, dy * 10.0)
96
+                    } else {
97
+                        (0.0, 0.0)
98
+                    }
99
+                },
100
+                MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
101
+            };
102
+            if let Some(p) = position {
103
+                input_state.mouse_position = p;
104
+            }
105
+            //TODO option to move or zoom on mouse wheel event
106
+            //map.move_pixel(-dx as f64, -dy as f64);
107
+
108
+            map.zoom_at(
109
+                ScreenCoord::new(
110
+                    f64::from(input_state.mouse_position.0),
111
+                    f64::from(input_state.mouse_position.1),
112
+                ),
113
+                f64::from(dy) * 0.0125,
114
+            );
115
+            Action::Redraw
116
+        },
117
+        Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(keycode)) => {
118
+            match keycode {
119
+                VirtualKeyCode::Escape => {
120
+                    Action::Close
121
+                },
122
+                VirtualKeyCode::Left => {
123
+                    map.move_pixel(-50.0, 0.0);
124
+                    Action::Redraw
125
+                },
126
+                VirtualKeyCode::Right => {
127
+                    map.move_pixel(50.0, 0.0);
128
+                    Action::Redraw
129
+                },
130
+                VirtualKeyCode::Up => {
131
+                    map.move_pixel(0.0, -50.0);
132
+                    Action::Redraw
133
+                },
134
+                VirtualKeyCode::Down => {
135
+                    map.move_pixel(0.0, 50.0);
136
+                    Action::Redraw
137
+                },
138
+                VirtualKeyCode::Add => {
139
+                    map.zoom(0.25);
140
+                    Action::Redraw
141
+                },
142
+                VirtualKeyCode::Subtract => {
143
+                    map.zoom(-0.25);
144
+                    Action::Redraw
145
+                },
146
+                _ => Action::Nothing,
147
+            }
148
+        },
149
+        Event::Refresh => {
150
+            Action::Redraw
151
+        },
152
+        Event::Resized(w, h) => {
153
+            map.set_viewport_size(w, h);
154
+            Action::Redraw
155
+        },
156
+        _ => Action::Nothing,
157
+    }
158
+}
159
+
160
+fn main() {
161
+    let config = config::Config::from_toml("deltamap.toml").unwrap();
162
+
163
+    let mut window = glutin::WindowBuilder::new().build().unwrap();
164
+    window.set_title("DeltaMap");
165
+    window.set_window_resize_callback(Some(resize_callback as fn(u32, u32)));
166
+    let _ = unsafe { window.make_current() };
167
+
168
+    let proxy = window.create_window_proxy();
169
+
170
+    let cx = context::Context::from_window(&window);
171
+    let mut sources = config.tile_sources();
172
+    let mut map = map_view_gl::MapViewGl::new(
173
+        &cx,
174
+        sources.into_iter().next().unwrap().1,
175
+        window.get_inner_size_pixels().unwrap(),
176
+        move || { proxy.wakeup_event_loop(); },
177
+    );
178
+
179
+    let mut input_state = InputState {
180
+        mouse_position: (0, 0),
181
+        mouse_pressed: false,
182
+    };
183
+
184
+    let milli16 = Duration::from_millis(16);
185
+    let mut draw_dur = Duration::from_millis(8);
186
+    let mut last_draw = Instant::now();
187
+
188
+    'outer: for event in window.wait_events() {
189
+        let mut start_loop = Instant::now();
190
+
191
+        let mut redraw = false;
192
+
193
+        match handle_event(event, &mut map, &mut input_state) {
194
+            Action::Close => break 'outer,
195
+            Action::Redraw => {
196
+                redraw = true;
197
+            },
198
+            Action::Nothing => {},
199
+        }
200
+
201
+        for event in window.poll_events() {
202
+            match handle_event(event, &mut map, &mut input_state) {
203
+                Action::Close => break 'outer,
204
+                Action::Redraw => {
205
+                    redraw = true;
206
+                },
207
+                Action::Nothing => {},
208
+            }
209
+        }
210
+
211
+        {
212
+            let diff = last_draw.elapsed();
213
+            if diff + draw_dur * 2 < milli16 {
214
+                if let Some(dur) = milli16.checked_sub(draw_dur * 2) {
215
+                    std::thread::sleep(dur);
216
+                    println!("SLEEP {}", dur.as_secs() as f64 + dur.subsec_nanos() as f64 * 1e-9);
217
+
218
+                    for event in window.poll_events() {
219
+                        match handle_event(event, &mut map, &mut input_state) {
220
+                            Action::Close => break 'outer,
221
+                            Action::Redraw => {
222
+                                redraw = true;
223
+                            },
224
+                            Action::Nothing => {},
225
+                        }
226
+                    }
227
+                }
228
+            }
229
+        }
230
+
231
+        if redraw {
232
+            let draw_start = Instant::now();
233
+            map.draw();
234
+            draw_dur = draw_start.elapsed();
235
+
236
+            let _ = window.swap_buffers();
237
+
238
+            last_draw = Instant::now();
239
+
240
+            let diff = start_loop.elapsed();
241
+            println!("EVENT LOOP SECS {}", diff.as_secs() as f64 + diff.subsec_nanos() as f64 * 1e-9);
242
+        }
243
+    }
244
+}

+ 137
- 0
src/map_view.rs Näytä tiedosto

@@ -0,0 +1,137 @@
1
+use coord::{MapCoord, ScreenCoord, ScreenRect};
2
+use tile::Tile;
3
+
4
+
5
+#[derive(Clone, Debug)]
6
+pub struct MapView {
7
+    pub width: f64,
8
+    pub height: f64,
9
+    pub tile_size: u32,
10
+    pub center: MapCoord,
11
+    pub zoom2: f64,
12
+}
13
+
14
+#[derive(Clone, Debug)]
15
+pub struct VisibleTile {
16
+    pub tile: Tile,
17
+    pub rect: ScreenRect,
18
+}
19
+
20
+impl MapView {
21
+    pub fn new(width: f64, height: f64, tile_size: u32) -> MapView {
22
+        MapView {
23
+            width: width,
24
+            height: height,
25
+            tile_size: tile_size,
26
+            center: MapCoord::new(0.5, 0.5),
27
+            zoom2: 0.0,
28
+        }
29
+    }
30
+
31
+    pub fn top_left_coord(&self) -> MapCoord {
32
+        let scale = f64::powf(2.0, -self.zoom2) / f64::from(self.tile_size);
33
+
34
+        let x = self.center.x + -0.5 * self.width * scale;
35
+        let y = self.center.y + -0.5 * self.height * scale;
36
+
37
+        MapCoord::new(x, y)
38
+    }
39
+
40
+    pub fn map_to_screen_coord(&self, map_coord: MapCoord) -> ScreenCoord {
41
+        let scale = f64::powf(2.0, self.zoom2) * f64::from(self.tile_size);
42
+
43
+        let delta_x = map_coord.x - self.center.x;
44
+        let delta_y = map_coord.y - self.center.y;
45
+
46
+        ScreenCoord {
47
+            x: 0.5 * self.width + delta_x * scale,
48
+            y: 0.5 * self.height + delta_y * scale,
49
+        }
50
+    }
51
+
52
+    pub fn tile_screen_position(&self, tile: &Tile) -> ScreenCoord {
53
+        self.map_to_screen_coord(tile.map_coord())
54
+    }
55
+
56
+    pub fn visible_tiles(&self, snap_to_pixel: bool) -> Vec<VisibleTile> {
57
+        let uzoom = self.zoom2.floor().max(0.0) as u32;
58
+        let top_left_tile = self.top_left_coord().on_tile_at_zoom(uzoom);
59
+        let mut top_left_tile_screen_coord = self.tile_screen_position(&top_left_tile);
60
+        let tile_screen_size = f64::powf(2.0, self.zoom2 - f64::from(uzoom)) * f64::from(self.tile_size);
61
+
62
+        if snap_to_pixel {
63
+            top_left_tile_screen_coord.snap_to_pixel();
64
+        }
65
+
66
+        let start_tile_x = top_left_tile.tile_x;
67
+        let start_tile_y = top_left_tile.tile_y;
68
+        let num_tiles_x = ((self.width - top_left_tile_screen_coord.x) / tile_screen_size).ceil().max(0.0) as i32;
69
+        let num_tiles_y = ((self.height - top_left_tile_screen_coord.y) / tile_screen_size).ceil().max(0.0) as i32;
70
+
71
+        let mut visible_tiles = Vec::with_capacity(num_tiles_x as usize * num_tiles_y as usize);
72
+
73
+        for y in 0..num_tiles_y {
74
+            for x in 0..num_tiles_x {
75
+                let t = Tile::new(uzoom, start_tile_x + x, start_tile_y + y);
76
+                if t.is_on_planet() {
77
+                    visible_tiles.push(
78
+                        VisibleTile {
79
+                            tile: t,
80
+                            rect: ScreenRect {
81
+                                x: top_left_tile_screen_coord.x + tile_screen_size * f64::from(x),
82
+                                y: top_left_tile_screen_coord.y + tile_screen_size * f64::from(y),
83
+                                width: tile_screen_size,
84
+                                height: tile_screen_size,
85
+                            }
86
+                        }
87
+                    );
88
+                }
89
+            }
90
+        }
91
+
92
+        visible_tiles
93
+    }
94
+
95
+    pub fn set_size(&mut self, width: f64, height: f64) {
96
+        self.width = width;
97
+        self.height = height;
98
+    }
99
+
100
+    pub fn set_zoom(&mut self, zoom: f64) {
101
+        self.zoom2 = zoom;
102
+    }
103
+
104
+    pub fn zoom(&mut self, zoom_delta: f64) {
105
+        self.zoom2 += zoom_delta;
106
+    }
107
+
108
+    pub fn zoom_at(&mut self, pos: ScreenCoord, zoom_delta: f64) {
109
+        let delta_x = pos.x - self.width * 0.5;
110
+        let delta_y = pos.y - self.height * 0.5;
111
+
112
+        let scale =
113
+            (f64::powf(2.0, -self.zoom2) - f64::powf(2.0, -self.zoom2 - zoom_delta))
114
+            / f64::from(self.tile_size);
115
+        self.zoom2 += zoom_delta;
116
+
117
+        self.center.x += delta_x * scale;
118
+        self.center.y += delta_y * scale;
119
+    }
120
+
121
+    pub fn set_zoom_at(&mut self, pos: ScreenCoord, zoom: f64) {
122
+        let delta_x = pos.x - self.width * 0.5;
123
+        let delta_y = pos.y - self.height * 0.5;
124
+
125
+        let scale = (f64::powf(2.0, -self.zoom2) - f64::powf(2.0, -zoom)) / f64::from(self.tile_size);
126
+        self.zoom2 = zoom;
127
+
128
+        self.center.x += delta_x * scale;
129
+        self.center.y += delta_y * scale;
130
+    }
131
+
132
+    pub fn move_pixel(&mut self, delta_x: f64, delta_y: f64) {
133
+        let scale = f64::powf(2.0, -self.zoom2) / f64::from(self.tile_size);
134
+        self.center.x += delta_x * scale;
135
+        self.center.y += delta_y * scale;
136
+    }
137
+}

+ 186
- 0
src/map_view_gl.rs Näytä tiedosto

@@ -0,0 +1,186 @@
1
+use ::context;
2
+use ::std::ffi::CStr;
3
+use buffer::{Buffer, DrawMode};
4
+use context::Context;
5
+use coord::ScreenCoord;
6
+use image;
7
+use map_view::MapView;
8
+use program::Program;
9
+use texture::{Texture, TextureFormat};
10
+use tile_cache::TileCache;
11
+use tile_cache_gl::TileCacheGl;
12
+use tile_source::TileSource;
13
+
14
+
15
+#[derive(Debug)]
16
+pub struct MapViewGl<'a> {
17
+    cx: &'a Context,
18
+    program: Program<'a>,
19
+    buf: Buffer<'a>,
20
+    viewport_size: (u32, u32),
21
+    map_view: MapView,
22
+    tile_source: TileSource,
23
+    tile_cache: TileCache,
24
+    tile_cache_gl: TileCacheGl<'a>,
25
+}
26
+
27
+impl<'a> MapViewGl<'a> {
28
+    pub fn new<F>(cx: &Context, tile_source: TileSource, initial_size: (u32, u32), update_func: F) -> MapViewGl
29
+        where F: Fn() + Sync + Send + 'static,
30
+    {
31
+        println!("version: {}", cx.gl_version());
32
+        println!("max texture size: {}", cx.max_texture_size());
33
+        unsafe {
34
+            let mut program = Program::from_paths(cx, "shader/map.vert", "shader/map.frag");
35
+
36
+            check_gl_errors!(cx);
37
+            let mut tex = Texture::empty(cx, 2048, 2048, TextureFormat::Rgb8);
38
+            check_gl_errors!(cx);
39
+            {
40
+                let img = image::open("no_tile.png").unwrap();
41
+                tex.sub_image(0, 0, &img);
42
+                check_gl_errors!(cx);
43
+            }
44
+
45
+            let buf = Buffer::new(cx, &[], 0);
46
+
47
+            check_gl_errors!(cx);
48
+
49
+            program.add_texture(&tex, CStr::from_bytes_with_nul(b"tex_map\0").unwrap());
50
+            check_gl_errors!(cx);
51
+
52
+            program.add_attribute(CStr::from_bytes_with_nul(b"position\0").unwrap(), 2, 8, 0);
53
+            check_gl_errors!(cx);
54
+            program.add_attribute(CStr::from_bytes_with_nul(b"tex_coord\0").unwrap(), 2, 8, 2);
55
+            check_gl_errors!(cx);
56
+            program.add_attribute(CStr::from_bytes_with_nul(b"tex_minmax\0").unwrap(), 4, 8, 4);
57
+            check_gl_errors!(cx);
58
+
59
+            program.before_render();
60
+
61
+            let tile_size = 256;
62
+            let mut map_view = MapView::new(f64::from(initial_size.0), f64::from(initial_size.1), tile_size);
63
+
64
+            // set initial zoom
65
+            {
66
+                let min_dimension = f64::from(initial_size.0.min(initial_size.1));
67
+                let zoom = (min_dimension / f64::from(tile_size)).log2().ceil();
68
+                map_view.set_zoom(zoom);
69
+            }
70
+
71
+            MapViewGl {
72
+                cx: cx,
73
+                program: program,
74
+                buf: buf,
75
+                viewport_size: initial_size,
76
+                map_view: map_view,
77
+                //TODO load templates from config
78
+                tile_source: tile_source,
79
+                tile_cache: TileCache::new(move |_tile| update_func()),
80
+                tile_cache_gl: TileCacheGl::new(tex, 256),
81
+            }
82
+        }
83
+    }
84
+
85
+    pub fn set_viewport_size(&mut self, width: u32, height: u32) {
86
+        self.viewport_size = (width, height);
87
+        self.map_view.set_size(f64::from(width), f64::from(height));
88
+        unsafe {
89
+            self.cx.gl.Viewport(
90
+                0,
91
+                0,
92
+                width as context::gl::types::GLsizei,
93
+                height as context::gl::types::GLsizei);
94
+        }
95
+    }
96
+
97
+    pub fn draw(&mut self) {
98
+        {
99
+            let visible_tiles = self.map_view.visible_tiles(true);
100
+            let textured_visible_tiles = self.tile_cache_gl.textured_visible_tiles(
101
+                &visible_tiles,
102
+                &self.tile_source,
103
+                &mut self.tile_cache,
104
+            );
105
+
106
+            let mut vertex_data: Vec<f32> = Vec::with_capacity(textured_visible_tiles.len() * (6 * 8));
107
+            let scale_x = 2.0 / f64::from(self.viewport_size.0);
108
+            let scale_y = -2.0 / f64::from(self.viewport_size.1);
109
+            for tvt in textured_visible_tiles {
110
+                let minmax = [
111
+                    tvt.tex_minmax.x1 as f32,
112
+                    tvt.tex_minmax.y1 as f32,
113
+                    tvt.tex_minmax.x2 as f32,
114
+                    tvt.tex_minmax.y2 as f32,
115
+                ];
116
+                let p1 = [
117
+                    (tvt.screen_rect.x * scale_x - 1.0) as f32,
118
+                    (tvt.screen_rect.y * scale_y + 1.0) as f32,
119
+                    tvt.tex_rect.x1 as f32,
120
+                    tvt.tex_rect.y1 as f32,
121
+                ];
122
+                let p2 = [
123
+                    (tvt.screen_rect.x * scale_x - 1.0) as f32,
124
+                    ((tvt.screen_rect.y + tvt.screen_rect.height) * scale_y + 1.0) as f32,
125
+                    tvt.tex_rect.x1 as f32,
126
+                    tvt.tex_rect.y2 as f32,
127
+                ];
128
+                let p3 = [
129
+                    ((tvt.screen_rect.x + tvt.screen_rect.width) * scale_x - 1.0) as f32,
130
+                    ((tvt.screen_rect.y + tvt.screen_rect.height) * scale_y + 1.0) as f32,
131
+                    tvt.tex_rect.x2 as f32,
132
+                    tvt.tex_rect.y2 as f32,
133
+                ];
134
+                let p4 = [
135
+                    ((tvt.screen_rect.x + tvt.screen_rect.width) * scale_x - 1.0) as f32,
136
+                    (tvt.screen_rect.y * scale_y + 1.0) as f32,
137
+                    tvt.tex_rect.x2 as f32,
138
+                    tvt.tex_rect.y1 as f32,
139
+                ];
140
+                vertex_data.extend(&p1);
141
+                vertex_data.extend(&minmax);
142
+                vertex_data.extend(&p2);
143
+                vertex_data.extend(&minmax);
144
+                vertex_data.extend(&p3);
145
+                vertex_data.extend(&minmax);
146
+                vertex_data.extend(&p1);
147
+                vertex_data.extend(&minmax);
148
+                vertex_data.extend(&p3);
149
+                vertex_data.extend(&minmax);
150
+                vertex_data.extend(&p4);
151
+                vertex_data.extend(&minmax);
152
+            }
153
+
154
+            self.buf.set_data(&vertex_data, vertex_data.len() / 4);
155
+        }
156
+
157
+        self.cx.clear_color((0.9, 0.9, 0.9, 1.0));
158
+        self.buf.draw(DrawMode::Triangles);
159
+    }
160
+
161
+    pub fn zoom(&mut self, zoom_delta: f64) {
162
+        if self.map_view.zoom2 + zoom_delta < 0.0 {
163
+            self.map_view.set_zoom(0.0);
164
+        } else if self.map_view.zoom2 + zoom_delta > 22.0 {
165
+            self.map_view.set_zoom(22.0);
166
+        } else {
167
+            self.map_view.zoom(zoom_delta);
168
+        }
169
+    }
170
+
171
+    pub fn zoom_at(&mut self, pos: ScreenCoord, zoom_delta: f64) {
172
+        if self.map_view.zoom2 + zoom_delta < 0.0 {
173
+            self.map_view.set_zoom_at(pos, 0.0);
174
+        } else if self.map_view.zoom2 + zoom_delta > 22.0 {
175
+            self.map_view.set_zoom_at(pos, 22.0);
176
+        } else {
177
+            self.map_view.zoom_at(pos, zoom_delta);
178
+        }
179
+        self.map_view.center.normalize_xy();
180
+    }
181
+
182
+    pub fn move_pixel(&mut self, delta_x: f64, delta_y: f64) {
183
+        self.map_view.move_pixel(delta_x, delta_y);
184
+        self.map_view.center.normalize_xy();
185
+    }
186
+}

+ 203
- 0
src/program.rs Näytä tiedosto

@@ -0,0 +1,203 @@
1
+use ::context;
2
+use context::Context;
3
+use std::ffi::CStr;
4
+use std::fs::File;
5
+use std::io::BufReader;
6
+use std::io::Read;
7
+use std::mem;
8
+use std::path::Path;
9
+use texture::{Texture, TextureId};
10
+
11
+#[derive(Clone, Debug)]
12
+pub struct Program<'a> {
13
+    cx: &'a ::context::Context,
14
+    vert_obj: u32,
15
+    frag_obj: u32,
16
+    program_obj: u32,
17
+    tex_ids: Vec<TextureId>,
18
+    tex_locations: Vec<i32>,
19
+}
20
+
21
+#[derive(Clone, Debug)]
22
+pub struct ProgramId {
23
+    id: u32,
24
+}
25
+
26
+impl<'a> Program<'a> {
27
+    pub unsafe fn from_paths<P: AsRef<Path>>(cx: &'a Context, vert_path: P, frag_path: P) -> Program<'a> {
28
+        let vert_src = {
29
+            let file = File::open(&vert_path).unwrap();
30
+            let mut reader = BufReader::new(file);
31
+            let mut buf: Vec<u8> = vec![];
32
+            reader.read_to_end(&mut buf).unwrap();
33
+            buf
34
+        };
35
+
36
+        let frag_src = {
37
+            let file = File::open(&frag_path).unwrap();
38
+            let mut reader = BufReader::new(file);
39
+            let mut buf: Vec<u8> = vec![];
40
+            reader.read_to_end(&mut buf).unwrap();
41
+            buf
42
+        };
43
+
44
+        Self::new(cx, &vert_src, &frag_src)
45
+    }
46
+
47
+    pub unsafe fn new(cx: &'a Context, vert_src: &[u8], frag_src: &[u8]) -> Program<'a> {
48
+        let vert_obj = {
49
+            let vert_obj = cx.gl.CreateShader(context::gl::VERTEX_SHADER);
50
+            let vert_len = vert_src.len() as i32;
51
+            cx.gl.ShaderSource(
52
+                vert_obj,
53
+                1,
54
+                [vert_src.as_ptr() as *const _].as_ptr(),
55
+                &vert_len as *const _);
56
+            cx.gl.CompileShader(vert_obj);
57
+            check_compile_errors(cx, vert_obj);
58
+            check_gl_errors!(cx);
59
+            vert_obj
60
+        };
61
+
62
+        let frag_obj = {
63
+            let frag_obj = cx.gl.CreateShader(context::gl::FRAGMENT_SHADER);
64
+            let frag_len = frag_src.len() as i32;
65
+            cx.gl.ShaderSource(
66
+                frag_obj,
67
+                1,
68
+                [frag_src.as_ptr() as *const _].as_ptr(),
69
+                &frag_len as *const _);
70
+            cx.gl.CompileShader(frag_obj);
71
+            check_compile_errors(cx, frag_obj);
72
+            check_gl_errors!(cx);
73
+            frag_obj
74
+        };
75
+
76
+        let program_obj = {
77
+            let prog = cx.gl.CreateProgram();
78
+            cx.gl.AttachShader(prog, vert_obj);
79
+            cx.gl.AttachShader(prog, frag_obj);
80
+            cx.gl.LinkProgram(prog);
81
+            check_link_errors(cx, prog);
82
+
83
+            cx.gl.UseProgram(prog);
84
+            check_gl_errors!(cx);
85
+            prog
86
+        };
87
+
88
+        Program {
89
+            cx: cx,
90
+            vert_obj: vert_obj,
91
+            frag_obj: frag_obj,
92
+            program_obj: program_obj,
93
+            tex_ids: vec![],
94
+            tex_locations: vec![],
95
+        }
96
+    }
97
+
98
+    pub unsafe fn add_texture(&mut self, texture: &Texture, uniform_name: &CStr) {
99
+        //TODO store reference to texture
100
+        let tex_loc = self.cx.gl.GetUniformLocation(self.program_obj, uniform_name.as_ptr() as *const _);
101
+        check_gl_errors!(self.cx);
102
+
103
+        self.tex_ids.push(texture.id());
104
+        self.tex_locations.push(tex_loc);
105
+
106
+    }
107
+
108
+    pub unsafe fn add_attribute(&mut self, name: &CStr, number_components: u32, stride: usize, offset: usize) {
109
+        let attrib_id = self.cx.gl.GetAttribLocation(self.program_obj, name.as_ptr() as *const _);
110
+        check_gl_errors!(self.cx);
111
+        self.cx.gl.VertexAttribPointer(
112
+            attrib_id as u32,
113
+            number_components as i32, // size
114
+            context::gl::FLOAT, // type
115
+            0, // normalized
116
+            (stride * mem::size_of::<f32>()) as context::gl::types::GLsizei,
117
+            (offset * mem::size_of::<f32>()) as *const () as *const _);
118
+        check_gl_errors!(self.cx);
119
+        self.cx.gl.EnableVertexAttribArray(attrib_id as u32);
120
+        check_gl_errors!(self.cx);
121
+    }
122
+
123
+    pub unsafe fn before_render(&self) {
124
+        check_gl_errors!(self.cx);
125
+        //self.cx.gl.UseProgram(self.program_obj);
126
+        //TODO check max texture number
127
+        for (i, (tex_id, &tex_loc)) in self.tex_ids.iter().zip(&self.tex_locations).enumerate() {
128
+            self.cx.gl.ActiveTexture(context::gl::TEXTURE0 + i as u32);
129
+            self.cx.gl.BindTexture(context::gl::TEXTURE_2D, tex_id.id);
130
+            self.cx.gl.Uniform1i(tex_loc, i as i32);
131
+        }
132
+    }
133
+
134
+    pub fn id(&self) -> ProgramId {
135
+        ProgramId {
136
+            id: self.program_obj,
137
+        }
138
+    }
139
+}
140
+
141
+unsafe fn check_link_errors(cx: &Context, program_obj: u32)
142
+{
143
+    let mut link_success: i32 = mem::uninitialized();
144
+
145
+    cx.gl.GetProgramiv(program_obj, context::gl::LINK_STATUS, &mut link_success);
146
+
147
+    if link_success == 0 {
148
+
149
+        match cx.gl.GetError() {
150
+            context::gl::NO_ERROR => (),
151
+            context::gl::INVALID_VALUE => {
152
+                println!("invalid value");
153
+                return;
154
+            },
155
+            context::gl::INVALID_OPERATION => {
156
+                println!("invalid operation");
157
+                return;
158
+            },
159
+            _ => {
160
+                println!("unknown error");
161
+                return;
162
+            }
163
+        };
164
+
165
+        let mut error_log_size: i32 = mem::uninitialized();
166
+
167
+        cx.gl.GetProgramiv(program_obj, context::gl::INFO_LOG_LENGTH, &mut error_log_size);
168
+
169
+        let mut error_log: Vec<u8> = Vec::with_capacity(error_log_size as usize);
170
+
171
+        cx.gl.GetProgramInfoLog(program_obj, error_log_size, &mut error_log_size,
172
+                             error_log.as_mut_ptr() as *mut context::gl::types::GLchar);
173
+
174
+        error_log.set_len(error_log_size as usize);
175
+
176
+        let msg = String::from_utf8(error_log).unwrap();
177
+        println!("{}", msg);
178
+    }
179
+}
180
+
181
+unsafe fn check_compile_errors(cx: &Context, shader_obj: u32) {
182
+    // checking compilation success by reading a flag on the shader
183
+    let compilation_success = {
184
+        let mut compilation_success: i32 = mem::uninitialized();
185
+        cx.gl.GetShaderiv(shader_obj, context::gl::COMPILE_STATUS, &mut compilation_success);
186
+        compilation_success
187
+    };
188
+
189
+    if compilation_success != 1 {
190
+        // compilation error
191
+        let mut error_log_size: i32 = mem::uninitialized();
192
+        cx.gl.GetShaderiv(shader_obj, context::gl::INFO_LOG_LENGTH, &mut error_log_size);
193
+        let mut error_log: Vec<u8> = Vec::with_capacity(error_log_size as usize);
194
+
195
+        cx.gl.GetShaderInfoLog(shader_obj, error_log_size, &mut error_log_size,
196
+                                 error_log.as_mut_ptr() as *mut _);
197
+        error_log.set_len(error_log_size as usize);
198
+
199
+        if let Ok(msg) = String::from_utf8(error_log) {
200
+            println!("{}", msg);
201
+        }
202
+    }
203
+}

+ 121
- 0
src/texture.rs Näytä tiedosto

@@ -0,0 +1,121 @@
1
+use ::context;
2
+use ::image;
3
+use context::Context;
4
+use image::GenericImage;
5
+use std::os::raw::c_void;
6
+
7
+#[derive(Clone, Debug)]
8
+pub struct Texture<'a> {
9
+    cx: &'a Context,
10
+    texture_obj: u32,
11
+    width: u32,
12
+    height: u32,
13
+}
14
+
15
+#[derive(Clone, Debug)]
16
+pub struct TextureId {
17
+    pub(crate) id: u32,
18
+}
19
+
20
+impl<'a> Texture<'a> {
21
+    pub fn new(cx: &'a Context, img: &image::DynamicImage) -> Result<Texture<'a>, ()> {
22
+        let format = match *img {
23
+            image::ImageRgb8(_) => TextureFormat::Rgb8,
24
+            image::ImageRgba8(_) => TextureFormat::Rgba8,
25
+            _ => return Err(()),
26
+        };
27
+
28
+        Ok(Self::from_bytes(cx, img.width(), img.height(), format, &img.raw_pixels()))
29
+    }
30
+
31
+    pub fn empty(cx: &'a Context, width: u32, height: u32, format: TextureFormat) -> Texture<'a> {
32
+        Self::from_ptr(cx, width, height, format, ::std::ptr::null() as *const _)
33
+    }
34
+
35
+    pub fn from_bytes(cx: &'a Context, width: u32, height: u32, format: TextureFormat, data: &[u8]) -> Texture<'a> {
36
+        Self::from_ptr(cx, width, height, format, data.as_ptr() as *const _)
37
+    }
38
+
39
+    fn from_ptr(cx: &'a Context, width: u32, height: u32, format: TextureFormat, data_ptr: *const c_void) -> Texture<'a> {
40
+        let mut tex_obj = 0_u32;
41
+        unsafe {
42
+            cx.gl.GenTextures(1, &mut tex_obj);
43
+            cx.gl.BindTexture(context::gl::TEXTURE_2D, tex_obj);
44
+
45
+            cx.gl.TexParameteri(context::gl::TEXTURE_2D, context::gl::TEXTURE_MIN_FILTER, context::gl::LINEAR as i32);
46
+            cx.gl.TexParameteri(context::gl::TEXTURE_2D, context::gl::TEXTURE_MAG_FILTER, context::gl::LINEAR as i32);
47
+            cx.gl.TexParameteri(context::gl::TEXTURE_2D, context::gl::TEXTURE_WRAP_S, context::gl::CLAMP_TO_EDGE as i32);
48
+            cx.gl.TexParameteri(context::gl::TEXTURE_2D, context::gl::TEXTURE_WRAP_T, context::gl::CLAMP_TO_EDGE as i32);
49
+
50
+            cx.gl.TexImage2D(
51
+                context::gl::TEXTURE_2D,
52
+                0, // level
53
+                format.to_gl_enum() as i32,
54
+                width as i32,
55
+                height as i32,
56
+                0, // border (must be zero)
57
+                format.to_gl_enum(),
58
+                context::gl::UNSIGNED_BYTE,
59
+                data_ptr);
60
+        }
61
+
62
+        Texture {
63
+            cx: cx,
64
+            texture_obj: tex_obj,
65
+            width: width,
66
+            height: height,
67
+        }
68
+    }
69
+
70
+    pub fn sub_image(&mut self, x: i32, y: i32, img: &image::DynamicImage) {
71
+        let format = match *img {
72
+            image::ImageRgb8(_) => TextureFormat::Rgb8,
73
+            image::ImageRgba8(_) => TextureFormat::Rgba8,
74
+            _ => return,
75
+        };
76
+
77
+        unsafe {
78
+            self.cx.gl.BindTexture(context::gl::TEXTURE_2D, self.texture_obj);
79
+            self.cx.gl.TexSubImage2D(
80
+                context::gl::TEXTURE_2D,
81
+                0, // level
82
+                x, // x offset
83
+                y, // y offset
84
+                img.width() as i32,
85
+                img.height() as i32,
86
+                format.to_gl_enum(),
87
+                context::gl::UNSIGNED_BYTE,
88
+                img.raw_pixels().as_ptr() as *const _,
89
+            );
90
+        }
91
+    }
92
+
93
+    pub fn id(&self) -> TextureId {
94
+        TextureId {
95
+            id: self.texture_obj,
96
+        }
97
+    }
98
+
99
+    pub fn width(&self) -> u32 {
100
+        self.width
101
+    }
102
+
103
+    pub fn height(&self) -> u32 {
104
+        self.height
105
+    }
106
+}
107
+
108
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
109
+pub enum TextureFormat {
110
+    Rgb8,
111
+    Rgba8,
112
+}
113
+
114
+impl TextureFormat {
115
+    pub fn to_gl_enum(self) -> u32 {
116
+        match self {
117
+            TextureFormat::Rgb8 => context::gl::RGB,
118
+            TextureFormat::Rgba8 => context::gl::RGBA,
119
+        }
120
+    }
121
+}

+ 70
- 0
src/tile.rs Näytä tiedosto

@@ -0,0 +1,70 @@
1
+use coord::MapCoord;
2
+
3
+
4
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
5
+pub struct Tile {
6
+    pub zoom: u32,
7
+    pub tile_x: i32,
8
+    pub tile_y: i32,
9
+}
10
+
11
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
12
+pub struct SubTileCoord {
13
+    pub size: u32,
14
+    pub x: u32,
15
+    pub y: u32,
16
+}
17
+
18
+impl Tile {
19
+    pub fn new(zoom: u32, tile_x: i32, tile_y: i32) -> Tile {
20
+        Tile {
21
+            zoom: zoom,
22
+            tile_x: Self::normalize_coord(tile_x, zoom),
23
+            tile_y: tile_y,
24
+        }
25
+    }
26
+
27
+    pub fn is_on_planet(&self) -> bool {
28
+        let num_tiles = Self::get_zoom_level_tiles(self.zoom);
29
+        self.tile_y >= 0 && self.tile_y < num_tiles &&
30
+        self.tile_x >= 0 && self.tile_x < num_tiles
31
+    }
32
+
33
+    pub fn map_coord(&self) -> MapCoord {
34
+        let inv_zoom_factor = f64::powi(2.0, -(self.zoom as i32));
35
+        MapCoord::new(f64::from(self.tile_x) * inv_zoom_factor, f64::from(self.tile_y) * inv_zoom_factor)
36
+    }
37
+
38
+    pub fn parent(&self, distance: u32) -> Option<(Tile, SubTileCoord)> {
39
+        if distance > self.zoom {
40
+            None
41
+        } else {
42
+            let scale = u32::pow(2, distance);
43
+
44
+            Some((
45
+                Tile {
46
+                    zoom: self.zoom - distance,
47
+                    tile_x: self.tile_x / scale as i32,
48
+                    tile_y: self.tile_y / scale as i32,
49
+                },
50
+                SubTileCoord {
51
+                    size: scale,
52
+                    x: (Self::normalize_coord(self.tile_x, self.zoom) as u32) % scale,
53
+                    y: (Self::normalize_coord(self.tile_y, self.zoom) as u32) % scale,
54
+                },
55
+            ))
56
+        }
57
+    }
58
+
59
+    #[inline]
60
+    fn normalize_coord(coord: i32, zoom: u32) -> i32 {
61
+        let max = Self::get_zoom_level_tiles(zoom);
62
+        ((coord % max) + max) % max
63
+    }
64
+
65
+    #[inline]
66
+    pub fn get_zoom_level_tiles(zoom: u32) -> i32 {
67
+        //TODO throw error when zoom too big
68
+        i32::pow(2, zoom)
69
+    }
70
+}

+ 98
- 0
src/tile_cache.rs Näytä tiedosto

@@ -0,0 +1,98 @@
1
+use image;
2
+use linked_hash_map::{Entry, LinkedHashMap};
3
+use tile::Tile;
4
+use tile_loader::TileLoader;
5
+use tile_source::TileSource;
6
+
7
+
8
+pub struct TileCache {
9
+    loader: TileLoader,
10
+    map: LinkedHashMap<Tile, image::DynamicImage>,
11
+    max_tiles: usize,
12
+}
13
+
14
+impl TileCache {
15
+    pub fn new<F>(new_tile_func: F) -> Self
16
+        where F: Fn(Tile) + Sync + Send + 'static,
17
+    {
18
+        TileCache {
19
+            loader: TileLoader::new(move |tile| {
20
+                new_tile_func(tile);
21
+            }),
22
+            map: LinkedHashMap::new(),
23
+            max_tiles: 512, //TODO set a reasonable value
24
+        }
25
+    }
26
+
27
+    pub fn get_sync(
28
+        &mut self,
29
+        tile: Tile,
30
+        source: &TileSource,
31
+        write_to_file: bool,
32
+        ) -> Option<&image::DynamicImage>
33
+    {
34
+        //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
35
+        self.map.get_refresh(&tile);
36
+
37
+        // remove old cache entries
38
+        while self.map.len() + 1 > self.max_tiles {
39
+            self.map.pop_front();
40
+        }
41
+
42
+        match self.map.entry(tile) {
43
+            Entry::Occupied(entry) => {
44
+                Some(entry.into_mut())
45
+            },
46
+            Entry::Vacant(entry) => {
47
+                self.loader.get_sync(tile, source, write_to_file).map(|img| entry.insert(img) as &_)
48
+            },
49
+        }
50
+    }
51
+
52
+    pub fn get_async(
53
+        &mut self,
54
+        tile: Tile,
55
+        source: &TileSource,
56
+        write_to_file: bool,
57
+        ) -> Option<&image::DynamicImage>
58
+    {
59
+        while let Some((t, img)) = self.loader.async_result() {
60
+            // remove old cache entries
61
+            while self.map.len() + 1 > self.max_tiles {
62
+                self.map.pop_front();
63
+            }
64
+
65
+            self.map.insert(t, img);
66
+            println!("CACHE SIZE: {} tiles", self.map.len());
67
+        }
68
+
69
+        //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
70
+        self.map.get_refresh(&tile);
71
+
72
+        match self.map.entry(tile) {
73
+            Entry::Occupied(entry) => Some(entry.into_mut()),
74
+            Entry::Vacant(_) => {
75
+                self.loader.async_request(tile, source, write_to_file);
76
+                None
77
+            }
78
+        }
79
+    }
80
+
81
+    // Return a tile from the cache but do not use TileLoader.
82
+    pub fn lookup(&mut self, tile: Tile, source: &TileSource) -> Option<&image::DynamicImage> {
83
+        //TODO Return the value from get_refresh with borrowck agreeing that this is OK.
84
+        self.map.get_refresh(&tile);
85
+
86
+        self.map.get(&tile)
87
+    }
88
+}
89
+
90
+impl ::std::fmt::Debug for TileCache {
91
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
92
+        write!(
93
+            f,
94
+            "TileCache {{ tiles: {:?} }}",
95
+            self.map.keys().collect::<Vec<_>>()
96
+        )
97
+    }
98
+}

+ 145
- 0
src/tile_cache_async.rs Näytä tiedosto

@@ -0,0 +1,145 @@
1
+use image::DynamicImage;
2
+use image;
3
+use std::collections::HashMap;
4
+use std::collections::hash_map::Entry;
5
+use std::ops::Deref;
6
+use std::sync::{Arc, Mutex, MutexGuard};
7
+use tile::Tile;
8
+use tile_loader::TileLoader;
9
+use tile_source::TileSource;
10
+
11
+
12
+pub struct TileCache {
13
+    loader: TileLoader,
14
+    map: Arc<Mutex<HashMap<Tile, image::DynamicImage>>>,
15
+}
16
+
17
+impl TileCache {
18
+    pub fn new<F>(new_tile_func: F) -> Self
19
+        where F: Fn(Tile) + Sync + Send + 'static,
20
+    {
21
+        let map = Arc::new(Mutex::new(HashMap::new()));
22
+        TileCache {
23
+            loader: TileLoader::new(TileSource::new(), move |tile| {
24
+                println!("TILECACHE NEW tile {:?}", tile);
25
+                new_tile_func(tile);
26
+            }),
27
+            map: map,
28
+        }
29
+    }
30
+
31
+    pub fn get_sync(&mut self, tile: Tile, source: &TileSource) -> Option<ImgRef> {
32
+        if let Ok(mut lock) = self.map.lock() {
33
+            let contains = lock.contains_key(&tile);
34
+
35
+            if contains {
36
+                Some(ImgRef {
37
+                    guard: lock,
38
+                    key: tile,
39
+                })
40
+            } else {
41
+                if let Some(img) = self.loader.get_sync(tile, source) {
42
+                    lock.insert(tile, img);
43
+
44
+                    Some(ImgRef {
45
+                        guard: lock,
46
+                        key: tile,
47
+                    })
48
+                } else {
49
+                    None
50
+                }
51
+            }
52
+        } else {
53
+            None
54
+        }
55
+    }
56
+
57
+    //TODO Return ImgRef, do not clone
58
+    pub fn get_async(&mut self, tile: Tile, source: &TileSource) -> Option<&DynamicImage> {
59
+        if let Ok(mut lock) = self.map.lock() {
60
+            match lock.entry(tile) {
61
+                Entry::Occupied(entry) => Some(entry.into_mut().clone()),
62
+                Entry::Vacant(_) => {
63
+                    self.loader.async_request(tile);
64
+                    None
65
+                }
66
+            }
67
+        } else {
68
+            None
69
+        }
70
+    }
71
+
72
+    /*
73
+    //TODO Return ImgRef, do not clone
74
+    pub fn get_sync(&mut self, tile: Tile, source: &TileSource) -> Option<ImgRef> {
75
+        if let Ok(mut lock) = self.map.lock() {
76
+            let contains = lock.contains_key(&tile);
77
+
78
+            if contains {
79
+                Some(ImgRef {
80
+                    guard: lock,
81
+                    key: tile,
82
+                })
83
+            } else {
84
+                if let Some(img) = self.loader.get_sync(tile, source) {
85
+                    lock.insert(tile, img);
86
+
87
+                    Some(ImgRef {
88
+                        guard: lock,
89
+                        key: tile,
90
+                    })
91
+                } else {
92
+                    None
93
+                }
94
+            }
95
+        } else {
96
+            None
97
+        }
98
+    }
99
+
100
+    //TODO Return ImgRef, do not clone
101
+    pub fn get_async(&mut self, tile: Tile, source: &TileSource) -> Option<DynamicImage> {
102
+        if let Ok(mut lock) = self.map.lock() {
103
+            match lock.entry(tile) {
104
+                Entry::Occupied(entry) => Some(entry.into_mut().clone()),
105
+                Entry::Vacant(_) => {
106
+                    self.loader.async_request(tile);
107
+                    None
108
+                }
109
+            }
110
+        } else {
111
+            None
112
+        }
113
+    }
114
+    */
115
+}
116
+
117
+impl ::std::fmt::Debug for TileCache {
118
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
119
+        if let Ok(lock) = self.map.try_lock() {
120
+            write!(
121
+                f,
122
+                "TileCache {{ tiles: {:?} }}",
123
+                lock.keys().collect::<Vec<_>>()
124
+            )
125
+        } else {
126
+            write!(
127
+                f,
128
+                "TileCache {{ tiles: <not accessible> }}",
129
+            )
130
+        }
131
+    }
132
+}
133
+
134
+pub struct ImgRef<'a> {
135
+    guard: MutexGuard<'a, HashMap<Tile, DynamicImage>>,
136
+    key: Tile,
137
+}
138
+
139
+impl<'a> Deref for ImgRef<'a> {
140
+    type Target = DynamicImage;
141
+    fn deref(&self) -> &Self::Target {
142
+        println!("DEREF {:?}", self.key);
143
+        self.guard.get(&self.key).unwrap()
144
+    }
145
+}

+ 194
- 0
src/tile_cache_gl.rs Näytä tiedosto

@@ -0,0 +1,194 @@
1
+use coord::ScreenRect;
2
+use linked_hash_map::LinkedHashMap;
3
+use map_view::VisibleTile;
4
+use std::collections::HashMap;
5
+use std::collections::hash_map::Entry;
6
+use texture::Texture;
7
+use tile::{Tile, SubTileCoord};
8
+use tile_cache::TileCache;
9
+use tile_source::TileSource;
10
+
11
+#[derive(Copy, Clone, Debug)]
12
+pub struct TextureRect {
13
+    pub x1: f64,
14
+    pub y1: f64,
15
+    pub x2: f64,
16
+    pub y2: f64,
17
+}
18
+
19
+impl TextureRect {
20
+    pub fn inset(self, margin_x: f64, margin_y: f64) -> TextureRect {
21
+        TextureRect {
22
+            x1: self.x1 + margin_x,
23
+            y1: self.y1 + margin_y,
24
+            x2: self.x2 - margin_x,
25
+            y2: self.y2 - margin_y,
26
+        }
27
+    }
28
+}
29
+
30
+#[derive(Clone, Debug)]
31
+pub struct TexturedVisibleTile {
32
+    pub screen_rect: ScreenRect,
33
+    pub tex_rect: TextureRect,
34
+    pub tex_minmax: TextureRect,
35
+}
36
+
37
+#[derive(Clone, Debug)]
38
+pub struct TileCacheGl<'a> {
39
+    texture: Texture<'a>,
40
+    tile_size: u32,
41
+    slots_lru: LinkedHashMap<CacheSlot, Option<Tile>>, // LRU cache of slots
42
+    tile_to_slot: HashMap<Tile, CacheSlot>,
43
+}
44
+
45
+impl<'a> TileCacheGl<'a> {
46
+    pub fn new(tex: Texture<'a>, tile_size: u32) -> Self {
47
+        let slots_x = tex.width() / tile_size;
48
+        let slots_y = tex.height() / tile_size;
49
+        let num_slots = (slots_x * slots_y) as usize;
50
+
51
+        let mut slots_lru = LinkedHashMap::with_capacity(num_slots);
52
+        for x in 0..slots_x {
53
+            for y in 0..slots_y {
54
+                let slot = CacheSlot { x: x, y: y };
55
+                slots_lru.insert(slot, None);
56
+            }
57
+        }
58
+
59
+        slots_lru.remove(&Self::default_slot());
60
+
61
+        TileCacheGl {
62
+            texture: tex,
63
+            tile_size: tile_size,
64
+            slots_lru: slots_lru,
65
+            tile_to_slot: HashMap::with_capacity(num_slots),
66
+        }
67
+    }
68
+
69
+    pub fn default_slot() -> CacheSlot {
70
+        CacheSlot { x: 0, y: 0 }
71
+    }
72
+
73
+    pub fn store(&mut self, tile: Tile, source: &TileSource, cache: &mut TileCache, load: bool) -> Option<CacheSlot> {
74
+        let mut remove_tile = None;
75
+
76
+        let slot = match self.tile_to_slot.entry(tile) {
77
+            Entry::Vacant(entry) => {
78
+                let img_option = if load {
79
+                    cache.get_async(tile, source, true)
80
+                } else {
81
+                    cache.lookup(tile, source)
82
+                };
83
+
84
+                if let Some(img) = img_option {
85
+                    let (slot, old_tile) = self.slots_lru.pop_front().unwrap();
86
+                    self.slots_lru.insert(slot, Some(tile));
87
+
88
+                    remove_tile = old_tile;
89
+
90
+                    self.texture.sub_image(
91
+                        (slot.x * self.tile_size) as i32,
92
+                        (slot.y * self.tile_size) as i32,
93
+                        img,
94
+                    );
95
+                    Some(*entry.insert(slot))
96
+                } else {
97
+                    None
98
+                }
99
+            },
100
+            Entry::Occupied(entry) => {
101
+                let slot = *entry.into_mut();
102
+
103
+                self.slots_lru.get_refresh(&slot);
104
+
105
+                Some(slot)
106
+            },
107
+        };
108
+
109
+        if let Some(t) = remove_tile {
110
+            self.tile_to_slot.remove(&t);
111
+        }
112
+
113
+        slot
114
+    }
115
+
116
+    pub fn textured_visible_tiles(
117
+        &mut self,
118
+        visible_tiles: &[VisibleTile],
119
+        source: &TileSource,
120
+        cache: &mut TileCache,
121
+        ) -> Vec<TexturedVisibleTile>
122
+    {
123
+        let mut tvt = Vec::with_capacity(visible_tiles.len());
124
+
125
+        let inset_x = 0.5 / f64::from(self.texture.width());
126
+        let inset_y = 0.5 / f64::from(self.texture.height());
127
+
128
+        for vt in visible_tiles {
129
+            if let Some(slot) = self.store(vt.tile, source, cache, true) {
130
+                let tex_rect = self.slot_to_texture_rect(slot);
131
+                tvt.push(
132
+                    TexturedVisibleTile {
133
+                        screen_rect: vt.rect,
134
+                        tex_rect: tex_rect,
135
+                        tex_minmax: tex_rect.inset(inset_x, inset_y),
136
+                    }
137
+                );
138
+            } else {
139
+                // look for cached tiles in lower zoom layers
140
+                let mut tex_sub_rect = self.slot_to_texture_rect(Self::default_slot());
141
+                let mut tex_rect = tex_sub_rect;
142
+                for dist in 1..31 {
143
+                    if let Some((parent_tile, sub_coord)) = vt.tile.parent(dist) {
144
+                        if let Some(slot) = self.store(parent_tile, source, cache, false) {
145
+                            tex_sub_rect = self.subslot_to_texture_rect(slot, sub_coord);
146
+                            tex_rect = self.slot_to_texture_rect(slot);
147
+                            break;
148
+                        }
149
+                    }
150
+                }
151
+                tvt.push(
152
+                    TexturedVisibleTile {
153
+                        screen_rect: vt.rect,
154
+                        tex_rect: tex_sub_rect,
155
+                        tex_minmax: tex_rect.inset(inset_x, inset_y),
156
+                    }
157
+                );
158
+            };
159
+
160
+        }
161
+
162
+        tvt
163
+    }
164
+
165
+    fn slot_to_texture_rect(&self, slot: CacheSlot) -> TextureRect {
166
+        let scale_x = f64::from(self.tile_size) / f64::from(self.texture.width());
167
+        let scale_y = f64::from(self.tile_size) / f64::from(self.texture.height());
168
+
169
+        TextureRect {
170
+            x1: f64::from(slot.x) * scale_x,
171
+            y1: f64::from(slot.y) * scale_y,
172
+            x2: f64::from(slot.x + 1) * scale_x,
173
+            y2: f64::from(slot.y + 1) * scale_y,
174
+        }
175
+    }
176
+
177
+    fn subslot_to_texture_rect(&self, slot: CacheSlot, sub_coord: SubTileCoord) -> TextureRect {
178
+        let scale_x = f64::from(self.tile_size) / (f64::from(self.texture.width()) * f64::from(sub_coord.size));
179
+        let scale_y = f64::from(self.tile_size) / (f64::from(self.texture.height()) * f64::from(sub_coord.size));
180
+
181
+        TextureRect {
182
+            x1: f64::from(slot.x * sub_coord.size + sub_coord.x) * scale_x,
183
+            y1: f64::from(slot.y * sub_coord.size + sub_coord.y) * scale_y,
184
+            x2: f64::from(slot.x * sub_coord.size + sub_coord.x + 1) * scale_x,
185
+            y2: f64::from(slot.y * sub_coord.size + sub_coord.y + 1) * scale_y,
186
+        }
187
+    }
188
+}
189
+
190
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
191
+pub struct CacheSlot {
192
+    pub x: u32,
193
+    pub y: u32,
194
+}

+ 165
- 0
src/tile_loader.rs Näytä tiedosto

@@ -0,0 +1,165 @@
1
+use image::DynamicImage;
2
+use image;
3
+use reqwest::Client;
4
+use std::collections::hash_set::HashSet;
5
+use std::fs::File;
6
+use std::io::Write;
7
+use std::path::{Path, PathBuf};
8
+use std::sync::mpsc;
9
+use std::thread;
10
+use tile::Tile;
11
+use tile_source::TileSource;
12
+
13
+
14
+//TODO remember failed loading attempts
15
+
16
+#[derive(Debug)]
17
+pub struct TileLoader {
18
+    client: Option<Client>,
19
+    join_handle: thread::JoinHandle<()>,
20
+    request_tx: mpsc::Sender<(Tile, String, PathBuf, bool)>,
21
+    result_rx: mpsc::Receiver<(Tile, Option<DynamicImage>)>,
22
+    //TODO store source together with tile in pending
23
+    pending: HashSet<Tile>,
24
+}
25
+
26
+impl TileLoader {
27
+    pub fn new<F>(notice_func: F) -> Self
28
+        where F: Fn(Tile) + Sync + Send + 'static,
29
+    {
30
+        let (request_tx, request_rx) = mpsc::channel();
31
+        let (result_tx, result_rx) = mpsc::channel();
32
+
33
+        TileLoader {
34
+            client: None,
35
+            join_handle: thread::spawn(move || Self::work(request_rx, result_tx, notice_func)),
36
+            request_tx: request_tx,
37
+            result_rx: result_rx,
38
+            pending: HashSet::new(),
39
+        }
40
+    }
41
+
42
+    fn work<F>(
43
+        request_rx: mpsc::Receiver<(Tile, String, PathBuf, bool)>,
44
+        result_tx: mpsc::Sender<(Tile, Option<DynamicImage>)>,
45
+        notice_func: F,
46
+    )
47
+        where F: Fn(Tile) + Sync + Send + 'static,
48
+    {
49
+        let mut client_opt = None;
50
+        while let Ok((tile, url, path, write_to_file)) = request_rx.recv() {
51
+            println!("work {:?}", tile);
52
+            match image::open(&path) {
53
+                Ok(img) => {
54
+                    result_tx.send((tile, Some(img))).unwrap();
55
+                    notice_func(tile);
56
+                    continue;
57
+                },
58
+                Err(_) => {
59
+                    //TODO do not try to create a client every time when it failed before
60
+                    if client_opt.is_none() {
61
+                        client_opt = Client::builder().build().ok();
62
+                    }
63
+
64
+                    if let Some(ref client) = client_opt {
65
+                        println!("use client {:?}", tile);
66
+                        if let Ok(mut response) = client.get(&url).send() {
67
+                            let mut buf: Vec<u8> = vec![];
68
+                            response.copy_to(&mut buf).unwrap();
69
+                            if let Ok(img) = image::load_from_memory(&buf) {
70
+                                result_tx.send((tile, Some(img))).unwrap();
71
+                                notice_func(tile);
72
+
73
+                                if write_to_file {
74
+                                    //TODO do something on write errors
75
+                                    let _ = Self::write_to_file(&path, &buf);
76
+                                }
77
+
78
+                                continue;
79
+                            }
80
+                        }
81
+                    }
82
+                },
83
+            }
84
+            result_tx.send((tile, None)).unwrap();
85
+        }
86
+    }
87
+
88
+    pub fn async_request(&mut self, tile: Tile, source: &TileSource, write_to_file: bool) {
89
+        if tile.zoom > source.max_tile_zoom() {
90
+            return;
91
+        }
92
+
93
+        if !self.pending.contains(&tile) {
94
+            self.pending.insert(tile);
95
+            self.request_tx.send((
96
+                tile,
97
+                source.remote_tile_url(tile),
98
+                source.local_tile_path(tile),
99
+                write_to_file
100
+            )).unwrap();
101
+        }
102
+    }
103
+
104
+    pub fn async_result(&mut self) -> Option<(Tile, DynamicImage)> {
105
+        match self.result_rx.try_recv() {
106
+            Err(_) => None,
107
+            Ok((tile, None)) => {
108
+                self.pending.remove(&tile);
109
+                None
110
+            },
111
+            Ok((tile, Some(img))) => {
112
+                self.pending.remove(&tile);
113
+                Some((tile, img))
114
+            },
115
+        }
116
+    }
117
+
118
+    pub fn get_sync(&mut self, tile: Tile, source: &TileSource, write_to_file: bool) -> Option<DynamicImage> {
119
+        match image::open(source.local_tile_path(tile)) {
120
+            Ok(img) => {
121
+                Some(img)
122
+            },
123
+            Err(_) => {
124
+                //TODO do not try to create a client every time when it failed before
125
+                if self.client.is_none() {
126
+                    self.client = Client::builder().build().ok();
127
+                }
128
+
129
+                if let Some(ref client) = self.client {
130
+                    println!("use client {:?}", tile);
131
+                    if let Ok(mut response) = client.get(&source.remote_tile_url(tile)).send() {
132
+                        let mut buf: Vec<u8> = vec![];
133
+                        response.copy_to(&mut buf).unwrap();
134
+                        if let Ok(img) = image::load_from_memory(&buf) {
135
+                            if write_to_file {
136
+                                let path = source.local_tile_path(tile);
137
+                                let _ = Self::write_to_file(path, &buf);
138
+                            }
139
+                            Some(img)
140
+                        } else {
141
+                            None
142
+                        }
143
+                    } else {
144
+                        None
145
+                    }
146
+                } else {
147
+                    None
148
+                }
149
+            },
150
+        }
151
+    }
152
+
153
+    fn write_to_file<P: AsRef<Path>>(path: P, img_data: &[u8]) -> ::std::io::Result<()> {
154
+
155
+        if let Some(dir) = path.as_ref().parent() {
156
+            ::std::fs::create_dir_all(dir)?;
157
+        }
158
+
159
+        //TODO remove
160
+        println!("write file {:?}", path.as_ref());
161
+
162
+        let mut file = File::create(path)?;
163
+        file.write_all(img_data)
164
+    }
165
+}

+ 71
- 0
src/tile_source.rs Näytä tiedosto

@@ -0,0 +1,71 @@
1
+use tile::Tile;
2
+use std::path::{Path, PathBuf};
3
+
4
+
5
+#[derive(Clone, Debug)]
6
+pub struct TileSource {
7
+    id: u32,
8
+    url_template: String,
9
+    directory: PathBuf,
10
+    max_zoom: u32,
11
+}
12
+
13
+#[derive(Copy, Clone, Debug)]
14
+pub struct TileSourceId {
15
+    id: u32,
16
+}
17
+
18
+impl TileSource {
19
+    pub fn new<S: Into<String>, P: Into<PathBuf>>(
20
+        id: u32,
21
+        url_template: S,
22
+        directory: P,
23
+        max_zoom: u32,
24
+    ) -> Self {
25
+        TileSource {
26
+            id: id,
27
+            url_template: url_template.into(),
28
+            directory: directory.into(),
29
+            max_zoom: max_zoom,
30
+        }
31
+    }
32
+
33
+    pub fn id(&self) -> TileSourceId {
34
+        TileSourceId {
35
+            id: self.id,
36
+        }
37
+    }
38
+
39
+    pub fn local_tile_path(&self, tile: Tile) -> PathBuf {
40
+
41
+        let mut path = PathBuf::from(&self.directory);
42
+        path.push(tile.zoom.to_string());
43
+        path.push(tile.tile_x.to_string());
44
+        path.push(tile.tile_y.to_string() + ".png");
45
+
46
+        path
47
+    }
48
+
49
+    pub fn remote_tile_url(&self, tile: Tile) -> String {
50
+        Self::fill_template(&self.url_template, tile)
51
+    }
52
+
53
+    pub fn max_tile_zoom(&self) -> u32 {
54
+        self.max_zoom
55
+    }
56
+
57
+    fn fill_template(template: &str, tile: Tile) -> String {
58
+        let x_str = tile.tile_x.to_string();
59
+        let y_str = tile.tile_y.to_string();
60
+        let z_str = tile.zoom.to_string();
61
+
62
+        //let len = (template.len() + x_str.len() + y_str.len() + z_str.len()).saturating_sub(9);
63
+
64
+        //TODO use the regex crate for templates or some other more elegant method
65
+        let string = template.replacen("{x}", &x_str, 1);
66
+        let string = string.replacen("{y}", &y_str, 1);
67
+        let string = string.replacen("{z}", &z_str, 1);
68
+
69
+        return string;
70
+    }
71
+}