|
|
@@ -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(¢er_diff_a).unwrap_or(Ordering::Equal)
|
|
|
245
|
+ }
|
|
|
246
|
+ },
|
|
|
247
|
+ }
|
|
|
248
|
+}
|