use crate::backend::default::citymodel::{CityModelCore, CityModelCoreCapacities};
use crate::backend::default::geometry_validation::{
BoundaryVertexSource, GeometryValidationContext, validate_stored_geometry,
validate_stored_geometry_for_boundary_source,
};
use crate::cityjson::core::appearance::ThemeName;
use crate::error::Error;
use crate::error::Result;
use crate::raw::{RawAccess, RawPoolView, RawSliceView};
use crate::resources::handles::{
CityObjectHandle, GeometryHandle, GeometryTemplateHandle, MaterialHandle, SemanticHandle,
TextureHandle,
};
use crate::resources::id::ResourceId32;
use crate::resources::storage::{BorrowedStringStorage, OwnedStringStorage, StringStorage};
use crate::v2_0::CityObjects;
use crate::v2_0::appearance::material::Material;
use crate::v2_0::appearance::texture::Texture;
use crate::v2_0::attributes::AttributeValue;
use crate::v2_0::coordinate::{RealWorldCoordinate, UVCoordinate};
use crate::v2_0::extension::Extensions;
use crate::v2_0::geometry::GeometryView;
use crate::v2_0::geometry::semantic::Semantic;
use crate::v2_0::geometry::{Geometry, GeometryType};
use crate::v2_0::metadata::Metadata;
use crate::v2_0::transform::Transform;
use crate::v2_0::vertex::{VertexIndex, VertexRef};
use crate::v2_0::vertices::Vertices;
use crate::{CityJSONVersion, format_option};
use std::collections::HashSet;
use std::fmt;
pub type OwnedCityModel = CityModel<u32, OwnedStringStorage>;
pub type BorrowedCityModel<'a> = CityModel<u32, BorrowedStringStorage<'a>>;
#[derive(Debug, Clone, Copy, Default)]
pub struct CityModelCapacities {
pub cityobjects: usize,
pub vertices: usize,
pub semantics: usize,
pub materials: usize,
pub textures: usize,
pub geometries: usize,
pub template_vertices: usize,
pub template_geometries: usize,
pub uv_coordinates: usize,
}
impl From<CityModelCapacities> for CityModelCoreCapacities {
fn from(value: CityModelCapacities) -> Self {
Self {
cityobjects: value.cityobjects,
vertices: value.vertices,
semantics: value.semantics,
materials: value.materials,
textures: value.textures,
geometries: value.geometries,
template_vertices: value.template_vertices,
template_geometries: value.template_geometries,
uv_coordinates: value.uv_coordinates,
}
}
}
#[derive(Debug, Clone)]
pub struct CityModel<VR: VertexRef = u32, SS: StringStorage = OwnedStringStorage> {
#[allow(clippy::type_complexity)]
inner: CityModelCore<
VR,
ResourceId32,
SS,
Semantic<SS>,
Material<SS>,
Texture<SS>,
Geometry<VR, SS>,
Metadata<SS>,
Transform,
Extensions<SS>,
CityObjects<SS>,
>,
}
impl<VR: VertexRef, SS: StringStorage> CityModel<VR, SS> {
#[must_use]
pub fn new(type_citymodel: crate::CityModelType) -> Self {
Self {
inner: CityModelCore::new(type_citymodel, Some(CityJSONVersion::V2_0)),
}
}
#[must_use]
pub fn with_capacities(
type_citymodel: crate::CityModelType,
capacities: CityModelCapacities,
) -> Self {
Self {
inner: CityModelCore::with_capacities(
type_citymodel,
Some(CityJSONVersion::V2_0),
capacities.into(),
CityObjects::with_capacity,
),
}
}
pub fn get_semantic(&self, id: SemanticHandle) -> Option<&Semantic<SS>> {
self.inner.get_semantic(id.to_raw())
}
pub fn get_semantic_mut(&mut self, id: SemanticHandle) -> Option<&mut Semantic<SS>> {
self.inner.get_semantic_mut(id.to_raw())
}
pub fn add_semantic(&mut self, semantic: Semantic<SS>) -> Result<SemanticHandle> {
self.inner
.add_semantic(semantic)
.map(SemanticHandle::from_raw)
}
pub fn semantic_count(&self) -> usize {
self.inner.semantic_count()
}
pub fn has_semantics(&self) -> bool {
self.inner.has_semantics()
}
pub fn iter_semantics(&self) -> impl Iterator<Item = (SemanticHandle, &Semantic<SS>)> + '_ {
self.inner
.iter_semantics()
.map(|(id, v)| (SemanticHandle::from_raw(id), v))
}
pub fn iter_semantics_mut(
&mut self,
) -> impl Iterator<Item = (SemanticHandle, &mut Semantic<SS>)> + '_ {
self.inner
.iter_semantics_mut()
.map(|(id, v)| (SemanticHandle::from_raw(id), v))
}
pub fn find_semantic(&self, semantic: &Semantic<SS>) -> Option<SemanticHandle>
where
Semantic<SS>: PartialEq,
{
self.inner
.find_semantic(semantic)
.map(SemanticHandle::from_raw)
}
#[cfg(test)]
pub(crate) fn remove_semantic(&mut self, id: SemanticHandle) -> Option<Semantic<SS>> {
self.inner.remove_semantic(id.to_raw())
}
pub fn get_or_insert_semantic(&mut self, semantic: Semantic<SS>) -> Result<SemanticHandle>
where
Semantic<SS>: PartialEq,
{
self.inner
.get_or_insert_semantic(semantic)
.map(SemanticHandle::from_raw)
}
pub fn get_material(&self, id: MaterialHandle) -> Option<&Material<SS>> {
self.inner.get_material(id.to_raw())
}
pub fn get_material_mut(&mut self, id: MaterialHandle) -> Option<&mut Material<SS>> {
self.inner.get_material_mut(id.to_raw())
}
pub fn add_material(&mut self, material: Material<SS>) -> Result<MaterialHandle> {
self.inner
.add_material(material)
.map(MaterialHandle::from_raw)
}
pub fn material_count(&self) -> usize {
self.inner.material_count()
}
pub fn iter_materials(&self) -> impl Iterator<Item = (MaterialHandle, &Material<SS>)> + '_ {
self.inner
.iter_materials()
.map(|(id, v)| (MaterialHandle::from_raw(id), v))
}
pub fn iter_materials_mut(
&mut self,
) -> impl Iterator<Item = (MaterialHandle, &mut Material<SS>)> + '_ {
self.inner
.iter_materials_mut()
.map(|(id, v)| (MaterialHandle::from_raw(id), v))
}
pub fn find_material(&self, material: &Material<SS>) -> Option<MaterialHandle>
where
Material<SS>: PartialEq,
{
self.inner
.find_material(material)
.map(MaterialHandle::from_raw)
}
#[cfg(test)]
pub(crate) fn remove_material(&mut self, id: MaterialHandle) -> Option<Material<SS>> {
self.inner.remove_material(id.to_raw())
}
pub fn get_or_insert_material(&mut self, material: Material<SS>) -> Result<MaterialHandle>
where
Material<SS>: PartialEq,
{
self.inner
.get_or_insert_material(material)
.map(MaterialHandle::from_raw)
}
pub fn get_texture(&self, id: TextureHandle) -> Option<&Texture<SS>> {
self.inner.get_texture(id.to_raw())
}
pub fn get_texture_mut(&mut self, id: TextureHandle) -> Option<&mut Texture<SS>> {
self.inner.get_texture_mut(id.to_raw())
}
pub fn add_texture(&mut self, texture: Texture<SS>) -> Result<TextureHandle> {
self.inner.add_texture(texture).map(TextureHandle::from_raw)
}
pub fn texture_count(&self) -> usize {
self.inner.texture_count()
}
pub fn iter_textures(&self) -> impl Iterator<Item = (TextureHandle, &Texture<SS>)> + '_ {
self.inner
.iter_textures()
.map(|(id, v)| (TextureHandle::from_raw(id), v))
}
pub fn iter_textures_mut(
&mut self,
) -> impl Iterator<Item = (TextureHandle, &mut Texture<SS>)> + '_ {
self.inner
.iter_textures_mut()
.map(|(id, v)| (TextureHandle::from_raw(id), v))
}
pub fn find_texture(&self, texture: &Texture<SS>) -> Option<TextureHandle>
where
Texture<SS>: PartialEq,
{
self.inner
.find_texture(texture)
.map(TextureHandle::from_raw)
}
#[cfg(test)]
pub(crate) fn remove_texture(&mut self, id: TextureHandle) -> Option<Texture<SS>> {
self.inner.remove_texture(id.to_raw())
}
pub fn get_or_insert_texture(&mut self, texture: Texture<SS>) -> Result<TextureHandle>
where
Texture<SS>: PartialEq,
{
self.inner
.get_or_insert_texture(texture)
.map(TextureHandle::from_raw)
}
pub fn get_geometry(&self, id: GeometryHandle) -> Option<&Geometry<VR, SS>> {
self.inner.get_geometry(id.to_raw())
}
pub fn resolve_geometry(&self, id: GeometryHandle) -> Result<GeometryView<'_, VR, SS>> {
let geometry =
self.get_geometry(id)
.ok_or_else(|| crate::error::Error::InvalidReference {
element_type: "geometry".to_string(),
index: id.to_raw().index() as usize,
max_index: self.geometry_count().saturating_sub(1),
})?;
if let Some(instance) = geometry.instance() {
let template = self
.get_geometry_template(instance.template())
.ok_or_else(|| crate::error::Error::InvalidReference {
element_type: "template geometry".to_string(),
index: instance.template().to_raw().index() as usize,
max_index: self.geometry_template_count().saturating_sub(1),
})?;
Ok(GeometryView::from_geometry(template, Some(instance)))
} else {
Ok(GeometryView::from_geometry(geometry, None))
}
}
pub fn add_geometry(&mut self, geometry: Geometry<VR, SS>) -> Result<GeometryHandle> {
validate_stored_geometry(geometry.raw(), self)?;
self.add_geometry_unchecked(geometry)
}
pub fn add_geometry_unchecked(&mut self, geometry: Geometry<VR, SS>) -> Result<GeometryHandle> {
self.inner
.add_geometry(geometry)
.map(GeometryHandle::from_raw)
}
pub fn geometry_count(&self) -> usize {
self.inner.geometry_count()
}
pub fn iter_geometries(
&self,
) -> impl Iterator<Item = (GeometryHandle, &Geometry<VR, SS>)> + '_ {
self.inner
.iter_geometries()
.map(|(id, v)| (GeometryHandle::from_raw(id), v))
}
pub fn vertices(&self) -> &Vertices<VR, RealWorldCoordinate> {
self.inner.vertices()
}
pub fn add_vertex(
&mut self,
coordinate: RealWorldCoordinate,
) -> crate::error::Result<VertexIndex<VR>> {
self.inner.add_vertex(coordinate)
}
pub fn add_vertices(
&mut self,
coordinates: &[RealWorldCoordinate],
) -> crate::error::Result<std::ops::Range<VertexIndex<VR>>> {
self.inner.add_vertices(coordinates)
}
pub fn get_vertex(&self, index: VertexIndex<VR>) -> Option<&RealWorldCoordinate> {
self.inner.get_vertex(index)
}
pub fn metadata(&self) -> Option<&Metadata<SS>> {
self.inner.metadata()
}
pub fn metadata_mut(&mut self) -> &mut Metadata<SS> {
self.inner.metadata_mut()
}
pub fn id(&self) -> Option<CityObjectHandle> {
self.inner.id().map(CityObjectHandle::from_raw)
}
pub fn set_id(&mut self, id: Option<CityObjectHandle>) {
self.inner.set_id(id.map(CityObjectHandle::to_raw));
}
pub fn extra(&self) -> Option<&crate::v2_0::attributes::Attributes<SS>> {
self.inner.extra()
}
pub fn extra_mut(&mut self) -> &mut crate::v2_0::attributes::Attributes<SS> {
self.inner.extra_mut()
}
pub fn transform(&self) -> Option<&Transform> {
self.inner.transform()
}
pub fn transform_mut(&mut self) -> &mut Transform {
self.inner.transform_mut()
}
pub fn extensions(&self) -> Option<&Extensions<SS>> {
self.inner.extensions()
}
pub fn extensions_mut(&mut self) -> &mut Extensions<SS> {
self.inner.extensions_mut()
}
pub fn cityobjects(&self) -> &CityObjects<SS> {
self.inner.cityobjects()
}
#[inline]
pub fn raw(&self) -> crate::raw::v2_0::CityModelRawAccessor<'_, VR, SS> {
crate::raw::v2_0::CityModelRawAccessor::new(self)
}
pub fn cityobjects_mut(&mut self) -> &mut CityObjects<SS> {
self.inner.cityobjects_mut()
}
pub fn clear_cityobjects(&mut self) {
self.inner.cityobjects_mut().clear();
}
pub fn add_uv_coordinate(
&mut self,
uvcoordinate: UVCoordinate,
) -> crate::error::Result<VertexIndex<VR>> {
self.inner.add_uv_coordinate(uvcoordinate)
}
pub fn get_uv_coordinate(&self, index: VertexIndex<VR>) -> Option<&UVCoordinate> {
self.inner.get_uv_coordinate(index)
}
pub fn vertices_texture(&self) -> &Vertices<VR, UVCoordinate> {
self.inner.vertices_texture()
}
pub fn add_template_vertex(
&mut self,
coordinate: RealWorldCoordinate,
) -> crate::error::Result<VertexIndex<VR>> {
self.inner.add_template_vertex(coordinate)
}
pub fn get_template_vertex(&self, index: VertexIndex<VR>) -> Option<&RealWorldCoordinate> {
self.inner.get_template_vertex(index)
}
pub fn template_vertices(&self) -> &Vertices<VR, RealWorldCoordinate> {
self.inner.template_vertices()
}
pub fn get_geometry_template(&self, id: GeometryTemplateHandle) -> Option<&Geometry<VR, SS>> {
self.inner.get_template_geometry(id.to_raw())
}
pub fn add_geometry_template(
&mut self,
geometry: Geometry<VR, SS>,
) -> Result<GeometryTemplateHandle> {
if *geometry.type_geometry() == GeometryType::GeometryInstance {
return Err(crate::error::Error::InvalidGeometry(
"GeometryInstance cannot be inserted into the template geometry pool".to_string(),
));
}
validate_stored_geometry_for_boundary_source(
geometry.raw(),
self,
BoundaryVertexSource::Template,
)?;
self.add_geometry_template_unchecked(geometry)
}
pub fn add_geometry_template_unchecked(
&mut self,
geometry: Geometry<VR, SS>,
) -> Result<GeometryTemplateHandle> {
self.inner
.add_template_geometry(geometry)
.map(GeometryTemplateHandle::from_raw)
}
pub fn geometry_template_count(&self) -> usize {
self.inner.template_geometry_count()
}
pub fn iter_geometry_templates(
&self,
) -> impl Iterator<Item = (GeometryTemplateHandle, &Geometry<VR, SS>)> + '_ {
self.inner
.iter_template_geometries()
.map(|(id, v)| (GeometryTemplateHandle::from_raw(id), v))
}
pub fn type_citymodel(&self) -> crate::CityModelType {
self.inner.type_citymodel()
}
pub fn version(&self) -> Option<crate::CityJSONVersion> {
self.inner.version()
}
pub fn reserve_import(&mut self, capacities: CityModelCapacities) -> Result<()> {
self.cityobjects_mut().reserve(capacities.cityobjects)?;
self.inner.reserve_vertex_capacity(capacities.vertices)?;
self.inner
.reserve_geometry_capacity(capacities.geometries)?;
self.inner
.reserve_template_vertex_capacity(capacities.template_vertices)?;
self.inner
.reserve_template_geometry_capacity(capacities.template_geometries)?;
self.inner.reserve_semantic_capacity(capacities.semantics)?;
self.inner.reserve_material_capacity(capacities.materials)?;
self.inner.reserve_texture_capacity(capacities.textures)?;
self.inner.reserve_uv_capacity(capacities.uv_coordinates)
}
pub(crate) fn reserve_draft_insert(
&mut self,
mode: super::geometry_draft::DraftInsertMode,
new_vertices: usize,
new_uvs: usize,
) -> Result<()> {
let capacities = match mode {
super::geometry_draft::DraftInsertMode::Regular => CityModelCapacities {
geometries: 1,
vertices: new_vertices,
uv_coordinates: new_uvs,
..CityModelCapacities::default()
},
super::geometry_draft::DraftInsertMode::Template => CityModelCapacities {
template_geometries: 1,
template_vertices: new_vertices,
uv_coordinates: new_uvs,
..CityModelCapacities::default()
},
};
self.reserve_import(capacities)
}
pub fn default_material_theme(&self) -> Option<&ThemeName<SS>> {
self.inner.default_material_theme()
}
pub fn set_default_material_theme(&mut self, theme: Option<ThemeName<SS>>) {
self.inner.set_default_material_theme(theme);
}
pub fn default_texture_theme(&self) -> Option<&ThemeName<SS>> {
self.inner.default_texture_theme()
}
pub fn set_default_texture_theme(&mut self, theme: Option<ThemeName<SS>>) {
self.inner.set_default_texture_theme(theme);
}
pub fn has_material_theme(&self, theme: &str) -> bool {
self.iter_geometries()
.any(|(_, geometry)| Self::geometry_has_material_theme(geometry, theme))
|| self
.iter_geometry_templates()
.any(|(_, geometry)| Self::geometry_has_material_theme(geometry, theme))
}
pub fn has_texture_theme(&self, theme: &str) -> bool {
self.iter_geometries()
.any(|(_, geometry)| Self::geometry_has_texture_theme(geometry, theme))
|| self
.iter_geometry_templates()
.any(|(_, geometry)| Self::geometry_has_texture_theme(geometry, theme))
}
pub fn validate_default_themes(&self) -> Result<()> {
if let Some(theme) = self.default_material_theme()
&& !self.has_material_theme(theme.as_ref())
{
return Err(Error::InvalidThemeName {
theme_type: "material".to_string(),
theme: theme.as_ref().to_string(),
});
}
if let Some(theme) = self.default_texture_theme()
&& !self.has_texture_theme(theme.as_ref())
{
return Err(Error::InvalidThemeName {
theme_type: "texture".to_string(),
theme: theme.as_ref().to_string(),
});
}
Ok(())
}
pub fn extract_float_column(&self, key: &str) -> (Vec<CityObjectHandle>, Vec<f64>) {
self.extract_attribute_column(key, |value| match value {
AttributeValue::Float(value) => Some(*value),
_ => None,
})
}
pub fn extract_integer_column(&self, key: &str) -> (Vec<CityObjectHandle>, Vec<i64>) {
self.extract_attribute_column(key, |value| match value {
AttributeValue::Integer(value) => Some(*value),
_ => None,
})
}
pub fn extract_string_column<'a>(
&'a self,
key: &str,
) -> (Vec<CityObjectHandle>, Vec<&'a SS::String>) {
self.extract_attribute_column(key, |value| match value {
AttributeValue::String(value) => Some(value),
_ => None,
})
}
fn extract_attribute_column<'a, T, F>(
&'a self,
key: &str,
mut select: F,
) -> (Vec<CityObjectHandle>, Vec<T>)
where
F: FnMut(&'a AttributeValue<SS>) -> Option<T>,
{
let mut object_refs = Vec::new();
let mut values = Vec::new();
for (id, cityobject) in self.cityobjects().iter() {
if let Some(attributes) = cityobject.attributes()
&& let Some(value) = attributes.get(key).and_then(&mut select)
{
object_refs.push(id);
values.push(value);
}
}
(object_refs, values)
}
fn geometry_has_material_theme(geometry: &Geometry<VR, SS>, theme: &str) -> bool {
geometry
.materials()
.is_some_and(|themes| themes.iter().any(|(name, _)| name.as_ref() == theme))
}
fn geometry_has_texture_theme(geometry: &Geometry<VR, SS>, theme: &str) -> bool {
geometry
.textures()
.is_some_and(|themes| themes.iter().any(|(name, _)| name.as_ref() == theme))
}
pub fn attribute_keys(&self) -> HashSet<&str> {
let mut keys = HashSet::new();
for (_, cityobject) in self.cityobjects().iter() {
if let Some(attributes) = cityobject.attributes() {
for key in attributes.keys() {
keys.insert(key.as_ref());
}
}
}
keys
}
}
impl<VR: VertexRef, SS: StringStorage> RawAccess for CityModel<VR, SS> {
type Vertex = RealWorldCoordinate;
type Geometry = Geometry<VR, SS>;
type Semantic = Semantic<SS>;
type Material = Material<SS>;
type Texture = Texture<SS>;
fn vertices_raw(&self) -> RawSliceView<'_, Self::Vertex> {
RawSliceView::new(self.vertices().as_slice())
}
fn geometries_raw(&self) -> RawPoolView<'_, Self::Geometry> {
self.inner.geometries_raw()
}
fn semantics_raw(&self) -> RawPoolView<'_, Self::Semantic> {
self.inner.semantics_raw()
}
fn materials_raw(&self) -> RawPoolView<'_, Self::Material> {
self.inner.materials_raw()
}
fn textures_raw(&self) -> RawPoolView<'_, Self::Texture> {
self.inner.textures_raw()
}
}
impl<VR: VertexRef, SS: StringStorage> fmt::Display for CityModel<VR, SS> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "CityModel {{")?;
writeln!(f, "\ttype: {}", self.type_citymodel())?;
writeln!(f, "\tversion: {}", format_option(self.version().as_ref()))?;
writeln!(
f,
"\textensions: {{ {} }}",
format_option(self.extensions())
)?;
writeln!(f, "\tid: {}", format_option(self.id().as_ref()))?;
writeln!(f, "\ttransform: {{ {} }}", format_option(self.transform()))?;
writeln!(f, "\tmetadata: {}", format_option(self.metadata()))?;
writeln!(
f,
"\tCityObjects: {{ nr. cityobjects: {}, nr. geometries: {} }}",
self.cityobjects().len(),
self.geometry_count()
)?;
writeln!(
f,
"\tappearance: {{ nr. materials: {}, nr. textures: {}, nr. vertices-texture: {}, default-theme-texture: {}, default-theme-material: {} }}",
self.material_count(),
self.texture_count(),
self.vertices_texture().len(),
format_option(self.default_texture_theme()),
format_option(self.default_material_theme())
)?;
writeln!(f, "\tgeometry-templates: not implemented")?;
writeln!(
f,
"\tvertices: {{ nr. vertices: {}, quantized coordinates: not implemented }}",
self.vertices().len()
)?;
writeln!(f, "\textra: {}", format_option(self.extra()))?;
writeln!(f, "}}")
}
}
impl<VR: VertexRef, SS: StringStorage> GeometryValidationContext<VR, ResourceId32>
for CityModel<VR, SS>
{
fn semantic_exists(&self, id: ResourceId32) -> bool {
self.inner.get_semantic(id).is_some()
}
fn material_exists(&self, id: ResourceId32) -> bool {
self.inner.get_material(id).is_some()
}
fn texture_exists(&self, id: ResourceId32) -> bool {
self.inner.get_texture(id).is_some()
}
fn uv_exists(&self, id: VertexIndex<VR>) -> bool {
self.inner.get_uv_coordinate(id).is_some()
}
fn regular_vertex_exists(&self, id: VertexIndex<VR>) -> bool {
self.inner.get_vertex(id).is_some()
}
fn template_vertex_exists(&self, id: VertexIndex<VR>) -> bool {
self.inner.get_template_vertex(id).is_some()
}
fn template_geometry_exists(&self, id: ResourceId32) -> bool {
self.inner.get_template_geometry(id).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CityModelType;
use crate::backend::default::geometry::GeometryInstanceData;
use crate::resources::id::ResourceId32;
use crate::v2_0::appearance::ImageType;
use crate::v2_0::appearance::material::Material;
use crate::v2_0::appearance::texture::Texture;
use crate::v2_0::boundary::nested::BoundaryNestedMultiPoint32;
use crate::v2_0::geometry::{AffineTransform3D, LoD, StoredGeometryParts};
use crate::v2_0::{
CityObject, CityObjectIdentifier, CityObjectType, GeometryDraft, RingDraft, SurfaceDraft,
};
fn multi_point_geometry(
vertices: BoundaryNestedMultiPoint32,
) -> Geometry<u32, OwnedStringStorage> {
Geometry::from_raw_parts(
GeometryType::MultiPoint,
Some(LoD::LoD1),
Some(vertices.into()),
None,
None,
None,
None,
)
}
fn stored_multi_point_geometry(
vertices: BoundaryNestedMultiPoint32,
) -> Geometry<u32, OwnedStringStorage> {
Geometry::from_stored_parts(StoredGeometryParts {
type_geometry: GeometryType::MultiPoint,
lod: Some(LoD::LoD1),
boundaries: Some(vertices.into()),
semantics: None,
materials: None,
textures: None,
instance: None,
})
}
#[test]
fn add_geometry_rejects_missing_regular_boundary_vertex_even_when_template_vertex_exists() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model
.add_template_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
.unwrap();
let err = model
.add_geometry(multi_point_geometry(vec![0u32]))
.unwrap_err();
assert!(format!("{err}").contains("missing regular vertex"));
}
#[test]
fn add_geometry_template_rejects_missing_template_boundary_vertex_even_when_regular_vertex_exists()
{
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model
.add_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
.unwrap();
let err = model
.add_geometry_template(multi_point_geometry(vec![0u32]))
.unwrap_err();
assert!(format!("{err}").contains("missing template vertex"));
}
#[test]
fn add_geometry_unchecked_accepts_valid_stored_geometry() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model
.add_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
.unwrap();
let geometry = stored_multi_point_geometry(vec![0u32]);
let handle = model.add_geometry_unchecked(geometry).unwrap();
assert_eq!(model.geometry_count(), 1);
assert_eq!(
model.get_geometry(handle).unwrap().type_geometry(),
&GeometryType::MultiPoint
);
}
#[test]
fn add_geometry_template_unchecked_accepts_valid_stored_geometry() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model
.add_template_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
.unwrap();
let geometry = stored_multi_point_geometry(vec![0u32]);
let handle = model.add_geometry_template_unchecked(geometry).unwrap();
assert_eq!(model.geometry_template_count(), 1);
assert_eq!(
model.get_geometry_template(handle).unwrap().type_geometry(),
&GeometryType::MultiPoint
);
}
#[test]
fn add_geometry_template_rejects_geometry_instance() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
let geometry = Geometry::from_raw_parts(
GeometryType::GeometryInstance,
None,
None,
None,
None,
None,
Some(GeometryInstanceData::new(
ResourceId32::new(0, 0),
VertexIndex::new(0),
AffineTransform3D::identity(),
)),
);
let err = model.add_geometry_template(geometry).unwrap_err();
assert_eq!(
format!("{err}"),
"GeometryInstance cannot be inserted into the template geometry pool"
);
}
#[test]
fn set_default_material_theme_stores_theme_name_without_validation() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model.set_default_material_theme(Some(ThemeName::new("missing-theme".to_string())));
assert_eq!(
model.default_material_theme().map(AsRef::as_ref),
Some("missing-theme")
);
}
#[test]
fn validate_default_themes_rejects_missing_material_theme() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model.set_default_material_theme(Some(ThemeName::new("missing-theme".to_string())));
let err = model.validate_default_themes().unwrap_err();
assert!(matches!(
err,
Error::InvalidThemeName { ref theme_type, ref theme }
if theme_type == "material" && theme == "missing-theme"
));
}
#[test]
fn validate_default_themes_rejects_missing_texture_theme() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model.set_default_texture_theme(Some(ThemeName::new("missing-theme".to_string())));
let err = model.validate_default_themes().unwrap_err();
assert!(matches!(
err,
Error::InvalidThemeName { ref theme_type, ref theme }
if theme_type == "texture" && theme == "missing-theme"
));
}
#[test]
fn validate_default_themes_accepts_present_geometry_themes() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
let material = model
.add_material(Material::new("mat-a".to_string()))
.unwrap();
let texture = model
.add_texture(Texture::new("tex-a.png".to_string(), ImageType::Png))
.unwrap();
let theme = ThemeName::new("theme-a".to_string());
GeometryDraft::multi_surface(
None,
[SurfaceDraft::new(
RingDraft::new([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]).with_texture(
theme.clone(),
texture,
[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
),
[],
)
.with_material(theme.clone(), material)],
)
.insert_into(&mut model)
.unwrap();
model.set_default_material_theme(Some(theme.clone()));
model.set_default_texture_theme(Some(theme.clone()));
assert!(model.has_material_theme("theme-a"));
assert!(model.has_texture_theme("theme-a"));
assert!(model.validate_default_themes().is_ok());
}
#[test]
fn cityjsonfeature_root_id_is_stored_as_typed_model_state() {
let mut model = OwnedCityModel::new(CityModelType::CityJSONFeature);
let handle = model
.cityobjects_mut()
.add(CityObject::new(
CityObjectIdentifier::new("feature-1".to_string()),
CityObjectType::Building,
))
.unwrap();
model.set_id(Some(handle));
assert_eq!(model.id(), Some(handle));
assert!(model.extra().is_none());
}
#[test]
fn cityjson_root_extra_id_remains_independent_from_typed_feature_id() {
let mut model = OwnedCityModel::new(CityModelType::CityJSON);
model.extra_mut().insert(
"id".to_string(),
AttributeValue::String("document-root-id".to_string()),
);
assert_eq!(model.id(), None);
assert_eq!(
model.extra().and_then(|extra| extra.get("id")),
Some(&AttributeValue::String("document-root-id".to_string()))
);
}
}