@@ -9,6 +9,9 @@ use reqwest::{Client, IntoUrl};
9
9
#[ cfg( any( feature = "http-async" , feature = "mmap-async-tokio" ) ) ]
10
10
use tokio:: io:: AsyncReadExt ;
11
11
12
+ use crate :: cache:: SearchResult ;
13
+ #[ cfg( any( feature = "http-async" , feature = "mmap-async-tokio" ) ) ]
14
+ use crate :: cache:: { DirectoryCache , NoCache } ;
12
15
use crate :: directory:: { Directory , Entry } ;
13
16
use crate :: error:: Error ;
14
17
use crate :: header:: { HEADER_SIZE , MAX_INITIAL_BYTES } ;
@@ -19,17 +22,27 @@ use crate::mmap::MmapBackend;
19
22
use crate :: tile:: tile_id;
20
23
use crate :: { Compression , Header } ;
21
24
22
- pub struct AsyncPmTilesReader < B > {
25
+ pub struct AsyncPmTilesReader < B , C > {
23
26
backend : B ,
27
+ cache : C ,
24
28
header : Header ,
25
29
root_directory : Directory ,
26
30
}
27
31
28
- impl < B : AsyncBackend + Sync + Send > AsyncPmTilesReader < B > {
29
- /// Creates a new reader from a specified source and validates the provided PMTiles archive is valid.
32
+ impl < B : AsyncBackend + Sync + Send > AsyncPmTilesReader < B , NoCache > {
33
+ /// Creates a new cached reader from a specified source and validates the provided PMTiles archive is valid.
30
34
///
31
35
/// Note: Prefer using new_with_* methods.
32
36
pub async fn try_from_source ( backend : B ) -> Result < Self , Error > {
37
+ Self :: try_from_cached_source ( backend, NoCache ) . await
38
+ }
39
+ }
40
+
41
+ impl < B : AsyncBackend + Sync + Send , C : DirectoryCache + Sync + Send > AsyncPmTilesReader < B , C > {
42
+ /// Creates a new reader from a specified source and validates the provided PMTiles archive is valid.
43
+ ///
44
+ /// Note: Prefer using new_with_* methods.
45
+ pub async fn try_from_cached_source ( backend : B , cache : C ) -> Result < Self , Error > {
33
46
// Read the first 127 and up to 16,384 bytes to ensure we can initialize the header and root directory.
34
47
let mut initial_bytes = backend. read ( 0 , MAX_INITIAL_BYTES ) . await ?;
35
48
if initial_bytes. len ( ) < HEADER_SIZE {
@@ -47,11 +60,14 @@ impl<B: AsyncBackend + Sync + Send> AsyncPmTilesReader<B> {
47
60
48
61
Ok ( Self {
49
62
backend,
63
+ cache,
50
64
header,
51
65
root_directory,
52
66
} )
53
67
}
68
+ }
54
69
70
+ impl < B : AsyncBackend + Sync + Send , C : DirectoryCache + Sync + Send > AsyncPmTilesReader < B , C > {
55
71
/// Fetches tile bytes from the archive.
56
72
pub async fn get_tile ( & self , z : u8 , x : u64 , y : u64 ) -> Option < Bytes > {
57
73
let tile_id = tile_id ( z, x, y) ;
@@ -137,11 +153,21 @@ impl<B: AsyncBackend + Sync + Send> AsyncPmTilesReader<B> {
137
153
// the recursion is done as two functions because it is a bit cleaner,
138
154
// and it allows directory to be cached later without cloning it first.
139
155
let offset = ( self . header . leaf_offset + entry. offset ) as _ ;
140
- let length = entry. length as _ ;
141
- let dir = self . read_directory ( offset, length) . await . ok ( ) ?;
142
- let entry = dir. find_tile_id ( tile_id) ;
143
156
144
- if let Some ( entry) = entry {
157
+ let entry = match self . cache . get_dir_entry ( offset, tile_id) {
158
+ SearchResult :: NotCached => {
159
+ // Cache miss - read from backend
160
+ let length = entry. length as _ ;
161
+ let dir = self . read_directory ( offset, length) . await . ok ( ) ?;
162
+ let entry = dir. find_tile_id ( tile_id) . cloned ( ) ;
163
+ self . cache . insert_dir ( offset, dir) ;
164
+ entry
165
+ }
166
+ SearchResult :: NotFound => None ,
167
+ SearchResult :: Found ( entry) => Some ( entry) ,
168
+ } ;
169
+
170
+ if let Some ( ref entry) = entry {
145
171
if entry. is_leaf ( ) {
146
172
return if depth <= 4 {
147
173
self . find_entry_rec ( tile_id, entry, depth + 1 ) . await
@@ -151,7 +177,7 @@ impl<B: AsyncBackend + Sync + Send> AsyncPmTilesReader<B> {
151
177
}
152
178
}
153
179
154
- entry. cloned ( )
180
+ entry
155
181
}
156
182
157
183
async fn read_directory ( & self , offset : usize , length : usize ) -> Result < Directory , Error > {
@@ -183,26 +209,50 @@ impl<B: AsyncBackend + Sync + Send> AsyncPmTilesReader<B> {
183
209
}
184
210
185
211
#[ cfg( feature = "http-async" ) ]
186
- impl AsyncPmTilesReader < HttpBackend > {
212
+ impl AsyncPmTilesReader < HttpBackend , NoCache > {
187
213
/// Creates a new PMTiles reader from a URL using the Reqwest backend.
188
214
///
189
215
/// Fails if [url] does not exist or is an invalid archive. (Note: HTTP requests are made to validate it.)
190
216
pub async fn new_with_url < U : IntoUrl > ( client : Client , url : U ) -> Result < Self , Error > {
217
+ Self :: new_with_cached_url ( client, url, NoCache ) . await
218
+ }
219
+ }
220
+
221
+ #[ cfg( feature = "http-async" ) ]
222
+ impl < C : DirectoryCache + Sync + Send > AsyncPmTilesReader < HttpBackend , C > {
223
+ /// Creates a new PMTiles reader with cache from a URL using the Reqwest backend.
224
+ ///
225
+ /// Fails if [url] does not exist or is an invalid archive. (Note: HTTP requests are made to validate it.)
226
+ pub async fn new_with_cached_url < U : IntoUrl > (
227
+ client : Client ,
228
+ url : U ,
229
+ cache : C ,
230
+ ) -> Result < Self , Error > {
191
231
let backend = HttpBackend :: try_from ( client, url) ?;
192
232
193
- Self :: try_from_source ( backend) . await
233
+ Self :: try_from_cached_source ( backend, cache ) . await
194
234
}
195
235
}
196
236
197
237
#[ cfg( feature = "mmap-async-tokio" ) ]
198
- impl AsyncPmTilesReader < MmapBackend > {
238
+ impl AsyncPmTilesReader < MmapBackend , NoCache > {
199
239
/// Creates a new PMTiles reader from a file path using the async mmap backend.
200
240
///
201
241
/// Fails if [p] does not exist or is an invalid archive.
202
242
pub async fn new_with_path < P : AsRef < Path > > ( path : P ) -> Result < Self , Error > {
243
+ Self :: new_with_cached_path ( path, NoCache ) . await
244
+ }
245
+ }
246
+
247
+ #[ cfg( feature = "mmap-async-tokio" ) ]
248
+ impl < C : DirectoryCache + Sync + Send > AsyncPmTilesReader < MmapBackend , C > {
249
+ /// Creates a new cached PMTiles reader from a file path using the async mmap backend.
250
+ ///
251
+ /// Fails if [p] does not exist or is an invalid archive.
252
+ pub async fn new_with_cached_path < P : AsRef < Path > > ( path : P , cache : C ) -> Result < Self , Error > {
203
253
let backend = MmapBackend :: try_from ( path) . await ?;
204
254
205
- Self :: try_from_source ( backend) . await
255
+ Self :: try_from_cached_source ( backend, cache ) . await
206
256
}
207
257
}
208
258
0 commit comments