pub mod cesium3dtiles {
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::Write;
use std::path::Path;
use bitvec::prelude as bv;
use log::{debug, error, warn};
use morton_encoding::morton_encode;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::coordinates::RootEnuFrame;
use crate::proj::Proj;
use crate::spatial_structs::{Bbox, CellId, QuadTree, QuadTreeNodeId, SquareGrid};
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Tileset {
asset: Asset,
geometric_error: GeometricError,
root: Tile,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<Properties>,
#[serde(skip_serializing_if = "Option::is_none")]
extensions_used: Option<Vec<ExtensionName>>,
#[serde(skip_serializing_if = "Option::is_none")]
extensions_required: Option<Vec<ExtensionName>>,
#[serde(skip_serializing_if = "Option::is_none")]
extensions: Option<Extensions>,
}
impl Tileset {
fn normalize_tileset_geometric_error(
root: &Tile,
geometric_error: GeometricError,
) -> GeometricError {
if root.children.is_none() {
geometric_error.max(f64::EPSILON)
} else {
geometric_error
}
}
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
let file_out = File::create(path.as_ref())?;
let mut ser =
serde_json::ser::Serializer::with_formatter(file_out, BoundingVolumeFormatter);
self.serialize(&mut ser)?;
Ok(())
}
#[allow(dead_code)]
pub fn export_bincode(
&self,
name: Option<&str>,
output_dir: Option<&Path>,
) -> bincode::Result<()> {
let file_name: &str = name.unwrap_or("tileset");
let file = match output_dir {
None => File::create(format!("{file_name}.bincode"))?,
Some(outdir) => File::create(outdir.join(format!("{file_name}.bincode")))?,
};
bincode::serialize_into(file, self)
}
pub fn export(&self, output_dir: Option<&Path>) -> std::io::Result<()> {
let mut q = VecDeque::new();
q.push_back(&self.root);
let mut tileset_level: u16 = self.root.id.level;
let [outdir_tileset, outdir_tileset_content] = match output_dir {
None => [Path::new(""), Path::new("")],
Some(outdir) => [outdir, outdir],
};
let mut file_tileset =
File::create(outdir_tileset.join(format!("tileset_level-{tileset_level}.tsv")))?;
let mut file_tileset_content = File::create(
outdir_tileset_content.join(format!("tileset_content_level-{tileset_level}.tsv")),
)?;
file_tileset
.write_all("id\tlevel\thas_content\twkt\n".as_bytes())
.expect("cannot write tileset TSV header");
file_tileset_content
.write_all("id\tlevel\twkt\n".as_bytes())
.expect("cannot write tileset content TSV header");
while let Some(tile) = q.pop_front() {
if tile.id.level != tileset_level {
tileset_level = tile.id.level;
file_tileset = File::create(
outdir_tileset.join(format!("tileset_level-{tileset_level}.tsv")),
)?;
file_tileset_content = File::create(
outdir_tileset_content
.join(format!("tileset_content_level-{tileset_level}.tsv")),
)?;
file_tileset
.write_all("id\tlevel\thas_content\twkt\n".as_bytes())
.expect("cannot write tileset TSV header");
file_tileset_content
.write_all("id\tlevel\twkt\n".as_bytes())
.expect("cannot write tileset content TSV header");
}
let wkt = tile.bounding_volume.as_wkt();
file_tileset
.write_all(
format!(
"{}\t{}\t{}\t{}\n",
tile.id,
tile.id.level,
tile.content.is_some(),
wkt
)
.as_bytes(),
)
.expect("cannot write tileset tile");
if let Some(ref content) = tile.content {
if let Some(ref bv) = content.bounding_volume {
let wkt_content_bbox = bv.as_wkt();
file_tileset_content
.write_all(
format!("{}\t{}\t{}\n", tile.id, tile.id.level, wkt_content_bbox)
.as_bytes(),
)
.expect("cannot write tileset tile content");
}
}
if let Some(ref children) = tile.children {
for child in children {
q.push_back(child);
}
}
}
Ok(())
}
pub fn from_quadtree(
quadtree: &QuadTree,
world: &crate::parser::World,
geometric_error_factor: f64,
_arg_cellsize: u32,
arg_minz: Option<i32>,
arg_maxz: Option<i32>,
content_bv_from_tile: bool,
content_add_bv: bool,
root_enu_frame: &RootEnuFrame,
) -> Self {
let crs_from = format!("EPSG:{}", world.crs.to_epsg().unwrap());
let transformer = Proj::new_known_crs(&crs_from, "EPSG:4979", None).unwrap();
let root_bbox = quadtree.bbox(&world.grid);
let root_geometric_error = ((root_bbox[3] - root_bbox[0])
.max(root_bbox[4] - root_bbox[1]))
* geometric_error_factor;
let root = Self::generate_tiles(
quadtree,
world,
&transformer,
geometric_error_factor,
arg_minz,
arg_maxz,
content_bv_from_tile,
content_add_bv,
);
log::info!(
"Root ENU frame - input CRS origin: [{:.2}, {:.2}, {:.2}], geodetic: [{:.8}, {:.8}, {:.2}], ECEF: [{:.2}, {:.2}, {:.2}]",
root_enu_frame.source_origin[0],
root_enu_frame.source_origin[1],
root_enu_frame.source_origin[2],
root_enu_frame.geodetic_origin[0],
root_enu_frame.geodetic_origin[1],
root_enu_frame.geodetic_origin[2],
root_enu_frame.ecef_origin[0],
root_enu_frame.ecef_origin[1],
root_enu_frame.ecef_origin[2],
);
let root_transform = Transform(root_enu_frame.transform());
let mut extensions: Extensions = HashMap::new();
let e1 = Extension::ContentGtlf {
extensions_used: None,
extensions_required: None,
};
extensions.insert(ExtensionName::ContentGltf, e1);
let mut root_with_transform = root;
root_with_transform.transform = Some(root_transform);
let geometric_error =
Self::normalize_tileset_geometric_error(&root_with_transform, root_geometric_error);
Self {
asset: Default::default(),
geometric_error,
root: root_with_transform,
properties: None,
extensions_used: Some(vec![ExtensionName::ContentGltf]),
extensions_required: Some(vec![ExtensionName::ContentGltf]),
extensions: Some(extensions),
}
}
fn generate_tiles(
quadtree: &QuadTree,
world: &crate::parser::World,
transformer: &Proj,
geometric_error_factor: f64,
arg_minz: Option<i32>,
arg_maxz: Option<i32>,
content_bv_from_tile: bool,
content_add_bv: bool,
) -> Tile {
if !quadtree.children.is_empty() {
let tile_id = TileId::from(&quadtree.id);
if quadtree.children.len() != 4 {
warn!("Quadtree does not have 4 children {:?}", &quadtree);
}
let mut tile_bbox = quadtree.bbox(&world.grid);
if tile_bbox[5] < tile_bbox[2] {
debug!(
"Internal tile {tile_id} {:?} (in input CRS) bbox maxz {} is less than minz {}. Replacing maxz with minz + minz * 0.01.",
&tile_bbox, tile_bbox[5], tile_bbox[2]
);
tile_bbox[5] = tile_bbox[2] + tile_bbox[2] * 0.01;
}
let bounding_volume =
BoundingVolume::region_from_bbox(&tile_bbox, transformer).unwrap();
let tile_width = (tile_bbox[3] - tile_bbox[0]).max(tile_bbox[4] - tile_bbox[1]);
let geometric_error = tile_width * geometric_error_factor;
let mut tile_children: Vec<Tile> = Vec::new();
for child in quadtree.children.iter() {
tile_children.push(Self::generate_tiles(
child,
world,
transformer,
geometric_error_factor,
arg_minz,
arg_maxz,
content_bv_from_tile,
content_add_bv,
));
}
Tile {
id: tile_id,
bounding_volume,
geometric_error,
viewer_request_volume: None,
refine: Some(Refinement::Replace),
transform: None,
content: None,
children: Some(tile_children),
implicit_tiling: None,
}
} else {
let tile_id = TileId::from(&quadtree.id);
let mut tile_bbox = quadtree.bbox(&world.grid);
if tile_bbox[5] < tile_bbox[2] {
debug!(
"Leaf tile {tile_id} {:?} (in input CRS) bbox maxz {} is less than minz {}. Replacing maxz with minz + minz * 0.01.",
&tile_bbox, tile_bbox[5], tile_bbox[2]
);
tile_bbox[5] = tile_bbox[2] + tile_bbox[2] * 0.01;
}
let bounding_volume =
BoundingVolume::region_from_bbox(&tile_bbox, transformer).unwrap();
let mut content: Option<Content> = None;
if quadtree.nr_items > 0 {
let mut tile_content_bbox_rw =
quadtree.node_content_bbox(world, arg_minz, arg_maxz);
if content_bv_from_tile {
tile_content_bbox_rw = tile_bbox;
} else {
tile_content_bbox_rw[0..3]
.iter()
.enumerate()
.for_each(|(i, min_c)| {
if min_c < &tile_bbox[i] {
tile_bbox[i] = min_c - min_c * 0.01;
}
});
tile_content_bbox_rw[3..6]
.iter()
.enumerate()
.for_each(|(i0, max_c)| {
let i = 3 + i0;
if max_c > &tile_bbox[i] {
tile_bbox[i] = max_c + max_c * 0.01;
}
});
}
if tile_content_bbox_rw[5] < tile_content_bbox_rw[2] {
debug!(
"Leaf tile content {tile_id} {:?} (in input CRS) bbox maxz {} is less than minz {}. Replacing maxz with minz + minz * 0.01.",
&tile_content_bbox_rw, tile_content_bbox_rw[5], tile_content_bbox_rw[2]
);
tile_content_bbox_rw[5] =
tile_content_bbox_rw[2] + tile_content_bbox_rw[2] * 0.01;
}
let content_bounding_volume =
BoundingVolume::region_from_bbox(&tile_content_bbox_rw, transformer)
.unwrap();
content = Some(Content {
bounding_volume: if content_add_bv {
Some(content_bounding_volume)
} else {
None
},
uri: format!("t/{}.glb", quadtree.id),
});
}
Tile {
id: tile_id,
bounding_volume,
geometric_error: 0.0,
viewer_request_volume: None,
refine: Some(Refinement::Replace),
transform: None,
content,
children: None,
implicit_tiling: None,
}
}
}
#[allow(dead_code)]
pub fn flatten(&self, levels_up: Option<u16>) -> Vec<&Tile> {
self.root.flatten(levels_up)
}
pub fn collect_leaves(&self) -> Vec<&Tile> {
self.root.collect_leaves()
}
#[allow(dead_code)]
pub fn add_content(&mut self, levels_up: Option<u16>) {
self.root.add_content_from_level(levels_up);
}
pub fn available_levels(&self) -> u16 {
self.root.max_level() + 1
}
#[allow(dead_code)]
pub fn make_implicit(
&mut self,
grid: &SquareGrid,
qtree: &QuadTree,
grid_export: bool,
subtrees_dir: Option<&str>,
output_dir_debug: Option<&Path>,
) -> (Vec<(Tile, TileId)>, Vec<(TileId, Vec<u8>)>) {
let mut subtrees_vec: Vec<(TileId, Vec<u8>)> = Vec::new();
let mut flat_tiles_with_content: Vec<(Tile, TileId)> = Vec::new();
let subtree_sections: usize = 1;
let subtree_levels =
(self.available_levels() as f32 / subtree_sections as f32).ceil() as u16;
let subtrees = match subtrees_dir {
None => Subtrees::default(),
Some(dirname) => Subtrees::new(dirname),
};
let implicittiling = ImplicitTiling {
subdivision_scheme: SubdivisionScheme::Quadtree,
subtree_levels,
available_levels: self.available_levels(),
subtrees,
};
debug!("{:?}", &implicittiling);
self.root.implicit_tiling = Some(implicittiling);
let grid_epsg = grid.epsg;
let level_subtree_root: u32 = 0; let mut subtree_queue = VecDeque::new();
let rootid = &self.root.id;
let cellid: CellId = rootid.into();
subtree_queue.push_back((level_subtree_root, cellid, &self.root));
while let Some((level_subtree_root, cellid, tile)) = subtree_queue.pop_front() {
let subtree_id = TileId::new(cellid.column, cellid.row, level_subtree_root as u16);
let mut buffer_vec: Vec<u8> = Vec::new();
let mut tile_availability_bitstream: bv::BitVec<u8, bv::Lsb0> = bv::BitVec::new();
let mut content_availability_bitstream: bv::BitVec<u8, bv::Lsb0> =
bv::BitVec::new();
let tileid = &tile.id;
let qtree_nodeid: QuadTreeNodeId = tileid.into();
let tile_bbox = qtree.node(&qtree_nodeid).unwrap().bbox(grid);
let extent_width = tile_bbox[3] - tile_bbox[0];
let mut tiles_queue = VecDeque::new();
tiles_queue.push_back(tile);
let mut level_quadtree: u32 = 0;
for level_subtree in 0..subtree_levels as u32 {
level_quadtree = level_subtree_root + level_subtree;
let _nr_tiles = 4_usize.pow(level_quadtree);
let nr_tiles_subtree = 4_usize.pow(level_subtree);
let grid_coordinate_map = Self::grid_coordinate_map(
level_subtree,
extent_width,
&tile_bbox,
grid_epsg,
grid_export,
output_dir_debug,
);
let grid_coordinate_map_global = Self::grid_coordinate_map(
level_quadtree,
extent_width,
&tile_bbox,
grid_epsg,
false,
None,
);
let mut children_current_level: Vec<&Tile> = Vec::new();
for t in tiles_queue.iter() {
if let Some(ref ch) = t.children {
for c in ch {
let tileid = &c.id;
let qtree_nodeid: QuadTreeNodeId = tileid.into();
let cell = qtree.node(&qtree_nodeid).unwrap();
if cell.nr_items > 0 {
children_current_level.push(c)
}
}
}
}
let mut tile_availability_for_level: bv::BitVec<u8, bv::Lsb0> =
bv::BitVec::new();
tile_availability_for_level.resize(nr_tiles_subtree, false);
let mut content_availability_for_level: bv::BitVec<u8, bv::Lsb0> =
bv::BitVec::new();
content_availability_for_level.resize(nr_tiles_subtree, false);
let mut tile_availability_for_level_vec: Vec<bool> = Vec::new();
tile_availability_for_level_vec.resize(nr_tiles_subtree, false);
let mut content_availability_for_level_vec: Vec<bool> = Vec::new();
content_availability_for_level_vec.resize(nr_tiles_subtree, false);
let mut tileids_contiguous_vec: Vec<String> = Vec::new();
tileids_contiguous_vec.resize(nr_tiles_subtree, String::default());
while let Some(tile) = tiles_queue.pop_front() {
let tile_corner_coord = Self::tile_corner_coordinate(grid, qtree, tile);
if let Some((_cellid_grid_level, i_z_curve)) =
grid_coordinate_map.get(&tile_corner_coord)
{
if let Some((cellid_grid_global, ..)) =
grid_coordinate_map_global.get(&tile_corner_coord)
{
tile_availability_for_level.set(*i_z_curve, true);
tile_availability_for_level_vec[*i_z_curve] = true;
if tile.content.is_some() {
content_availability_for_level.set(*i_z_curve, true);
content_availability_for_level_vec[*i_z_curve] = true;
let tileid_continuous = TileId::new(
cellid_grid_global.column,
cellid_grid_global.row,
level_quadtree as u16,
);
tileids_contiguous_vec[*i_z_curve] =
tileid_continuous.to_string();
flat_tiles_with_content.push((tile.clone(), tileid_continuous));
}
} else {
debug!(
"could not locate tile {} in grid_coordinate_map_global",
tile.id
);
}
} else {
debug!("could not locate tile {} in grid_coordinate_map", tile.id);
}
}
if grid_export {
let nr_tiles = 4_usize.pow(level_subtree);
let tile_width = (extent_width / (nr_tiles as f64).sqrt()) as u32;
let grid_for_level = SquareGrid::new(&tile_bbox, tile_width, grid_epsg);
let outdir = output_dir_debug.unwrap_or(Path::new(""));
let filename = outdir.join(format!(
"implicit-level-{}-{}-{}.tsv",
&level_quadtree, &tile.id.x, &tile.id.y
));
debug!(
"Exporting the subtree {}/{}/{} to TSV file to {}",
&level_quadtree,
&tile.id.x,
&tile.id.y,
filename.display()
);
let mut file_implicit_tileset_at_level = File::create(&filename).unwrap();
writeln!(
file_implicit_tileset_at_level,
"cell_id\ttile_id_subtree\ttile_available\tcontent_available\twkt",
)
.unwrap();
for (cellid_grid_level, i_z_curve) in grid_coordinate_map.values() {
let wkt = grid_for_level.cell_to_wkt(cellid_grid_level);
let va = tile_availability_for_level.get(*i_z_curve);
let vc = content_availability_for_level.get(*i_z_curve);
let tile_id_subtree = &tileids_contiguous_vec[*i_z_curve];
if va.is_none() {
error!(
"tileAvailability bitstream is inconsistent, there is no value at index {i_z_curve}"
);
};
if vc.is_none() {
error!(
"contentAvailability bitstream is inconsistent, there is no value at index {i_z_curve}"
);
}
let tile_available = va.unwrap();
let content_available = vc.unwrap();
writeln!(
file_implicit_tileset_at_level,
"{}\t{}\t{}\t{}\t{}",
cellid_grid_level,
tile_id_subtree,
tile_available.as_ref(),
content_available.as_ref(),
wkt
)
.unwrap();
}
}
tile_availability_for_level.set_uninitialized(false);
content_availability_for_level.set_uninitialized(false);
tile_availability_bitstream.extend_from_bitslice(&tile_availability_for_level);
content_availability_bitstream
.extend_from_bitslice(&content_availability_for_level);
tiles_queue.extend(children_current_level);
}
let level_child_subtree = level_quadtree + 1;
let nr_tiles_child_level = 4_usize.pow(level_child_subtree);
let nr_tiles_total_subtree = (4_usize.pow(subtree_levels as u32) - 1) / 3;
assert_eq!(
tile_availability_bitstream.len(),
nr_tiles_total_subtree,
"tileAvailability bitstream must have {} elements, but it has {}",
&nr_tiles_total_subtree,
&tile_availability_bitstream.len()
);
let grid_coordinate_map = Self::grid_coordinate_map(
level_child_subtree,
extent_width,
&tile_bbox,
grid_epsg,
false,
None,
);
let mut child_subtree_availability_bitstream: bv::BitVec<u8, bv::Lsb0> =
bv::BitVec::new();
child_subtree_availability_bitstream.resize(nr_tiles_child_level, false);
for child in tiles_queue.iter() {
let tile_corner_coord = Self::tile_corner_coordinate(grid, qtree, child);
if let Some((cellid_grid_level, i_z_curve)) =
grid_coordinate_map.get(&tile_corner_coord)
{
child_subtree_availability_bitstream.set(*i_z_curve, true);
subtree_queue.push_back((level_child_subtree, *cellid_grid_level, child));
} else {
debug!(
"could not locate tile {} in grid_for_child_subtree",
child.id
);
}
}
let mut bufferviews: Vec<BufferView> = Vec::with_capacity(3);
let mut bufferview_idx: usize = 0;
let tile_availability =
Self::create_availability(bufferview_idx, &mut tile_availability_bitstream);
if tile_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(
&mut buffer_vec,
&mut bufferviews,
tile_availability_bitstream,
);
bufferview_idx += 1;
}
let content_availability =
Self::create_availability(bufferview_idx, &mut content_availability_bitstream);
if content_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(
&mut buffer_vec,
&mut bufferviews,
content_availability_bitstream,
);
bufferview_idx += 1;
}
let child_subtree_availability = Self::create_availability(
bufferview_idx,
&mut child_subtree_availability_bitstream,
);
if child_subtree_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(
&mut buffer_vec,
&mut bufferviews,
child_subtree_availability_bitstream,
);
}
Self::add_padding(&mut buffer_vec, 8);
debug!("Writing subtree {}", &subtree_id);
let buffer = Buffer {
name: None,
byte_length: buffer_vec.len(),
};
let subtree = Subtree {
buffers: Some(vec![buffer]),
buffer_views: Some(bufferviews),
tile_availability,
content_availability: Some(vec![content_availability]),
child_subtree_availability,
};
let mut subtree_json = serde_json::to_string(&subtree)
.expect("failed to serialize the subtree to json");
let remainder = subtree_json.as_bytes().len() % 8;
let mut padding = 0;
if remainder > 0 {
padding = 8 - remainder;
}
for _i in 0..padding {
subtree_json += " ";
}
let subtree_json_bytes = subtree_json.as_bytes();
let mut subtree_bytes: Vec<u8> = Vec::new();
let mut header: Vec<u8> = Vec::new();
let magic = 0x74627573u32.to_le_bytes(); let version = 1_u32.to_le_bytes();
let json_byte_length = (subtree_json_bytes.len() as u64).to_le_bytes();
let buffer_byte_length = ((buffer_vec.len()) as u64).to_le_bytes();
header.extend_from_slice(&magic);
header.extend_from_slice(&version);
header.extend_from_slice(&json_byte_length);
header.extend_from_slice(&buffer_byte_length);
if header.len() != 24 {
warn!(
"Subtree {} binary header must be 24 bytes long, it is {}",
&subtree_id,
header.len()
);
}
subtree_bytes.extend(header);
subtree_bytes.extend(subtree_json_bytes);
let buffer_bytes: Vec<u8> = buffer_vec
.iter()
.map(|i| i.to_le_bytes())
.flat_map(|bytearray| bytearray.to_vec())
.collect();
subtree_bytes.extend(buffer_bytes);
subtrees_vec.push((subtree_id, subtree_bytes));
}
self.root.content = Some(Content {
bounding_volume: None,
uri: "t/{level}/{x}/{y}.glb".to_string(),
});
self.root.refine = Some(Refinement::Add);
self.root.children = None;
(flat_tiles_with_content, subtrees_vec)
}
pub fn make_implicit_from_content_tile_ids(
&mut self,
content_tile_ids: &[TileId],
subtrees_dir: Option<&str>,
) -> Vec<(TileId, Vec<u8>)> {
let available_levels = self.available_levels();
let subtree_levels = available_levels;
let subtrees = match subtrees_dir {
None => Subtrees::default(),
Some(dirname) => Subtrees::new(dirname),
};
self.root.implicit_tiling = Some(ImplicitTiling {
subdivision_scheme: SubdivisionScheme::Quadtree,
subtree_levels,
available_levels,
subtrees,
});
self.root.content = Some(Content {
bounding_volume: None,
uri: "t/{level}/{x}/{y}.glb".to_string(),
});
self.root.refine = Some(Refinement::Add);
self.root.children = None;
vec![Self::implicit_root_subtree_from_content_tile_ids(
available_levels,
content_tile_ids,
)]
}
fn implicit_root_subtree_from_content_tile_ids(
available_levels: u16,
content_tile_ids: &[TileId],
) -> (TileId, Vec<u8>) {
let mut available_tiles = HashSet::new();
let mut content_tiles = HashSet::new();
for tile_id in content_tile_ids {
if tile_id.level >= available_levels {
warn!(
"Skipping implicit content tile {} because availableLevels is {}",
tile_id, available_levels
);
continue;
}
content_tiles.insert(tile_id.clone());
for ancestor_level in 0..=tile_id.level {
let shift = tile_id.level - ancestor_level;
available_tiles.insert(TileId::new(
tile_id.x >> shift,
tile_id.y >> shift,
ancestor_level,
));
}
}
let nr_tiles_total_subtree = (4_usize.pow(available_levels as u32) - 1) / 3;
let mut tile_availability_bitstream: bv::BitVec<u8, bv::Lsb0> = bv::BitVec::new();
tile_availability_bitstream.resize(nr_tiles_total_subtree, false);
let mut content_availability_bitstream: bv::BitVec<u8, bv::Lsb0> = bv::BitVec::new();
content_availability_bitstream.resize(nr_tiles_total_subtree, false);
for tile_id in available_tiles {
let index = Self::implicit_tile_bit_index(&tile_id);
if index < nr_tiles_total_subtree {
tile_availability_bitstream.set(index, true);
}
}
for tile_id in content_tiles {
let index = Self::implicit_tile_bit_index(&tile_id);
if index < nr_tiles_total_subtree {
content_availability_bitstream.set(index, true);
}
}
let nr_tiles_child_level = 4_usize.pow(available_levels as u32);
let mut child_subtree_availability_bitstream: bv::BitVec<u8, bv::Lsb0> =
bv::BitVec::new();
child_subtree_availability_bitstream.resize(nr_tiles_child_level, false);
let subtree_id = TileId::new(0, 0, 0);
(
subtree_id,
Self::subtree_bytes_from_availability(
&tile_availability_bitstream,
&content_availability_bitstream,
&child_subtree_availability_bitstream,
),
)
}
fn implicit_tile_bit_index(tile_id: &TileId) -> usize {
let level_offset = (4_usize.pow(tile_id.level as u32) - 1) / 3;
let morton_index = morton_encode([tile_id.y as u64, tile_id.x as u64]) as usize;
level_offset + morton_index
}
fn subtree_bytes_from_availability(
tile_availability_bitstream: &bv::BitVec<u8, bv::Lsb0>,
content_availability_bitstream: &bv::BitVec<u8, bv::Lsb0>,
child_subtree_availability_bitstream: &bv::BitVec<u8, bv::Lsb0>,
) -> Vec<u8> {
let mut buffer_vec: Vec<u8> = Vec::new();
let mut bufferviews: Vec<BufferView> = Vec::with_capacity(3);
let mut bufferview_idx: usize = 0;
let mut tile_availability_bits = tile_availability_bitstream.clone();
let tile_availability =
Self::create_availability(bufferview_idx, &mut tile_availability_bits);
if tile_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(&mut buffer_vec, &mut bufferviews, tile_availability_bits);
bufferview_idx += 1;
}
let mut content_availability_bits = content_availability_bitstream.clone();
let content_availability =
Self::create_availability(bufferview_idx, &mut content_availability_bits);
if content_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(&mut buffer_vec, &mut bufferviews, content_availability_bits);
bufferview_idx += 1;
}
let mut child_subtree_availability_bits = child_subtree_availability_bitstream.clone();
let child_subtree_availability =
Self::create_availability(bufferview_idx, &mut child_subtree_availability_bits);
if child_subtree_availability.constant.is_none() {
Self::add_padding(&mut buffer_vec, 8);
Self::add_bitstream(
&mut buffer_vec,
&mut bufferviews,
child_subtree_availability_bits,
);
}
Self::add_padding(&mut buffer_vec, 8);
let buffer = Buffer {
name: None,
byte_length: buffer_vec.len(),
};
let subtree = Subtree {
buffers: Some(vec![buffer]),
buffer_views: Some(bufferviews),
tile_availability,
content_availability: Some(vec![content_availability]),
child_subtree_availability,
};
let mut subtree_json =
serde_json::to_string(&subtree).expect("failed to serialize the subtree to json");
let remainder = subtree_json.as_bytes().len() % 8;
if remainder > 0 {
let padding = 8 - remainder;
for _i in 0..padding {
subtree_json += " ";
}
}
let subtree_json_bytes = subtree_json.as_bytes();
let mut subtree_bytes: Vec<u8> = Vec::new();
subtree_bytes.extend(0x74627573u32.to_le_bytes());
subtree_bytes.extend(1_u32.to_le_bytes());
subtree_bytes.extend((subtree_json_bytes.len() as u64).to_le_bytes());
subtree_bytes.extend((buffer_vec.len() as u64).to_le_bytes());
subtree_bytes.extend(subtree_json_bytes);
subtree_bytes.extend(buffer_vec);
subtree_bytes
}
fn add_padding(buffer_vec: &mut Vec<u8>, align_by: usize) {
let padding = (align_by - (buffer_vec.len() % align_by)) % align_by;
for _i in 0..padding {
buffer_vec.push(0);
}
}
fn add_bitstream(
buffer_vec: &mut Vec<u8>,
bufferviews: &mut Vec<BufferView>,
availability_bitstream: bv::BitVec<u8, bv::Lsb0>,
) {
let availability_vec = availability_bitstream.into_vec();
bufferviews.push(BufferView {
buffer: 0,
byte_offset: buffer_vec.len(),
byte_length: availability_vec.len(),
name: None,
});
buffer_vec.extend(availability_vec);
}
fn create_availability(
bf_availability: usize,
availability_bitstream: &mut bv::BitVec<u8, bv::Lsb0>,
) -> Availability {
availability_bitstream.set_uninitialized(false);
if availability_bitstream.not_any() {
Availability {
bitstream: None,
available_count: None,
constant: Some(AvailabilityConstant::Unavailable),
}
} else if availability_bitstream.all() {
Availability {
bitstream: None,
available_count: None,
constant: Some(AvailabilityConstant::Available),
}
} else {
Availability {
bitstream: Some(bf_availability),
available_count: Some(availability_bitstream.count_ones()),
constant: None,
}
}
}
#[allow(dead_code)]
fn tile_corner_coordinate(grid: &SquareGrid, qtree: &QuadTree, tile: &Tile) -> String {
let tileid = &tile.id;
let qtree_nodeid: QuadTreeNodeId = tileid.into();
let tile_bbox = qtree.node(&qtree_nodeid).unwrap().bbox(grid);
let [minx, miny, ..] = tile_bbox;
format!("{:.0},{:.0}", minx, miny)
}
#[allow(dead_code)]
fn grid_coordinate_map(
level_current: u32,
extent_width: f64,
bbox: &Bbox,
epsg: u16,
do_export: bool,
output_dir_debug: Option<&Path>,
) -> HashMap<String, (CellId, usize)> {
let nr_tiles = 4_usize.pow(level_current);
let tile_width = (extent_width / (nr_tiles as f64).sqrt()) as u32;
let grid_for_level = SquareGrid::new(bbox, tile_width, epsg);
let mut grid_for_level_corner_coords: HashMap<String, (CellId, usize)> = HashMap::new();
let mut mortoncodes: Vec<(u128, CellId)> = grid_for_level
.into_iter()
.map(|(cellid, _)| {
(
morton_encode([cellid.row as u64, cellid.column as u64]),
cellid,
)
})
.collect();
mortoncodes.sort_by_key(|k| k.0);
for (i, (_mc, cellid)) in mortoncodes.iter().enumerate() {
let [minx, miny, ..] = grid_for_level.cell_bbox(cellid);
let corner_coord_string = format!("{:.0},{:.0}", minx, miny);
grid_for_level_corner_coords.insert(corner_coord_string, (*cellid, i));
}
if do_export {
let first_cell = grid_for_level.into_iter().next().unwrap();
let outdir = output_dir_debug.unwrap_or(Path::new(""));
let mut file_grid = File::create(outdir.join(format!(
"grid_for_level-{}-{}.tsv",
&grid_for_level.length, &first_cell.0
)))
.unwrap();
writeln!(file_grid, "cell_id\twkt").unwrap();
for (cellid, _) in &grid_for_level {
let wkt = grid_for_level.cell_to_wkt(&cellid);
writeln!(file_grid, "{}\t{}", &cellid, wkt).unwrap();
}
let mut file_grid_morton = File::create(outdir.join(format!(
"grid_for_level_morton-{}-{}.tsv",
&grid_for_level.length, &first_cell.0
)))
.unwrap();
writeln!(file_grid_morton, "idx\tcell_id\twkt").unwrap();
for (i, (_mc, cellid)) in mortoncodes.iter().enumerate() {
let [minx, miny, ..] = grid_for_level.cell_bbox(cellid);
writeln!(
file_grid_morton,
"{}\t{}\tPOINT({} {})",
i, &cellid, minx, miny
)
.unwrap();
}
}
grid_for_level_corner_coords
}
pub fn prune(&mut self, tiles_to_remove: &Vec<Tile>, qtree: &QuadTree) {
self.root.prune(tiles_to_remove, qtree);
}
pub fn split(&mut self, level: u16) -> Vec<(String, Tileset)> {
let max_nr_tilesets = 4_usize.pow(level as u32);
let mut child_tilesets: Vec<(String, Tileset)> = Vec::with_capacity(max_nr_tilesets);
let mut q = VecDeque::new();
q.push_back(&mut self.root);
while let Some(tile) = q.pop_front() {
if tile.id.level == level {
let filename =
format!("tileset-{}-{}-{}.json", tile.id.level, tile.id.x, tile.id.y);
let root = tile.clone();
let geometric_error =
Self::normalize_tileset_geometric_error(&root, root.geometric_error);
child_tilesets.push((
filename.clone(),
Tileset {
asset: Default::default(),
geometric_error,
root,
properties: None,
extensions_used: None,
extensions_required: None,
extensions: None,
},
));
tile.content = Some(Content {
bounding_volume: None,
uri: filename,
});
tile.children = None;
}
if let Some(ref mut children) = tile.children {
for child in children.iter_mut() {
q.push_back(child);
}
}
}
child_tilesets
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct Asset {
version: String,
#[serde(skip_serializing_if = "Option::is_none")]
tileset_version: Option<String>,
}
impl Default for Asset {
fn default() -> Self {
Self {
version: String::from("1.1"),
tileset_version: None,
}
}
}
type GeometricError = f64;
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct Properties {
maximum: f64,
minimum: f64,
}
type Extensions = HashMap<ExtensionName, Extension>;
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(untagged)]
enum Extension {
#[default]
None,
#[serde(rename = "3DTILES_content_gltf")]
ContentGtlf {
#[serde(rename = "extensionsUsed", skip_serializing_if = "Option::is_none")]
extensions_used: Option<Vec<ExtensionName>>,
#[serde(rename = "extensionsRequired", skip_serializing_if = "Option::is_none")]
extensions_required: Option<Vec<ExtensionName>>,
},
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum ExtensionName {
#[default]
None,
#[serde(rename = "3DTILES_content_gltf")]
ContentGltf,
#[serde(rename = "EXT_mesh_features")]
MeshFeatures,
#[serde(rename = "EXT_structural_metadata")]
StructuralMetadata,
#[serde(rename = "3DTILES_implicit_tiling")]
ImplicitTiling,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Tile {
#[serde(skip)]
pub id: TileId,
bounding_volume: BoundingVolume,
pub geometric_error: GeometricError,
#[serde(skip_serializing_if = "Option::is_none")]
viewer_request_volume: Option<BoundingVolume>,
#[serde(skip_serializing_if = "Option::is_none")]
refine: Option<Refinement>,
#[serde(skip_serializing_if = "Option::is_none")]
transform: Option<Transform>,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<Tile>>,
#[serde(skip_serializing_if = "Option::is_none")]
implicit_tiling: Option<ImplicitTiling>,
}
impl PartialEq for Tile {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Tile {}
impl Tile {
#[allow(dead_code)]
fn flatten_recurse<'collect>(
&'collect self,
nodes: &mut Vec<&'collect Tile>,
limit_upwards: &u16,
) {
if self.id.level < *limit_upwards {
if let Some(ref children) = self.children {
debug!("nr of children {}", children.len());
for child in children {
child.flatten_recurse(nodes, limit_upwards);
}
}
} else {
nodes.push(self);
if let Some(ref children) = self.children {
debug!("nr of children {}", children.len());
for child in children {
child.flatten_recurse(nodes, limit_upwards);
}
}
}
}
#[allow(dead_code)]
pub fn flatten(&self, levels_up: Option<u16>) -> Vec<&Tile> {
let max_level = self.max_level();
let mut limit_upwards: u16 = 0;
if let Some(limit) = levels_up {
if limit < max_level {
limit_upwards = max_level - limit;
}
}
let mut flat_tiles: Vec<&Tile> = Vec::new();
self.flatten_recurse(&mut flat_tiles, &limit_upwards);
flat_tiles
}
fn collect_leaves_recurse<'collect>(&'collect self, leaves: &mut Vec<&'collect Tile>) {
if let Some(ref children) = self.children {
for child in children {
child.collect_leaves_recurse(leaves);
}
} else {
leaves.push(self);
}
}
pub fn collect_leaves(&self) -> Vec<&Self> {
let mut leaves: Vec<&Tile> = Vec::new();
self.collect_leaves_recurse(&mut leaves);
leaves
}
#[allow(dead_code)]
fn add_content_from_level(&mut self, levels_up: Option<u16>) {
let max_level = self.max_level();
let mut lower_limit: u16 = 0;
if let Some(limit) = levels_up {
if limit < max_level {
lower_limit = max_level - limit;
}
}
let mut q = VecDeque::new();
q.push_back(self);
while let Some(node) = q.pop_front() {
if node.id.level >= lower_limit {
node.add_content();
}
if let Some(ref mut children) = node.children {
for child in children.iter_mut() {
q.push_back(child);
}
}
}
}
fn max_level(&self) -> u16 {
let mut max_level: u16 = 0;
self.max_level_recurse(&mut max_level);
max_level
}
fn max_level_recurse(&self, current_level: &mut u16) {
if let Some(ref children) = self.children {
for child in children {
child.max_level_recurse(current_level);
}
} else if self.id.level > *current_level {
*current_level = self.id.level;
}
}
#[allow(dead_code)]
pub fn add_content(&mut self) {
self.content = Some(Content {
bounding_volume: Some(self.bounding_volume),
uri: format!("t/{}.glb", self.id),
})
}
fn prune(&mut self, tiles_to_remove: &Vec<Tile>, qtree: &QuadTree) {
if let Some(mut children) = self.children.take() {
let mut children_new: Vec<Tile> = Vec::with_capacity(4);
for child in children.iter_mut() {
if !tiles_to_remove.contains(&*child) {
let tileid: &TileId = &child.id;
let qtree_nodeid: QuadTreeNodeId = tileid.into();
if let Some(qtree_node) = qtree.node(&qtree_nodeid) {
if qtree_node.nr_items > 0 {
child.prune(tiles_to_remove, qtree);
children_new.push(child.clone());
}
} else {
error!("Did not find matching QuadTree node for TileId {}", tileid);
}
}
}
self.children = Some(children_new);
}
}
}
#[derive(
Serialize, Deserialize, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
)]
pub struct TileId {
pub(crate) x: usize,
pub(crate) y: usize,
pub(crate) level: u16,
}
impl TileId {
pub fn new(x: usize, y: usize, level: u16) -> Self {
Self { x, y, level }
}
}
impl Display for TileId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}/{}", self.level, self.x, self.y)
}
}
impl From<&QuadTreeNodeId> for TileId {
fn from(value: &QuadTreeNodeId) -> Self {
Self {
x: value.x,
y: value.y,
level: value.level,
}
}
}
impl From<&TileId> for QuadTreeNodeId {
fn from(val: &TileId) -> Self {
QuadTreeNodeId::new(val.x, val.y, val.level)
}
}
impl From<&TileId> for CellId {
fn from(val: &TileId) -> Self {
CellId {
column: val.x,
row: val.y,
}
}
}
struct BoundingVolumeFormatter;
impl serde_json::ser::Formatter for BoundingVolumeFormatter {
fn write_f64<W>(&mut self, writer: &mut W, value: f64) -> std::io::Result<()>
where
W: ?Sized + Write,
{
write!(writer, "{:.6}", value)
}
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
#[serde(rename_all = "lowercase")]
enum BoundingVolume {
Box([f64; 12]),
Region([f64; 6]),
Sphere([f64; 4]),
}
impl Default for BoundingVolume {
fn default() -> Self {
Self::Region([0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
}
}
impl BoundingVolume {
#[allow(dead_code)]
fn box_from_bbox(
bbox: &Bbox,
transformer: &Proj,
) -> Result<Self, Box<dyn std::error::Error>> {
let dx = bbox[3] - bbox[0];
let dy = bbox[4] - bbox[1];
let dz = bbox[5] - bbox[2];
let center: [f64; 3] = [bbox[0] + dx * 0.5, bbox[1] + dy * 0.5, (bbox[2] + dz * 0.5)];
let x_max_pt: [f64; 3] = [bbox[3], center[1], center[2]];
let y_max_pt: [f64; 3] = [center[0], bbox[4], center[2]];
let center_ecef = transformer.convert((center[0], center[1], center[2]))?;
let pnx = transformer.convert((center[0] + 1.0, center[1], center[2]))?;
let pny = transformer.convert((center[0], center[1] + 1.0, center[2]))?;
let x_max_pt_ecef = transformer.convert((x_max_pt[0], x_max_pt[1], x_max_pt[2]))?;
let y_max_pt_ecef = transformer.convert((y_max_pt[0], y_max_pt[1], y_max_pt[2]))?;
let dx_ = ((x_max_pt_ecef.0 - center_ecef.0).powi(2)
+ (x_max_pt_ecef.1 - center_ecef.1).powi(2)
+ (x_max_pt_ecef.2 - center_ecef.2).powi(2))
.sqrt();
let dy_ = ((y_max_pt_ecef.0 - center_ecef.0).powi(2)
+ (y_max_pt_ecef.1 - center_ecef.1).powi(2)
+ (y_max_pt_ecef.2 - center_ecef.2).powi(2))
.sqrt();
let s_to_unit_vx = ((pnx.0 - center_ecef.0).powi(2)
+ (pnx.1 - center_ecef.1).powi(2)
+ (pnx.2 - center_ecef.2).powi(2))
.sqrt();
let vx = (
(pnx.0 - center_ecef.0) / s_to_unit_vx * dx_,
(pnx.1 - center_ecef.1) / s_to_unit_vx * dx_,
(pnx.2 - center_ecef.2) / s_to_unit_vx * dx_,
);
let s_to_unit_vy = ((pny.0 - center_ecef.0).powi(2)
+ (pny.1 - center_ecef.1).powi(2)
+ (pny.2 - center_ecef.2).powi(2))
.sqrt();
let vy = (
(pny.0 - center_ecef.0) / s_to_unit_vy * dy_,
(pny.1 - center_ecef.1) / s_to_unit_vy * dy_,
(pny.2 - center_ecef.2) / s_to_unit_vy * dy_,
);
let dvz =
(center_ecef.0.powi(2) + center_ecef.1.powi(2) + center_ecef.2.powi(2)).sqrt();
let vz_unit = (
center_ecef.0 / dvz,
center_ecef.1 / dvz,
center_ecef.2 / dvz,
);
let r_earth: f64 = 6371000.0;
let dxy_ = (dx_.powi(2) + dy.powi(2)).sqrt();
let curvature_drop = (dxy_.powi(2) + r_earth.powi(2)).sqrt() - r_earth;
let center_z_correction = (dz + curvature_drop) / 2.0 - 0.5 * dz;
let center_ecef_dropped = (
center_ecef.0 - center_z_correction * vz_unit.0,
center_ecef.1 - center_z_correction * vz_unit.1,
center_ecef.2 - center_z_correction * vz_unit.2,
);
Ok(Self::Box([
center_ecef_dropped.0,
center_ecef_dropped.1,
center_ecef_dropped.2,
vx.0,
vx.1,
vx.2,
vy.0,
vy.1,
vy.2,
vz_unit.0 * (curvature_drop + dz) / 2.0,
vz_unit.1 * (curvature_drop + dz) / 2.0,
vz_unit.2 * (curvature_drop + dz) / 2.0,
]))
}
#[allow(dead_code)]
fn region_from_bbox(
bbox: &Bbox,
transformer: &Proj,
) -> Result<Self, Box<dyn std::error::Error>> {
let mut west = f64::INFINITY;
let mut south = f64::INFINITY;
let mut min_h = f64::INFINITY;
let mut east = f64::NEG_INFINITY;
let mut north = f64::NEG_INFINITY;
let mut max_h = f64::NEG_INFINITY;
for [x, y, z] in bbox_corners(bbox) {
let (lon, lat, height) = transformer.convert((x, y, z))?;
west = west.min(lon);
south = south.min(lat);
min_h = min_h.min(height);
east = east.max(lon);
north = north.max(lat);
max_h = max_h.max(height);
}
Ok(BoundingVolume::Region([
west.to_radians(),
south.to_radians(),
east.to_radians(),
north.to_radians(),
min_h,
max_h,
]))
}
fn as_wkt(&self) -> String {
let [minx, miny, _minz, maxx, maxy, _maxz] = match self {
BoundingVolume::Box(bbox) => {
let center = &bbox[0..3];
let vx = &bbox[3..6];
let vy = &bbox[6..9];
let corner_1_v = [vx[0] + vy[0], vx[1] + vy[1], vx[2] + vy[2]];
let corner_2_v = [vy[0] + -vx[0], vy[1] + -vx[1], vy[2] + -vx[2]];
let corner_3_v = [-corner_1_v[0], -corner_1_v[1], -corner_1_v[2]];
let corner_4_v = [-corner_2_v[0], -corner_2_v[1], -corner_2_v[2]];
let corner_points: Vec<[f64; 3]> =
[corner_1_v, corner_2_v, corner_3_v, corner_4_v]
.iter()
.map(|corner| {
[
center[0] + corner[0],
center[1] + corner[1],
center[2] + corner[2],
]
})
.collect();
let minx = corner_points.iter().map(|a| a[0]).reduce(f64::min).unwrap();
let miny = corner_points.iter().map(|a| a[1]).reduce(f64::min).unwrap();
let minz = corner_points.iter().map(|a| a[2]).reduce(f64::min).unwrap();
let maxx = corner_points.iter().map(|a| a[0]).reduce(f64::max).unwrap();
let maxy = corner_points.iter().map(|a| a[1]).reduce(f64::max).unwrap();
let maxz = corner_points.iter().map(|a| a[2]).reduce(f64::max).unwrap();
[minx, miny, minz, maxx, maxy, maxz]
}
BoundingVolume::Region(bbox) => [
bbox[0].to_degrees(),
bbox[1].to_degrees(),
bbox[4],
bbox[2].to_degrees(),
bbox[3].to_degrees(),
bbox[5],
],
BoundingVolume::Sphere(_) => {
unimplemented!()
}
};
format!(
"POLYGON(({minx} {miny}, {maxx} {miny}, {maxx} {maxy}, {minx} {maxy}, {minx} {miny}))",
minx = minx,
miny = miny,
maxx = maxx,
maxy = maxy
)
}
}
fn bbox_corners(bbox: &Bbox) -> [[f64; 3]; 8] {
[
[bbox[0], bbox[1], bbox[2]],
[bbox[0], bbox[1], bbox[5]],
[bbox[0], bbox[4], bbox[2]],
[bbox[0], bbox[4], bbox[5]],
[bbox[3], bbox[1], bbox[2]],
[bbox[3], bbox[1], bbox[5]],
[bbox[3], bbox[4], bbox[2]],
[bbox[3], bbox[4], bbox[5]],
]
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "UPPERCASE")]
enum Refinement {
Add,
Replace,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
struct Transform([f64; 16]);
impl Default for Transform {
#[rustfmt::skip]
fn default() -> Self {
Self([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
}
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct Content {
#[serde(skip_serializing_if = "Option::is_none")]
bounding_volume: Option<BoundingVolume>,
uri: String,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct ImplicitTiling {
subdivision_scheme: SubdivisionScheme,
subtree_levels: u16,
available_levels: u16,
subtrees: Subtrees,
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "UPPERCASE")]
enum SubdivisionScheme {
#[default]
Quadtree,
Octree,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Subtrees {
uri: String,
}
impl Default for Subtrees {
fn default() -> Self {
Self {
uri: String::from("subtrees/{level}/{x}/{y}.subtree"),
}
}
}
impl Subtrees {
fn new(subtrees_dir: &str) -> Self {
Self {
uri: format!("{}/{{level}}/{{x}}/{{y}}.subtree", subtrees_dir),
}
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
struct Subtree {
#[serde(skip_serializing_if = "Option::is_none")]
buffers: Option<Vec<Buffer>>,
#[serde(skip_serializing_if = "Option::is_none")]
buffer_views: Option<Vec<BufferView>>,
tile_availability: Availability,
#[serde(skip_serializing_if = "Option::is_none")]
content_availability: Option<Vec<Availability>>,
child_subtree_availability: Availability,
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
struct Buffer {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
byte_length: usize,
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
struct BufferView {
buffer: u8,
byte_offset: usize,
byte_length: usize,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
struct Availability {
#[serde(skip_serializing_if = "Option::is_none")]
bitstream: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
available_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
constant: Option<AvailabilityConstant>,
}
#[allow(dead_code)]
#[derive(Debug, Default, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
enum AvailabilityConstant {
#[default]
Unavailable = 0,
Available = 1,
}
#[cfg(test)]
mod tests {
use super::*;
use cityjson_index;
use serde_json::to_string_pretty;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
fn test_data_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("resources")
.join("data")
}
fn resource_path(name: &str) -> PathBuf {
test_data_dir().join(name)
}
fn unique_test_dir(prefix: &str) -> PathBuf {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time")
.as_nanos();
let path = std::env::temp_dir().join(format!("tyler-{prefix}-{unique}"));
fs::create_dir_all(&path).expect("create test dir");
path
}
fn assert_tile_bounding_volumes_are_regions(tile: &Tile) {
assert!(
matches!(tile.bounding_volume, BoundingVolume::Region(_)),
"tile {} bounding volume should be a region",
tile.id
);
if let Some(content) = &tile.content {
if let Some(bounding_volume) = &content.bounding_volume {
assert!(
matches!(bounding_volume, BoundingVolume::Region(_)),
"tile {} content bounding volume should be a region",
tile.id
);
}
}
if let Some(children) = &tile.children {
for child in children {
assert_tile_bounding_volumes_are_regions(child);
}
}
}
fn assert_geometric_error_decreases_to_zero(tile: &Tile) {
if let Some(children) = &tile.children {
for child in children {
assert!(
tile.geometric_error >= child.geometric_error,
"tile {} geometricError {} should be >= child {} geometricError {}",
tile.id,
tile.geometric_error,
child.id,
child.geometric_error
);
assert_geometric_error_decreases_to_zero(child);
}
} else {
assert!(
tile.geometric_error.abs() <= f64::EPSILON,
"leaf tile {} should have zero geometricError",
tile.id
);
}
}
#[test]
fn test_implicittiling() {
let dataset_dir = unique_test_dir("implicittiling");
let metadata =
fs::read_to_string(resource_path("3dbag_x00.city.json")).expect("read metadata");
let feature = fs::read_to_string(resource_path("3dbag_feature_x71.city.jsonl"))
.expect("read feature");
let ndjson_source = dataset_dir.join("source.city.jsonl");
fs::write(&ndjson_source, format!("{metadata}\n{feature}\n"))
.expect("write ndjson source");
let resolved = cityjson_index::resolve_dataset(&dataset_dir, None)
.expect("resolve cjindex dataset");
let mut city_index =
cityjson_index::CityIndex::open(resolved.storage_layout(), &resolved.index_path)
.expect("open index");
city_index.reindex().expect("reindex");
let metadata_doc = city_index
.metadata()
.expect("load metadata")
.first()
.expect("metadata exists")
.as_ref()
.clone();
let feature_base_document =
serde_json::to_vec(&metadata_doc).expect("serialize metadata");
let metadata_path = dataset_dir.join("metadata.city.json");
fs::write(&metadata_path, &feature_base_document).expect("write metadata");
let cityobject_types = Some(vec![
crate::parser::CityObjectType::Building,
crate::parser::CityObjectType::BuildingPart,
]);
let feature_filter = crate::build_feature_filter(
cityobject_types.as_ref(),
&std::collections::BTreeMap::new(),
);
let mut world = crate::parser::World::from_cjindex(
crate::parser::InputSource::from_cjindex_resolved(&resolved),
metadata_path,
feature_base_document,
200,
cityobject_types,
feature_filter,
None,
None,
)
.unwrap();
world.index_with_grid().unwrap();
world.export_grid(false, None).unwrap();
let quadtree = QuadTree::from_world(
&world,
crate::spatial_structs::QuadTreeCapacity::Vertices(15000),
);
quadtree.export(&world, None).unwrap();
let source_crs = format!("EPSG:{}", world.crs.to_epsg().unwrap());
let root_bbox = quadtree.bbox(&world.grid);
let root_enu_frame =
RootEnuFrame::from_bbox(&source_crs, &root_bbox).expect("root ENU frame");
let tileset = Tileset::from_quadtree(
&quadtree,
&world,
0.024,
200,
None,
None,
true,
true,
&root_enu_frame,
);
assert_tile_bounding_volumes_are_regions(&tileset.root);
assert_geometric_error_decreases_to_zero(&tileset.root);
if tileset.root.children.is_some() {
assert!(
(tileset.geometric_error - tileset.root.geometric_error).abs() <= f64::EPSILON
);
} else {
assert!(tileset.root.geometric_error.abs() <= f64::EPSILON);
assert!(tileset.geometric_error > f64::EPSILON);
}
let root_transform = tileset
.root
.transform
.as_ref()
.expect("root tile should have ENU-to-ECEF transform");
assert!((root_transform.0[0] - 1.0).abs() > 1.0e-6);
assert!(root_transform.0[12].abs() > 1.0);
let i = ImplicitTiling::default();
println!("{}", serde_json::to_string(&i).unwrap());
}
#[test]
fn test_availability() {
let a = AvailabilityConstant::Available;
assert_eq!("1", serde_json::to_string(&a).unwrap());
}
#[test]
fn test_refinement() {
let r = Refinement::Replace;
let j = serde_json::to_string(&r).unwrap();
assert_eq!(j, r#""REPLACE""#.to_string());
}
#[test]
fn test_boundingvolume_from_bbox() {
let crs_to = "EPSG:4978";
let transformer = Proj::new_known_crs("EPSG:7415", crs_to, None).unwrap();
let bbox: Bbox = [
84362.90299999999,
446306.814,
-20.66,
212362.903,
574306.814,
62.882,
];
let bounding_volume = BoundingVolume::box_from_bbox(&bbox, &transformer).unwrap();
println!("{:?}", serde_json::to_string(&bounding_volume));
}
#[test]
fn test_boundingvolume_region_from_bbox() {
let crs_to = "EPSG:4979";
let transformer = Proj::new_known_crs("EPSG:7415", crs_to, None).unwrap();
let bbox: Bbox = [84995.279, 446316.813, -5.333, 85644.748, 446996.132, 52.881];
let bounding_volume = BoundingVolume::region_from_bbox(&bbox, &transformer)
.expect("bbox should transform to EPSG:4979 region");
let BoundingVolume::Region(region) = bounding_volume else {
panic!("region_from_bbox should return a region");
};
let mut expected = [
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
];
for [x, y, z] in bbox_corners(&bbox) {
let (lon, lat, height) = transformer.convert((x, y, z)).unwrap();
expected[0] = expected[0].min(lon.to_radians());
expected[1] = expected[1].min(lat.to_radians());
expected[2] = expected[2].max(lon.to_radians());
expected[3] = expected[3].max(lat.to_radians());
expected[4] = expected[4].min(height);
expected[5] = expected[5].max(height);
}
for (actual, expected) in region.into_iter().zip(expected) {
assert!(
(actual - expected).abs() < 1.0e-12,
"expected {expected}, got {actual}"
);
}
assert!(region[0].abs() < std::f64::consts::PI);
assert!(region[1].abs() < std::f64::consts::FRAC_PI_2);
assert!(region[0] < region[2]);
assert!(region[1] < region[3]);
assert!(region[4] <= region[5]);
}
#[test]
fn test_bbox_corners_returns_all_eight_corners() {
let bbox: Bbox = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let corners = bbox_corners(&bbox);
assert_eq!(corners.len(), 8);
assert!(corners.contains(&[1.0, 2.0, 3.0]));
assert!(corners.contains(&[1.0, 2.0, 6.0]));
assert!(corners.contains(&[1.0, 5.0, 3.0]));
assert!(corners.contains(&[1.0, 5.0, 6.0]));
assert!(corners.contains(&[4.0, 2.0, 3.0]));
assert!(corners.contains(&[4.0, 2.0, 6.0]));
assert!(corners.contains(&[4.0, 5.0, 3.0]));
assert!(corners.contains(&[4.0, 5.0, 6.0]));
}
#[test]
fn test_boundingvolume_json_precision() {
let bounding_volume = BoundingVolume::Region([
0.0762316935076296,
0.9075853268461559,
0.07639433996161832,
0.9076933035069118,
38.18193225618456,
96.3895906072444,
]);
println!("{}", serde_json::to_string(&bounding_volume).unwrap());
}
#[test]
fn test_3d_tiles_empty_extension() {
let mut extensions: Extensions = HashMap::new();
let e1 = Extension::ContentGtlf {
extensions_used: None,
extensions_required: None,
};
extensions.insert(ExtensionName::ContentGltf, e1);
let t = Tileset {
asset: Default::default(),
geometric_error: 0.0,
properties: None,
extensions_used: Some(vec![ExtensionName::ContentGltf]),
extensions_required: Some(vec![ExtensionName::ContentGltf]),
extensions: Some(extensions),
root: Default::default(),
};
println!("{}", to_string_pretty(&t).unwrap());
}
#[test]
fn test_3d_tiles_with_extension() {
let mut extensions: Extensions = HashMap::new();
let e1 = Extension::ContentGtlf {
extensions_used: Some(vec![
ExtensionName::MeshFeatures,
ExtensionName::StructuralMetadata,
]),
extensions_required: None,
};
extensions.insert(ExtensionName::ContentGltf, e1);
let t = Tileset {
asset: Default::default(),
geometric_error: 0.0,
properties: None,
extensions_used: Some(vec![ExtensionName::ContentGltf]),
extensions_required: Some(vec![ExtensionName::ContentGltf]),
extensions: Some(extensions),
root: Default::default(),
};
println!("{}", to_string_pretty(&t).unwrap());
}
}
}