Skip to content

Commit 387c173

Browse files
authored
Merge pull request #97 from yuiseki/main
refactor: modularize PMTiles functionality by splitting into dedicated files for processing, stats, types, and algorithms.
2 parents 213071d + c28afd2 commit 387c173

File tree

5 files changed

+535
-504
lines changed

5 files changed

+535
-504
lines changed

src/pmtiles/algo.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use crate::pmtiles::{
2+
Header,
3+
types::{Entry, HEADER_SIZE},
4+
};
5+
use anyhow::{Context, Result};
6+
use hilbert_2d::{Variant, h2xy_discrete, xy2h_discrete};
7+
use varint_rs::{VarintReader, VarintWriter};
8+
9+
pub fn histogram_bucket_index_pmtiles(
10+
value: u64,
11+
min_len: Option<u64>,
12+
max_len: Option<u64>,
13+
buckets: usize,
14+
) -> Option<usize> {
15+
if buckets == 0 {
16+
return None;
17+
}
18+
let min_len = min_len?;
19+
let max_len = max_len?;
20+
if min_len > max_len {
21+
return None;
22+
}
23+
let range = (max_len - min_len).max(1);
24+
let bucket_size = ((range as f64) / buckets as f64).ceil() as u64;
25+
let mut bucket = ((value.saturating_sub(min_len)) / bucket_size) as usize;
26+
if bucket >= buckets {
27+
bucket = buckets - 1;
28+
}
29+
Some(bucket)
30+
}
31+
32+
pub fn tile_id_from_xyz(z: u8, x: u32, y: u32) -> u64 {
33+
if z == 0 {
34+
return 0;
35+
}
36+
let order = z as usize;
37+
let hilbert = xy2h_discrete(x as usize, y as usize, order, Variant::Hilbert) as u64;
38+
let base_id = (pow4(z) - 1) / 3;
39+
base_id + hilbert
40+
}
41+
42+
pub fn tile_id_to_xyz(tile_id: u64) -> (u8, u32, u32) {
43+
if tile_id == 0 {
44+
return (0, 0, 0);
45+
}
46+
let mut z = 1u8;
47+
loop {
48+
let base_id = (pow4(z) - 1) / 3;
49+
let next_base = (pow4(z + 1) - 1) / 3;
50+
if tile_id < next_base {
51+
let idx = tile_id - base_id;
52+
let (x, y) = h2xy_discrete(idx as usize, z as usize, Variant::Hilbert);
53+
return (z, x as u32, y as u32);
54+
}
55+
z += 1;
56+
}
57+
}
58+
59+
pub fn pow4(z: u8) -> u64 {
60+
1u64 << (2 * (z as u64))
61+
}
62+
63+
pub fn splitmix64(mut x: u64) -> u64 {
64+
x = x.wrapping_add(0x9e3779b97f4a7c15);
65+
let mut z = x;
66+
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
67+
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
68+
z ^ (z >> 31)
69+
}
70+
71+
pub fn encode_directory(entries: &[Entry]) -> Result<Vec<u8>> {
72+
let mut buf = Vec::new();
73+
buf.write_usize_varint(entries.len())?;
74+
75+
let mut last_tile_id = 0u64;
76+
for entry in entries {
77+
let delta = entry.tile_id - last_tile_id;
78+
buf.write_u64_varint(delta)?;
79+
last_tile_id = entry.tile_id;
80+
}
81+
82+
for entry in entries {
83+
buf.write_u32_varint(entry.run_length)?;
84+
}
85+
86+
for entry in entries {
87+
buf.write_u32_varint(entry.length)?;
88+
}
89+
90+
for (idx, entry) in entries.iter().enumerate() {
91+
if idx == 0 {
92+
buf.write_u64_varint(entry.offset + 1)?;
93+
} else {
94+
let prev = &entries[idx - 1];
95+
let expected = prev.offset + prev.length as u64;
96+
if entry.offset == expected {
97+
buf.write_u64_varint(0)?;
98+
} else {
99+
buf.write_u64_varint(entry.offset + 1)?;
100+
}
101+
}
102+
}
103+
104+
Ok(buf)
105+
}
106+
107+
pub fn decode_directory(mut data: &[u8]) -> Result<Vec<Entry>> {
108+
let n_entries = data.read_usize_varint()?;
109+
let mut entries = vec![
110+
Entry {
111+
tile_id: 0,
112+
offset: 0,
113+
length: 0,
114+
run_length: 0,
115+
};
116+
n_entries
117+
];
118+
119+
let mut next_tile_id = 0u64;
120+
for entry in entries.iter_mut() {
121+
next_tile_id += data.read_u64_varint()?;
122+
entry.tile_id = next_tile_id;
123+
}
124+
125+
for entry in entries.iter_mut() {
126+
entry.run_length = data.read_u32_varint()?;
127+
}
128+
129+
for entry in entries.iter_mut() {
130+
entry.length = data.read_u32_varint()?;
131+
}
132+
133+
let mut last_entry: Option<Entry> = None;
134+
for entry in entries.iter_mut() {
135+
let offset = data.read_u64_varint()?;
136+
entry.offset = if offset == 0 {
137+
let prev = last_entry.as_ref().context("invalid directory entry")?;
138+
prev.offset + prev.length as u64
139+
} else {
140+
offset - 1
141+
};
142+
last_entry = Some(entry.clone());
143+
}
144+
145+
Ok(entries)
146+
}
147+
148+
pub fn build_header(
149+
root_length: u64,
150+
data_length: u64,
151+
tile_count: u64,
152+
min_zoom: u8,
153+
max_zoom: u8,
154+
) -> Header {
155+
Header {
156+
root_offset: HEADER_SIZE as u64,
157+
root_length,
158+
metadata_offset: 0,
159+
metadata_length: 0,
160+
leaf_offset: 0,
161+
leaf_length: 0,
162+
data_offset: HEADER_SIZE as u64 + root_length,
163+
data_length,
164+
n_addressed_tiles: tile_count,
165+
n_tile_entries: tile_count,
166+
n_tile_contents: tile_count,
167+
clustered: 0,
168+
internal_compression: 1,
169+
tile_compression: 1,
170+
tile_type: 0,
171+
min_zoom,
172+
max_zoom,
173+
min_longitude: (-180.0 * 10_000_000.0) as i32,
174+
min_latitude: (-85.0 * 10_000_000.0) as i32,
175+
max_longitude: (180.0 * 10_000_000.0) as i32,
176+
max_latitude: (85.0 * 10_000_000.0) as i32,
177+
center_zoom: 0,
178+
center_longitude: 0,
179+
center_latitude: 0,
180+
}
181+
}
182+
183+
#[allow(clippy::too_many_arguments)]
184+
pub fn build_header_with_metadata(
185+
root_length: u64,
186+
metadata_length: u64,
187+
data_length: u64,
188+
tile_count: u64,
189+
min_zoom: u8,
190+
max_zoom: u8,
191+
internal_compression: u8,
192+
tile_compression: u8,
193+
tile_type: u8,
194+
) -> Header {
195+
let root_offset = HEADER_SIZE as u64;
196+
let metadata_offset = if metadata_length == 0 {
197+
0
198+
} else {
199+
root_offset + root_length
200+
};
201+
let data_offset = if metadata_length == 0 {
202+
root_offset + root_length
203+
} else {
204+
metadata_offset + metadata_length
205+
};
206+
Header {
207+
root_offset,
208+
root_length,
209+
metadata_offset,
210+
metadata_length,
211+
leaf_offset: 0,
212+
leaf_length: 0,
213+
data_offset,
214+
data_length,
215+
n_addressed_tiles: tile_count,
216+
n_tile_entries: tile_count,
217+
n_tile_contents: tile_count,
218+
clustered: 0,
219+
internal_compression,
220+
tile_compression,
221+
tile_type,
222+
min_zoom,
223+
max_zoom,
224+
min_longitude: (-180.0 * 10_000_000.0) as i32,
225+
min_latitude: (-85.0 * 10_000_000.0) as i32,
226+
max_longitude: (180.0 * 10_000_000.0) as i32,
227+
max_latitude: (85.0 * 10_000_000.0) as i32,
228+
center_zoom: 0,
229+
center_longitude: 0,
230+
center_latitude: 0,
231+
}
232+
}

src/pmtiles/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub mod algo;
2+
pub mod processing;
3+
pub mod stats;
4+
pub mod types;
5+
6+
pub use self::algo::*;
7+
pub use self::processing::*;
8+
pub use self::stats::*;
9+
pub use self::types::*;

0 commit comments

Comments
 (0)