1use anyhow::{Context, Result};
12use std::fs;
13use std::path::{Path, PathBuf};
14use stt_core::TileEntry;
15use stt_core::arrow_tile::DecodedLayer;
16use stt_core::metadata::Metadata;
17use stt_core::pack::{Manifest, PackedReader};
18use stt_core::types::TimeRange;
19
20pub struct PackedTileset {
27 reader: PackedReader,
28 manifest: Manifest,
29 manifest_path: PathBuf,
30}
31
32impl PackedTileset {
33 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
36 let path = path.as_ref();
37 let manifest_path = if path.is_dir() {
38 path.join("manifest.json")
39 } else {
40 path.to_path_buf()
41 };
42 let manifest = Manifest::from_json_bytes(
43 &fs::read(&manifest_path)
44 .with_context(|| format!("reading {}", manifest_path.display()))?,
45 )?;
46 let reader = PackedReader::open(&manifest_path)
47 .with_context(|| format!("opening packed dataset {}", manifest_path.display()))?;
48 Ok(Self {
49 reader,
50 manifest,
51 manifest_path,
52 })
53 }
54
55 pub fn manifest_path(&self) -> &Path {
57 &self.manifest_path
58 }
59
60 pub fn manifest(&self) -> &Manifest {
62 &self.manifest
63 }
64
65 pub fn metadata(&self) -> &Metadata {
67 self.reader.metadata()
68 }
69
70 pub fn name(&self) -> &str {
72 &self.reader.metadata().name
73 }
74
75 pub fn zoom_range(&self) -> (u8, u8) {
77 let m = self.reader.metadata();
78 (m.min_zoom, m.max_zoom)
79 }
80
81 pub fn time_range(&self) -> TimeRange {
83 self.reader.metadata().time_range
84 }
85
86 pub fn tile_count(&self) -> u64 {
88 self.reader.metadata().tile_count
89 }
90
91 pub fn feature_count(&self) -> u64 {
94 self.reader.metadata().feature_count
95 }
96
97 pub fn temporal_bucket_ms(&self) -> u64 {
99 self.reader.metadata().temporal_bucket_ms
100 }
101
102 pub fn pack_count(&self) -> usize {
104 self.manifest.packs.len()
105 }
106
107 pub fn is_paged(&self) -> bool {
111 self.manifest.directory.is_paged()
112 }
113
114 pub fn entries(&self) -> &[TileEntry] {
122 self.reader.entries()
123 }
124
125 pub fn read_payload(&self, entry: &TileEntry) -> Result<Vec<u8>> {
127 Ok(self.reader.read_payload(entry)?)
128 }
129
130 pub fn read_layers(&self, entry: &TileEntry) -> Result<Vec<DecodedLayer>> {
133 Ok(self.reader.read_layers(entry)?)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use stt_core::arrow_tile::{encode_tile, ColumnarLayer, GeometryColumn};
141 use stt_core::curve::BlobOrdering;
142 use stt_core::pack::PackWriter;
143 use stt_core::tile::TileId;
144
145 fn tiny_tile(seed: u64) -> Vec<u8> {
146 let n = 4usize;
147 encode_tile(&[ColumnarLayer {
148 name: "default".to_string(),
149 feature_ids: (0..n as u64).map(|i| seed * 100 + i).collect(),
150 start_times: vec![0; n],
151 end_times: vec![100; n],
152 geometry: GeometryColumn::Point(vec![[-73.6, 45.5]; n]),
153 vertex_times: None,
154 vertex_values: None,
155 triangles: None,
156 vertex_value_matrix: None,
157 properties: vec![],
158 }])
159 .unwrap()
160 }
161
162 #[test]
165 fn open_by_dir_or_manifest_and_read_back() {
166 let dir = tempfile::tempdir().unwrap();
167 let out = dir.path().join("dataset");
168 let mut w = PackWriter::create(&out, BlobOrdering::Auto, 64 * 1024).unwrap();
169 let payloads: Vec<Vec<u8>> = (0..3).map(tiny_tile).collect();
170 for (k, p) in payloads.iter().enumerate() {
171 let id = TileId::new(7, k as u32, 0, 0);
172 w.add_tile_full(&id, 0, 3_599_999, Some(0), 4, Some(3_600_000), p)
173 .unwrap();
174 }
175 let meta =
176 stt_core::metadata::Metadata::new("packed-open").with_temporal_bucket_ms(3_600_000);
177 w.finalize(&meta).unwrap();
178
179 let by_dir = PackedTileset::open(&out).unwrap();
180 let by_manifest = PackedTileset::open(out.join("manifest.json")).unwrap();
181 for ts in [&by_dir, &by_manifest] {
182 assert_eq!(ts.name(), "packed-open");
183 assert_eq!(ts.tile_count(), 3);
184 assert_eq!(ts.feature_count(), 12);
185 assert_eq!(ts.temporal_bucket_ms(), 3_600_000);
186 assert_eq!(ts.entries().len(), 3);
187 assert!(!ts.is_paged());
188 assert!(ts.pack_count() >= 1);
189 }
190
191 let e = &by_dir.entries()[0];
193 let payload = by_dir.read_payload(e).unwrap();
194 assert!(payloads.contains(&payload));
195 let layers = by_dir.read_layers(e).unwrap();
196 assert_eq!(layers.len(), 1);
197 assert_eq!(layers[0].name, "default");
198 assert_eq!(layers[0].batch.num_rows(), 4);
199 }
200
201 #[test]
204 fn paged_layout_reads_like_single() {
205 let dir = tempfile::tempdir().unwrap();
206 let out = dir.path().join("paged");
207 let mut w = PackWriter::create(&out, BlobOrdering::Auto, 64 * 1024)
208 .unwrap()
209 .with_paging(Some(2));
210 for k in 0..6u64 {
211 let p = tiny_tile(k);
212 let id = TileId::new(9, k as u32, 0, 0);
213 w.add_tile_full(&id, 0, 100, None, 4, None, &p).unwrap();
214 }
215 w.finalize(&stt_core::metadata::Metadata::new("paged-open"))
216 .unwrap();
217
218 let ts = PackedTileset::open(&out).unwrap();
219 assert!(ts.is_paged());
220 assert_eq!(ts.entries().len(), 6);
221 for e in ts.entries() {
222 assert_eq!(ts.read_layers(e).unwrap()[0].batch.num_rows(), 4);
223 }
224 }
225}