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::{
141 encode_tile_with, ColumnarLayer, EncoderConfig, GeometryColumn,
142 };
143 use stt_core::curve::BlobOrdering;
144 use stt_core::pack::PackWriter;
145 use stt_core::tile::TileId;
146
147 fn tiny_tile(seed: u64, w: &PackWriter) -> Vec<u8> {
150 let n = 4usize;
151 let cfg = EncoderConfig {
152 format_version: w.format_version(),
153 template_collector: Some(w.template_collector()),
154 ..EncoderConfig::default()
155 };
156 encode_tile_with(
157 &[ColumnarLayer {
158 name: "default".to_string(),
159 feature_ids: (0..n as u64).map(|i| seed * 100 + i).collect(),
160 start_times: vec![0; n],
161 end_times: vec![100; n],
162 geometry: GeometryColumn::Point(vec![[-73.6, 45.5]; n]),
163 vertex_times: None,
164 vertex_values: None,
165 triangles: None,
166 vertex_value_matrix: None,
167 properties: vec![],
168 }],
169 &cfg,
170 )
171 .unwrap()
172 }
173
174 #[test]
177 fn open_by_dir_or_manifest_and_read_back() {
178 let dir = tempfile::tempdir().unwrap();
179 let out = dir.path().join("dataset");
180 let mut w = PackWriter::create(&out, BlobOrdering::Auto, 64 * 1024).unwrap();
181 let payloads: Vec<Vec<u8>> = (0..3).map(|k| tiny_tile(k, &w)).collect();
182 for (k, p) in payloads.iter().enumerate() {
183 let id = TileId::new(7, k as u32, 0, 0);
184 w.add_tile_full(&id, 0, 3_599_999, Some(0), 4, Some(3_600_000), p)
185 .unwrap();
186 }
187 let meta =
188 stt_core::metadata::Metadata::new("packed-open").with_temporal_bucket_ms(3_600_000);
189 w.finalize(&meta).unwrap();
190
191 let by_dir = PackedTileset::open(&out).unwrap();
192 let by_manifest = PackedTileset::open(out.join("manifest.json")).unwrap();
193 for ts in [&by_dir, &by_manifest] {
194 assert_eq!(ts.name(), "packed-open");
195 assert_eq!(ts.tile_count(), 3);
196 assert_eq!(ts.feature_count(), 12);
197 assert_eq!(ts.temporal_bucket_ms(), 3_600_000);
198 assert_eq!(ts.entries().len(), 3);
199 assert!(!ts.is_paged());
200 assert!(ts.pack_count() >= 1);
201 }
202
203 let e = &by_dir.entries()[0];
205 let payload = by_dir.read_payload(e).unwrap();
206 assert!(payloads.contains(&payload));
207 let layers = by_dir.read_layers(e).unwrap();
208 assert_eq!(layers.len(), 1);
209 assert_eq!(layers[0].name, "default");
210 assert_eq!(layers[0].batch.num_rows(), 4);
211 }
212
213 #[test]
216 fn paged_layout_reads_like_single() {
217 let dir = tempfile::tempdir().unwrap();
218 let out = dir.path().join("paged");
219 let mut w = PackWriter::create(&out, BlobOrdering::Auto, 64 * 1024)
220 .unwrap()
221 .with_paging(Some(2));
222 for k in 0..6u64 {
223 let p = tiny_tile(k, &w);
224 let id = TileId::new(9, k as u32, 0, 0);
225 w.add_tile_full(&id, 0, 100, None, 4, None, &p).unwrap();
226 }
227 w.finalize(&stt_core::metadata::Metadata::new("paged-open"))
228 .unwrap();
229
230 let ts = PackedTileset::open(&out).unwrap();
231 assert!(ts.is_paged());
232 assert_eq!(ts.entries().len(), 6);
233 for e in ts.entries() {
234 assert_eq!(ts.read_layers(e).unwrap()[0].batch.num_rows(), 4);
235 }
236 }
237}