1use std::{
2 path::PathBuf,
3 sync::{Arc, mpsc},
4 thread,
5};
6
7use crate::{Tile, math};
8
9pub struct Client
11{
12 cache_dir: PathBuf,
13 sender: mpsc::Sender<(u32, u32, u32, PathBuf)>,
14}
15
16pub struct ClientBuilder
18{
19 user_agent: String,
20 url: String,
21 qualifier: String,
22 organization: String,
23 application: String,
24 tiles_cache: String,
25 on_download_complted: Box<dyn Fn() + Sync + Send>,
26}
27
28impl ClientBuilder
29{
30 pub fn new(user_agent: impl Into<String>, url: impl Into<String>) -> Self
33 {
34 Self {
35 user_agent: user_agent.into(),
36 url: url.into(),
37 qualifier: "com".into(),
38 organization: "cyloncore".into(),
39 application: "tiles-client".into(),
40 tiles_cache: "tiles_cache".into(),
41 on_download_complted: Box::new(|| {}),
42 }
43 }
44 pub fn application_metainfo(
46 mut self,
47 qualifier: impl Into<String>,
48 organization: impl Into<String>,
49 application: impl Into<String>,
50 ) -> Self
51 {
52 self.qualifier = qualifier.into();
53 self.organization = organization.into();
54 self.application = application.into();
55 self
56 }
57 pub fn tiles_cache(mut self, tiles_cache: String) -> Self
59 {
60 self.tiles_cache = tiles_cache;
61 self
62 }
63 pub fn build(self) -> Client
65 {
66 let (sender, receiver) = mpsc::channel::<(u32, u32, u32, PathBuf)>();
67 let user_agent = self.user_agent;
68 let on_download_completed = Arc::new(self.on_download_complted);
69 thread::spawn(move || {
70 while let Ok(msg) = receiver.recv()
71 {
72 let url = self
73 .url
74 .replace("{x}", &msg.0.to_string())
75 .replace("{y}", &msg.1.to_string())
76 .replace("{z}", &msg.2.to_string());
77
78 let mut request = ehttp::Request::get(url);
79 request.headers.insert("User-Agent", user_agent.to_owned());
80 let on_download_completed = on_download_completed.clone();
81 ehttp::fetch(
82 request,
83 move |result: ehttp::Result<ehttp::Response>| match result
84 {
85 Ok(result) =>
86 {
87 if result.ok
88 {
89 if let Some(parent) = msg.3.parent()
90 {
91 std::fs::create_dir_all(parent).unwrap();
92 }
93 std::fs::write(msg.3, &result.bytes).expect("Failed to write file");
94 on_download_completed();
95 }
96 else
97 {
98 log::error!(
99 "Response: {:?} ({:?}) for {}",
100 result.status_text,
101 result.status,
102 result.url
103 );
104 }
105 }
106 Err(e) =>
107 {
108 log::error!("Error: {:?}", e);
109 }
110 },
111 );
112 }
113 });
114 let cache_dir =
115 directories::ProjectDirs::from(&self.qualifier, &self.organization, &self.application)
116 .unwrap()
117 .cache_dir()
118 .join(&self.tiles_cache);
119 Client { cache_dir, sender }
120 }
121}
122
123impl Client
124{
125 fn tile_filename(&self, x: u32, y: u32, z: u32) -> PathBuf
127 {
128 self
129 .cache_dir
130 .join(z.to_string())
131 .join(y.to_string())
132 .join(format!("{}.png", x))
133 }
134
135 fn clamp_tile_index(index: Option<u32>, max_index: u32) -> u32
136 {
137 index.unwrap_or(0).clamp(0, max_index - 1)
138 }
139
140 pub fn tiles(&self, rect: geo::Rect, zoom_level: u32, download: bool) -> Vec<Tile>
142 {
143 let max_index = 2u32.pow(zoom_level);
144
145 let x_min = Self::clamp_tile_index(math::lon_to_tile_x(rect.min().x, zoom_level), max_index);
146 let x_max = Self::clamp_tile_index(math::lon_to_tile_x(rect.max().x, zoom_level), max_index);
147 let y_min = Self::clamp_tile_index(math::lat_to_tile_y(rect.max().y, zoom_level), max_index);
148 let y_max = Self::clamp_tile_index(math::lat_to_tile_y(rect.min().y, zoom_level), max_index);
149
150 let mut tiles = Vec::new();
151 for tile_x in x_min..=x_max
152 {
153 for tile_y in y_min..=y_max
154 {
155 let min_lon = math::tile_x_to_lon(tile_x, zoom_level);
156 let max_lon = math::tile_x_to_lon(tile_x + 1, zoom_level);
157 let min_lat = math::tile_y_to_lat(tile_y, zoom_level);
158 let max_lat = math::tile_y_to_lat(tile_y + 1, zoom_level);
159 let tile = Tile {
160 tile_x,
161 tile_y,
162 tile_z: zoom_level,
163 filename: self.tile_filename(tile_x, tile_y, zoom_level),
164 bounding_box: geo::Rect::new(
165 geo::Coord {
166 x: min_lon,
167 y: min_lat,
168 },
169 geo::Coord {
170 x: max_lon,
171 y: max_lat,
172 },
173 ),
174 image: Default::default(),
175 };
176 if !tile.is_cached() && download
177 {
178 self
179 .sender
180 .send((
181 tile.tile_x(),
182 tile.tile_y(),
183 tile.tile_z(),
184 tile.filename().to_owned(),
185 ))
186 .unwrap();
187 }
188 tiles.push(tile);
189 }
190 }
191 tiles
192 }
193}