Skip to content

Commit

Permalink
CityGMLを読むだけのテストを追加 (#37)
Browse files Browse the repository at this point in the history
nusamai-plateau にテストが一切ないので、竹芝エリアの building
を読み込んで、読み込まれた都市オブジェクト数などを確認するだけのテストを追加します。レグレッション防止程度の意味合い。

test coverageの上昇 → https://app.codecov.io/gh/MIERUNE/nusamai/pull/37
  • Loading branch information
ciscorn authored Dec 7, 2023
1 parent 61c81e8 commit 3f21987
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 24 deletions.
2 changes: 2 additions & 0 deletions nusamai-plateau/citygml/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ fn generate_citygml_struct_model(
});
};

add_arm(0, b"lod0RoofEdge", "MultiSurface");
add_arm(0, b"lod0FootPrint", "MultiSurface");
add_arm(1, b"lod1Solid", "Solid");
add_arm(1, b"lod1MultiSurface", "MultiSurface");
add_arm(2, b"lod2MultiSurface", "MultiSurface");
Expand Down
22 changes: 11 additions & 11 deletions nusamai-plateau/citygml/src/geometric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,32 @@ pub type GeometryRef = Vec<GeometryRefEntry>;
#[derive(Debug, Default)]
pub struct Geometries {
pub vertices: Vec<[f64; 3]>,
pub polygons: MultiPolygon<'static, 1, u32>,
pub linestrings: MultiLineString<'static, 1, u32>,
pub multipolygon: MultiPolygon<'static, 1, u32>,
pub multilinestring: MultiLineString<'static, 1, u32>,
}

/// Store for collecting vertices and polygons from GML.
#[derive(Default)]
pub struct GeometryCollector {
pub vertices: indexmap::IndexSet<[u64; 3]>,
pub multi_polygon: MultiPolygon<'static, 1, u32>,
pub multi_linestring: MultiLineString<'static, 1, u32>,
pub multipolygon: MultiPolygon<'static, 1, u32>,
pub multilinestring: MultiLineString<'static, 1, u32>,
}

impl GeometryCollector {
pub fn add_exterior_ring(&mut self, iter: impl Iterator<Item = [f64; 3]>) -> usize {
self.multi_polygon.add_exterior(iter.map(|v| {
self.multipolygon.add_exterior(iter.map(|v| {
// ...
let vbits = [v[0].to_bits(), v[1].to_bits(), v[2].to_bits()];
let (index, _) = self.vertices.insert_full(vbits);
[index as u32]
}));

self.multi_polygon.len() - 1
self.multipolygon.len() - 1
}

pub fn add_interior_ring(&mut self, iter: impl Iterator<Item = [f64; 3]>) {
self.multi_polygon.add_interior(iter.map(|v| {
self.multipolygon.add_interior(iter.map(|v| {
// ...
let vbits = [v[0].to_bits(), v[1].to_bits(), v[2].to_bits()];
let (index, _) = self.vertices.insert_full(vbits);
Expand All @@ -81,14 +81,14 @@ impl GeometryCollector {
}
Geometries {
vertices,
polygons: self.multi_polygon.clone(),
linestrings: self.multi_linestring.clone(),
multipolygon: self.multipolygon.clone(),
multilinestring: self.multilinestring.clone(),
}
}

pub fn clear(&mut self) {
self.vertices.clear();
self.multi_polygon.clear();
self.multi_linestring.clear();
self.multipolygon.clear();
self.multilinestring.clear();
}
}
3 changes: 2 additions & 1 deletion nusamai-plateau/citygml/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
const IUR_PREFIX: &[u8] = b"https://www.geospatial.jp/iur/";
if name.starts_with(OPENGIS_PREFIX) {
let s = &name[OPENGIS_PREFIX.len()..];
// GML 3.1.1
if s == b"gml" {
b"gml:"
} else if s.starts_with(b"citygml/") {
// CityGML 2.0
match &s[b"citygml/".len()..] {
// CityGML
b"2.0" => b"core:",
b"appearance/2.0" => b"app:",
b"building/2.0" => b"bldg:",
Expand Down
24 changes: 12 additions & 12 deletions nusamai-plateau/citygml/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,14 @@ impl<R: BufRead> SubTreeReader<'_, R> {
geomrefs: &mut GeometryRef,
lod: u8,
) -> Result<(), ParseError> {
let poly_begin = self.state.geometry_collector.multi_polygon.len();
let poly_begin = self.state.geometry_collector.multipolygon.len();

if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"MultiSurface")? {
self.parse_multi_surface()?;
expect_end(self.reader, &mut self.state.buf1)?;
}

let poly_end = self.state.geometry_collector.multi_polygon.len();
let poly_end = self.state.geometry_collector.multipolygon.len();
if poly_end - poly_begin > 0 {
geomrefs.push(GeometryRefEntry {
ty: GeometryType::Surface,
Expand All @@ -272,14 +272,14 @@ impl<R: BufRead> SubTreeReader<'_, R> {
}

fn parse_solid_prop(&mut self, geomrefs: &mut GeometryRef, lod: u8) -> Result<(), ParseError> {
let poly_begin = self.state.geometry_collector.multi_polygon.len();
let poly_begin = self.state.geometry_collector.multipolygon.len();

if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"Solid")? {
self.parse_solid()?;
expect_end(self.reader, &mut self.state.buf1)?;
}

let poly_end = self.state.geometry_collector.multi_polygon.len();
let poly_end = self.state.geometry_collector.multipolygon.len();
if poly_end - poly_begin > 0 {
geomrefs.push(GeometryRefEntry {
ty: GeometryType::Solid,
Expand All @@ -300,7 +300,7 @@ impl<R: BufRead> SubTreeReader<'_, R> {
match self.reader.read_event_into(&mut self.state.buf1) {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
let poly_begin = self.state.geometry_collector.multi_polygon.len();
let poly_begin = self.state.geometry_collector.multipolygon.len();

let geomtype = match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"Solid") => {
Expand All @@ -315,10 +315,10 @@ impl<R: BufRead> SubTreeReader<'_, R> {
self.parse_composite_surface()?;
GeometryType::Surface
}
// (Bound(GML_NS), b"OrientableSurface") => ...
// (Bound(GML_NS), b"Polygon") => ...
// (Bound(GML_NS), b"TriangulatedSurface") => ...
// (Bound(GML_NS), b"Tin") => ...
(Bound(GML_NS), b"OrientableSurface") => todo!(),
(Bound(GML_NS), b"Polygon") => todo!(),
(Bound(GML_NS), b"TriangulatedSurface") => todo!(),
(Bound(GML_NS), b"Tin") => todo!(),
_ => {
return Err(ParseError::SchemaViolation(format!(
"Unexpected element <{}>",
Expand All @@ -327,7 +327,7 @@ impl<R: BufRead> SubTreeReader<'_, R> {
}
};

let poly_end = self.state.geometry_collector.multi_polygon.len();
let poly_end = self.state.geometry_collector.multipolygon.len();
if poly_end - poly_begin > 0 {
geomrefs.push(GeometryRefEntry {
ty: geomtype,
Expand Down Expand Up @@ -355,7 +355,7 @@ impl<R: BufRead> SubTreeReader<'_, R> {
geomrefs: &mut GeometryRef,
lod: u8,
) -> Result<(), ParseError> {
let poly_begin = self.state.geometry_collector.multi_polygon.len();
let poly_begin = self.state.geometry_collector.multipolygon.len();

loop {
match self.reader.read_event_into(&mut self.state.buf1) {
Expand Down Expand Up @@ -385,7 +385,7 @@ impl<R: BufRead> SubTreeReader<'_, R> {
}
}

let poly_end = self.state.geometry_collector.multi_polygon.len();
let poly_end = self.state.geometry_collector.multipolygon.len();
if poly_end - poly_begin > 0 {
geomrefs.push(GeometryRefEntry {
ty: GeometryType::Triangle,
Expand Down
59 changes: 59 additions & 0 deletions nusamai-plateau/citygml/tests/test_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use chrono::NaiveDate;
use citygml::{CityGMLElement, CityGMLReader};

#[test]
fn parse_date() {
#[derive(CityGMLElement, Default)]
struct Root {
#[citygml(path = b"date")]
date: Option<chrono::NaiveDate>,
}

let mut xml_reader = quick_xml::NsReader::from_reader(std::io::Cursor::new(
r#"<root><date>2019-03-21</date></root>"#,
));
match CityGMLReader::new().start_root(&mut xml_reader) {
Ok(mut st) => {
let mut root = Root::default();
root.parse(&mut st).unwrap();
assert!(root.date.is_some());
assert_eq!(root.date, NaiveDate::from_ymd_opt(2019, 3, 21));
}
Err(e) => panic!("Err: {:?}", e),
}
}

#[test]
fn parse_boolean() {
#[derive(CityGMLElement, Default)]
struct Root {
#[citygml(path = b"boolean")]
bools: Vec<bool>,
}

let mut xml_reader = quick_xml::NsReader::from_reader(std::io::Cursor::new(
r#"
<root>
<boolean>true</boolean>
<boolean>True</boolean>
<boolean>TRUE</boolean>
<boolean>1</boolean>
<boolean>false</boolean>
<boolean>False</boolean>
<boolean>FALSE</boolean>
<boolean>0</boolean>
</root>
"#,
));
match CityGMLReader::new().start_root(&mut xml_reader) {
Ok(mut st) => {
let mut root = Root::default();
root.parse(&mut st).unwrap();
assert_eq!(
root.bools,
[true, true, true, true, false, false, false, false]
);
}
Err(e) => panic!("Err: {:?}", e),
}
}
1 change: 1 addition & 0 deletions nusamai-plateau/examples/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn main() {
println!("elapsed time parsing: {:?}", parsing_time);
total_parsing_time += parsing_time;
}

println!("total parsing time: {:?}", total_parsing_time);
}

Expand Down
Binary file not shown.
5 changes: 5 additions & 0 deletions nusamai-plateau/tests/data/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
テスト用データ出典

- 「3D都市モデル(Project PLATEAU)東京都23区 ポートシティ竹芝 建築物モデル(LOD4)(2022年度)」 (https://www.geospatial.jp/ckan/dataset/plateau-tokyo23ku-2023-lod4)

Project PLATEAUのサイトポリシーに従って、どなたでも、複製、公衆送信、翻訳・変形等の翻案等、自由に利用できます。商用利用も可能です。 (https://www.mlit.go.jp/plateau/site-policy/)
79 changes: 79 additions & 0 deletions nusamai-plateau/tests/test_simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::io::BufRead;

use citygml::{CityGMLElement, CityGMLReader, ParseError, SubTreeReader};
use nusamai_plateau::models::CityObject;

#[derive(Default, Debug)]
struct Counter {
city_objects: usize,
appearances: usize,
multipolygons: usize,
buildings: usize,
cityobjectgroups: usize,
}

fn example_toplevel_dispatcher<R: BufRead>(
st: &mut SubTreeReader<R>,
) -> Result<Counter, ParseError> {
let mut counter = Counter::default();

match st.parse_children(|st| match st.current_path() {
b"core:cityObjectMember" => {
let mut cityobj: CityObject = Default::default();
cityobj.parse(st)?;
match cityobj {
CityObject::Building(_) => counter.buildings += 1,
CityObject::CityObjectGroup(_) => counter.cityobjectgroups += 1,
e => panic!("Unexpected city object type: {:?}", e),
}
let geometries = st.collect_geometries();
counter.city_objects += 1;
counter.multipolygons += geometries.multipolygon.len();
Ok(())
}
b"gml:boundedBy" => {
st.skip_current_element()?;
Ok(())
}
b"app:appearanceMember" => {
st.skip_current_element()?;
counter.appearances += 1;
Ok(())
}
other => Err(ParseError::SchemaViolation(format!(
"Unrecognized element {}",
String::from_utf8_lossy(other)
))),
}) {
Ok(_) => Ok(counter),
Err(e) => {
println!("Err: {:?}", e);
Err(e)
}
}
}

#[test]
fn simple_read() {
let reader = std::io::BufReader::new(
zstd::stream::Decoder::new(
std::fs::File::open("./tests/data/53393680_bldg_6697_lod4.2_op.gml.zst").unwrap(),
)
.unwrap(),
);

let mut xml_reader = quick_xml::NsReader::from_reader(reader);
match CityGMLReader::new().start_root(&mut xml_reader) {
Ok(mut st) => match example_toplevel_dispatcher(&mut st) {
Ok(counter) => {
assert_eq!(counter.city_objects, 1527);
assert_eq!(counter.appearances, 1);
assert_eq!(counter.multipolygons, 197633);
assert_eq!(counter.buildings, 1485);
assert_eq!(counter.cityobjectgroups, 42);
}
Err(e) => panic!("Err: {:?}", e),
},
Err(e) => panic!("Err: {:?}", e),
};
}

0 comments on commit 3f21987

Please sign in to comment.