Pārlūkot izejas kodu

Prioritize tile loading based on current view

Johannes Hofmann 8 gadus atpakaļ
vecāks
revīzija
e3607d7c78
5 mainītis faili ar 155 papildinājumiem un 40 dzēšanām
  1. 20
    1
      src/coord.rs
  2. 7
    2
      src/map_view.rs
  3. 7
    1
      src/map_view_gl.rs
  4. 5
    1
      src/tile_cache.rs
  5. 116
    35
      src/tile_loader.rs

+ 20
- 1
src/coord.rs Parādīt failu

@@ -1,4 +1,5 @@
1 1
 use std::f64::consts::PI;
2
+use tile_source::TileSourceId;
2 3
 
3 4
 /// A position in map coordinates.
4 5
 /// Valid values for x and y lie in the interval [0.0, 1.0].
@@ -131,11 +132,21 @@ impl TileCoord {
131 132
         self.x >= 0 && self.x < num_tiles
132 133
     }
133 134
 
134
-    pub fn map_coord(&self) -> MapCoord {
135
+    // Return the MapCoord of the top left corner of the current tile.
136
+    pub fn map_coord_north_west(&self) -> MapCoord {
135 137
         let inv_zoom_factor = f64::powi(2.0, -(self.zoom as i32));
136 138
         MapCoord::new(f64::from(self.x) * inv_zoom_factor, f64::from(self.y) * inv_zoom_factor)
137 139
     }
138 140
 
141
+    // Return the MapCoord of the center of the current tile.
142
+    pub fn map_coord_center(&self) -> MapCoord {
143
+        let inv_zoom_factor = f64::powi(2.0, -(self.zoom as i32));
144
+        MapCoord::new(
145
+            (f64::from(self.x) + 0.5) * inv_zoom_factor,
146
+            (f64::from(self.y) + 0.5) * inv_zoom_factor,
147
+        )
148
+    }
149
+
139 150
     pub fn parent(&self, distance: u32) -> Option<(TileCoord, SubTileCoord)> {
140 151
         if distance > self.zoom {
141 152
             None
@@ -169,3 +180,11 @@ impl TileCoord {
169 180
         i32::pow(2, zoom)
170 181
     }
171 182
 }
183
+
184
+//TODO include width and height of view rect to determine visibility
185
+#[derive(Copy, Clone, Debug, PartialEq)]
186
+pub struct View {
187
+    pub source_id: TileSourceId,
188
+    pub zoom: u32,
189
+    pub center: MapCoord,
190
+}

+ 7
- 2
src/map_view.rs Parādīt failu

@@ -49,11 +49,11 @@ impl MapView {
49 49
     }
50 50
 
51 51
     pub fn tile_screen_position(&self, tile: &TileCoord) -> ScreenCoord {
52
-        self.map_to_screen_coord(tile.map_coord())
52
+        self.map_to_screen_coord(tile.map_coord_north_west())
53 53
     }
54 54
 
55 55
     pub fn visible_tiles(&self, snap_to_pixel: bool) -> Vec<VisibleTile> {
56
-        let uzoom = self.zoom2.floor().max(0.0) as u32;
56
+        let uzoom = self.zoom_level();
57 57
         let top_left_tile = self.top_left_coord().on_tile_at_zoom(uzoom);
58 58
         let mut top_left_tile_screen_coord = self.tile_screen_position(&top_left_tile);
59 59
         let tile_screen_size = f64::powf(2.0, self.zoom2 - f64::from(uzoom)) * f64::from(self.tile_size);
@@ -91,6 +91,11 @@ impl MapView {
91 91
         visible_tiles
92 92
     }
93 93
 
94
+    //TODO make zoom level configurable
95
+    pub fn zoom_level(&self) -> u32 {
96
+        self.zoom2.floor().max(0.0) as u32
97
+    }
98
+
94 99
     pub fn set_size(&mut self, width: f64, height: f64) {
95 100
         self.width = width;
96 101
         self.height = height;

+ 7
- 1
src/map_view_gl.rs Parādīt failu

@@ -2,7 +2,7 @@ use ::context;
2 2
 use ::std::ffi::CStr;
3 3
 use buffer::{Buffer, DrawMode};
4 4
 use context::Context;
5
-use coord::ScreenCoord;
5
+use coord::{ScreenCoord, View};
6 6
 use image;
7 7
 use map_view::MapView;
8 8
 use program::Program;
@@ -92,6 +92,12 @@ impl<'a> MapViewGl<'a> {
92 92
     }
93 93
 
94 94
     pub fn draw(&mut self, source: &TileSource) {
95
+        self.tile_cache.set_view_location(View {
96
+            source_id: source.id(),
97
+            zoom: self.map_view.zoom_level(),
98
+            center: self.map_view.center,
99
+        });
100
+
95 101
         {
96 102
             let visible_tiles = self.map_view.visible_tiles(true);
97 103
             let textured_visible_tiles = self.tile_cache_gl.textured_visible_tiles(

+ 5
- 1
src/tile_cache.rs Parādīt failu

@@ -1,6 +1,6 @@
1 1
 use image;
2 2
 use linked_hash_map::{Entry, LinkedHashMap};
3
-use coord::TileCoord;
3
+use coord::{TileCoord, View};
4 4
 use tile::Tile;
5 5
 use tile_loader::TileLoader;
6 6
 use tile_source::TileSource;
@@ -90,6 +90,10 @@ impl TileCache {
90 90
 
91 91
         self.map.get(&tile)
92 92
     }
93
+
94
+    pub fn set_view_location(&mut self, view: View) {
95
+        self.loader.set_view_location(view);
96
+    }
93 97
 }
94 98
 
95 99
 impl ::std::fmt::Debug for TileCache {

+ 116
- 35
src/tile_loader.rs Parādīt failu

@@ -1,11 +1,14 @@
1
-use coord::TileCoord;
1
+use coord::{TileCoord, View};
2 2
 use image::DynamicImage;
3 3
 use image;
4 4
 use reqwest::Client;
5
+use std::cmp::Ordering;
6
+use std::cmp;
5 7
 use std::collections::hash_set::HashSet;
6 8
 use std::fs::File;
7 9
 use std::io::Write;
8 10
 use std::path::{Path, PathBuf};
11
+use std::sync::mpsc::TryRecvError;
9 12
 use std::sync::mpsc;
10 13
 use std::thread;
11 14
 use tile::Tile;
@@ -18,7 +21,7 @@ use tile_source::TileSource;
18 21
 pub struct TileLoader {
19 22
     client: Option<Client>,
20 23
     join_handle: thread::JoinHandle<()>,
21
-    request_tx: mpsc::Sender<(Tile, String, PathBuf, bool)>,
24
+    request_tx: mpsc::Sender<LoaderMessage>,
22 25
     result_rx: mpsc::Receiver<(Tile, Option<DynamicImage>)>,
23 26
     pending: HashSet<Tile>,
24 27
 }
@@ -40,48 +43,88 @@ impl TileLoader {
40 43
     }
41 44
 
42 45
     fn work<F>(
43
-        request_rx: mpsc::Receiver<(Tile, String, PathBuf, bool)>,
46
+        request_rx: mpsc::Receiver<LoaderMessage>,
44 47
         result_tx: mpsc::Sender<(Tile, Option<DynamicImage>)>,
45 48
         notice_func: F,
46 49
     )
47 50
         where F: Fn(Tile) + Sync + Send + 'static,
48 51
     {
49 52
         let mut client_opt = None;
50
-        while let Ok((tile, url, path, write_to_file)) = request_rx.recv() {
51
-            println!("work {:?} {:?}", tile, path);
52
-            match image::open(&path) {
53
-                Ok(img) => {
54
-                    result_tx.send((tile, Some(img))).unwrap();
55
-                    notice_func(tile);
56
-                    continue;
53
+        let mut queue: Vec<(Tile, String, PathBuf, bool)> = vec![];
54
+
55
+        'outer: while let Ok(message) = request_rx.recv() {
56
+            let mut view_opt: Option<View> = None;
57
+
58
+            match message {
59
+                LoaderMessage::SetViewLocation{view} => {
60
+                    view_opt = Some(view);
57 61
                 },
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
+                LoaderMessage::GetTile{tile, url, path, write_to_file} => {
63
+                    queue.push((tile, url, path, write_to_file));
64
+                }
65
+            }
66
+
67
+            loop {
68
+                loop {
69
+                    match request_rx.try_recv() {
70
+                        Ok(LoaderMessage::SetViewLocation{view}) => {
71
+                            view_opt = Some(view);
72
+                        },
73
+                        Ok(LoaderMessage::GetTile{tile, url, path, write_to_file}) => {
74
+                            queue.push((tile, url, path, write_to_file));
75
+                        },
76
+                        Err(TryRecvError::Empty) => break,
77
+                        Err(TryRecvError::Disconnected) => break 'outer,
62 78
                     }
79
+                }
63 80
 
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) {
81
+                if let Some(view) = view_opt {
82
+                    //TODO sort queue
83
+                    queue.as_mut_slice().sort_by(|&(tile_a, _, _, _), &(tile_b, _, _, _)| {
84
+                        compare_tiles(tile_a, tile_b, view)
85
+                    });
86
+                }
87
+
88
+                match queue.pop() {
89
+                    None => break,
90
+                    Some((tile, url, path, write_to_file)) => {
91
+                        println!("queue {:?} {:?}", tile, path);
92
+                        match image::open(&path) {
93
+                            Ok(img) => {
70 94
                                 result_tx.send((tile, Some(img))).unwrap();
71 95
                                 notice_func(tile);
72
-
73
-                                if write_to_file {
74
-                                    //TODO do something on write errors
75
-                                    let _ = Self::write_to_file(&path, &buf);
96
+                                continue;
97
+                            },
98
+                            Err(_) => {
99
+                                //TODO do not try to create a client every time when it failed before
100
+                                if client_opt.is_none() {
101
+                                    client_opt = Client::builder().build().ok();
76 102
                                 }
77 103
 
78
-                                continue;
79
-                            }
104
+                                if let Some(ref client) = client_opt {
105
+                                    println!("use client {:?}", tile);
106
+                                    if let Ok(mut response) = client.get(&url).send() {
107
+                                        let mut buf: Vec<u8> = vec![];
108
+                                        response.copy_to(&mut buf).unwrap();
109
+                                        if let Ok(img) = image::load_from_memory(&buf) {
110
+                                            result_tx.send((tile, Some(img))).unwrap();
111
+                                            notice_func(tile);
112
+
113
+                                            if write_to_file {
114
+                                                //TODO do something on write errors
115
+                                                let _ = Self::write_to_file(&path, &buf);
116
+                                            }
117
+
118
+                                            continue;
119
+                                        }
120
+                                    }
121
+                                }
122
+                            },
80 123
                         }
81
-                    }
82
-                },
124
+                        result_tx.send((tile, None)).unwrap();
125
+                    },
126
+                }
83 127
             }
84
-            result_tx.send((tile, None)).unwrap();
85 128
         }
86 129
     }
87 130
 
@@ -94,12 +137,12 @@ impl TileLoader {
94 137
 
95 138
         if !self.pending.contains(&tile) {
96 139
             self.pending.insert(tile);
97
-            self.request_tx.send((
98
-                tile,
99
-                source.remote_tile_url(tile_coord),
100
-                source.local_tile_path(tile_coord),
101
-                write_to_file
102
-            )).unwrap();
140
+            self.request_tx.send(LoaderMessage::GetTile{
141
+                tile: tile,
142
+                url: source.remote_tile_url(tile_coord),
143
+                path: source.local_tile_path(tile_coord),
144
+                write_to_file: write_to_file,
145
+            }).unwrap();
103 146
         }
104 147
     }
105 148
 
@@ -152,6 +195,12 @@ impl TileLoader {
152 195
         }
153 196
     }
154 197
 
198
+    pub fn set_view_location(&mut self, view: View) {
199
+        self.request_tx.send(LoaderMessage::SetViewLocation{
200
+            view: view,
201
+        }).unwrap();
202
+    }
203
+
155 204
     fn write_to_file<P: AsRef<Path>>(path: P, img_data: &[u8]) -> ::std::io::Result<()> {
156 205
 
157 206
         if let Some(dir) = path.as_ref().parent() {
@@ -165,3 +214,35 @@ impl TileLoader {
165 214
         file.write_all(img_data)
166 215
     }
167 216
 }
217
+
218
+enum LoaderMessage {
219
+    GetTile{tile: Tile, url: String, path: PathBuf, write_to_file: bool},
220
+    SetViewLocation{view: View},
221
+}
222
+
223
+fn compare_tiles(a: Tile, b: Tile, view: View) -> Ordering {
224
+    let source_a = view.source_id == a.source_id;
225
+    let source_b = view.source_id == b.source_id;
226
+
227
+    match (source_a, source_b) {
228
+        (true, false) => Ordering::Greater,
229
+        (false, true) => Ordering::Less,
230
+        _ => {
231
+            let zoom_diff_a = cmp::max(a.coord.zoom, view.zoom) - cmp::min(a.coord.zoom, view.zoom);
232
+            let zoom_diff_b = cmp::max(b.coord.zoom, view.zoom) - cmp::min(b.coord.zoom, view.zoom);
233
+
234
+            if zoom_diff_a < zoom_diff_b {
235
+                Ordering::Greater
236
+            } else if zoom_diff_a > zoom_diff_b {
237
+                Ordering::Less
238
+            } else {
239
+                let map_a = a.coord.map_coord_center();
240
+                let map_b = b.coord.map_coord_center();
241
+                let center_diff_a = (view.center.x - map_a.x).hypot(view.center.y - map_a.y);
242
+                let center_diff_b = (view.center.x - map_b.x).hypot(view.center.y - map_b.y);
243
+
244
+                center_diff_b.partial_cmp(&center_diff_a).unwrap_or(Ordering::Equal)
245
+            }
246
+        },
247
+    }
248
+}