From ab264e8cd6d025a4e45e9cb7e77c491ceca5abfa Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Thu, 27 Feb 2025 13:04:04 +0100 Subject: [PATCH 01/12] Bump dev version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 65a93c2..3853b7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ #include -std::string CITY4CFD_VERSION = "0.6.2"; +std::string CITY4CFD_VERSION = "0.6.2+dev"; void printWelcome() { auto logo{ From e30dabeaab02e8d68711918eeb05a6abecb336d0 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Tue, 4 Mar 2025 20:40:09 +0100 Subject: [PATCH 02/12] Fix cityjson surface layer output --- src/SurfaceLayer.cpp | 17 +++-------------- src/SurfaceLayer.h | 3 --- src/io.cpp | 21 +++++++++++++++------ 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/SurfaceLayer.cpp b/src/SurfaceLayer.cpp index 198dd92..e878e9e 100644 --- a/src/SurfaceLayer.cpp +++ b/src/SurfaceLayer.cpp @@ -29,21 +29,12 @@ #include "geomutils.h" -SurfaceLayer::SurfaceLayer() - : PolyFeature() {} - SurfaceLayer::SurfaceLayer(const int outputLayerID) : PolyFeature(outputLayerID) {} -SurfaceLayer::SurfaceLayer(const nlohmann::json& poly) - : PolyFeature(poly) {} - SurfaceLayer::SurfaceLayer(const nlohmann::json& poly, const int outputLayerID) : PolyFeature(poly, outputLayerID) {} -SurfaceLayer::SurfaceLayer(const Polygon_with_attr& poly) - : PolyFeature(poly) {} - SurfaceLayer::SurfaceLayer(const Polygon_with_attr& poly, const int outputLayerID) : PolyFeature(poly, outputLayerID) {} @@ -59,15 +50,13 @@ void SurfaceLayer::check_feature_scope(const Polygon_2& bndPoly) { } void SurfaceLayer::get_cityjson_info(nlohmann::json& b) const { - + b["type"] = "TINRelief"; } -void SurfaceLayer::get_cityjson_semantics(nlohmann::json& g) const { - -} +void SurfaceLayer::get_cityjson_semantics(nlohmann::json& /*g*/) const { } std::string SurfaceLayer::get_cityjson_primitive() const { - return "Dunno yet"; + return "CompositeSurface"; } TopoClass SurfaceLayer::get_class() const { diff --git a/src/SurfaceLayer.h b/src/SurfaceLayer.h index 7ed3185..d6c14aa 100644 --- a/src/SurfaceLayer.h +++ b/src/SurfaceLayer.h @@ -32,11 +32,8 @@ class SurfaceLayer : public PolyFeature { public: - SurfaceLayer(); SurfaceLayer(const int outputLayerID); - SurfaceLayer(const nlohmann::json& poly); SurfaceLayer(const nlohmann::json& poly, const int outputLayerID); - SurfaceLayer(const Polygon_with_attr& poly); SurfaceLayer(const Polygon_with_attr& poly, const int outputLayerID); ~SurfaceLayer() = default; diff --git a/src/io.cpp b/src/io.cpp index e12c329..9364248 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -31,6 +31,7 @@ #include "TopoFeature.h" #include "Boundary.h" #include "Building.h" +#include "SurfaceLayer.h" #include #include @@ -406,8 +407,9 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { j["metadata"]["referenceSystem"] = "urn:ogc:def:crs:EPSG::7415"; std::unordered_map dPts; for (auto& f : allFeatures) { - // Only Buildings and Terrain for now - if (f->get_class() != BUILDING && f->get_class() != TERRAIN) continue; + if (f->get_class() != BUILDING && + f->get_class() != TERRAIN && + f->get_class() != SURFACELAYER) continue; //-- Get feature info nlohmann::json b; f->get_cityjson_info(b); @@ -416,8 +418,8 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { nlohmann::json g; g["type"] = f->get_cityjson_primitive(); // grab lod information for buildings - auto derivedClass = std::dynamic_pointer_cast(f); - if (derivedClass) g["lod"] = derivedClass->get_lod(); + auto buildingDerived = std::dynamic_pointer_cast(f); + if (buildingDerived) g["lod"] = buildingDerived->get_lod(); IO::get_cityjson_geom(f->get_mesh(), g, dPts); @@ -426,7 +428,14 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { //-- Append to main json struct b["geometry"].push_back(g); - j["CityObjects"][f->get_id()] = b; + + // store terran and surface layers with the name, rest with the id + if (f->get_class() == TERRAIN || + f->get_class() == SURFACELAYER) { + j["CityObjects"][Config::get().outputSurfaces[f->get_output_layer_id()]] = b; + } else { + j["CityObjects"][f->get_id()] = b; + } } //-- Vertices - store them in a vector to quickly sort @@ -441,7 +450,7 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { j["vertices"].push_back({std::stod(c[0], NULL), std::stod(c[1], NULL), std::stod(c[2], NULL) }); } - of.open(Config::get().outputFileName + ".json"); + of.open(Config::get().outputFileName + ".city.json"); of << j.dump() << std::endl; } From 193621a060e7b5fd4566393f5401c730683aad4d Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Tue, 4 Mar 2025 21:06:12 +0100 Subject: [PATCH 03/12] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0906c20..fc24630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## [Unreleased] +### Fixed +- CityJSON terrain output + ## [0.6.2] - 2025-02-27 ### Changed - Improved inserting surface layer polygons when terrain pc is missing From 07183cc2698409612a19f58da2cc5cd83ee7b788 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Tue, 4 Mar 2025 22:07:21 +0100 Subject: [PATCH 04/12] Cleanup --- src/LoD22.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/LoD22.cpp b/src/LoD22.cpp index 7c36b35..953afb6 100644 --- a/src/LoD22.cpp +++ b/src/LoD22.cpp @@ -88,16 +88,12 @@ void LoD22::reconstruct(const PointSet3Ptr& buildingPtsPtr, ++j; } - //todo groundPts flag // reconstruct -// m_rooferMeshes = roofer::reconstruct_single_instance(buildingPts, groundPts, linearRing, -// {.lambda = config.m_lambda, -// .lod = config.m_lod, -// .lod13_step_height = config.m_lod13_step_height}); m_rooferMeshes = roofer::reconstruct_single_instance(buildingPts, linearRing, +// groundPts, //todo groundPts flag {.lambda = config.m_lambda, - .lod = config.m_lod, - .lod13_step_height = config.m_lod13_step_height}); + .lod = config.m_lod, + .lod13_step_height = config.m_lod13_step_height}); // store the first mesh from the vector, rest should be handled separately auto rooferMesh = m_rooferMeshes.front(); From 26e3d4542c914375bbc64a64835caad3376500de Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Wed, 5 Mar 2025 16:09:17 +0100 Subject: [PATCH 05/12] Make sure cityjson output is valid --- src/Boundary.cpp | 24 ++++--------------- src/Boundary.h | 5 +--- src/ImportedBuilding.cpp | 1 - src/TopoFeature.cpp | 12 +++------- src/io.cpp | 50 ++++++++++++++++++++++++++++++++++------ src/io.h | 6 ++++- 6 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/Boundary.cpp b/src/Boundary.cpp index 37f0e40..37c340c 100644 --- a/src/Boundary.cpp +++ b/src/Boundary.cpp @@ -153,8 +153,7 @@ void Boundary::prep_output(Vector_2 edge) { throw city4cfd_error("Cannot find side for output!"); } -std::vector Boundary::get_domain_bbox() { - //todo: proper bbox calculation +std::vector Boundary::get_outer_bnd_bbox() { double maxx(-global::largnum), maxy(-global::largnum), maxz(-global::largnum); double minx(global::largnum), miny(global::largnum), minz(global::largnum); @@ -165,24 +164,9 @@ std::vector Boundary::get_domain_bbox() { if (pt.y() > maxy) maxy = pt.y(); else if (pt.y() < miny) miny = pt.y(); -// if (pt.z() > maxz) maxz = pt.z(); -// else if (pt.z() < minz) minz = pt.z(); + if (pt.z() > maxz) maxz = pt.z(); + else if (pt.z() < minz) minz = pt.z(); } - minz = -5; - maxz = 100; return std::vector {minx, miny, minz, maxx, maxy, maxz}; -} - -//-- TEMP -void Boundary::get_cityjson_info(nlohmann::json& b) const { - //temp -} - -void Boundary::get_cityjson_semantics(nlohmann::json& g) const { - //temp -} - -std::string Boundary::get_cityjson_primitive() const { - return ""; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/Boundary.h b/src/Boundary.h index bb57a58..652ebee 100644 --- a/src/Boundary.h +++ b/src/Boundary.h @@ -40,7 +40,7 @@ class Boundary : public TopoFeature { static void set_bounds_to_buildings_pc(Point_set_3& pointCloud, const Polygon_2& pcBndPoly); static void set_bounds_to_terrain_pc(Point_set_3& pointCloud, const Polygon_2& bndPoly, const Polygon_2& pcBndPoly, const Polygon_2& startBufferPoly); - static std::vector get_domain_bbox(); + static std::vector get_outer_bnd_bbox(); virtual void reconstruct() = 0; @@ -49,9 +49,6 @@ class Boundary : public TopoFeature { virtual TopoClass get_class() const = 0; virtual std::string get_class_name() const = 0; - virtual void get_cityjson_info(nlohmann::json& b) const; - virtual void get_cityjson_semantics(nlohmann::json& g) const; - virtual std::string get_cityjson_primitive() const; protected: static std::vector s_outerPts; diff --git a/src/ImportedBuilding.cpp b/src/ImportedBuilding.cpp index 67391e1..16274e8 100644 --- a/src/ImportedBuilding.cpp +++ b/src/ImportedBuilding.cpp @@ -39,7 +39,6 @@ #include #include - int ImportedBuilding::noBottom = 0; ImportedBuilding::ImportedBuilding(std::unique_ptr& buildingJson, PointSet3Ptr& importedBuildingPts) diff --git a/src/TopoFeature.cpp b/src/TopoFeature.cpp index 69e7e38..0b0c6ef 100644 --- a/src/TopoFeature.cpp +++ b/src/TopoFeature.cpp @@ -81,16 +81,10 @@ void TopoFeature::deactivate() { m_f_active = false; } -void TopoFeature::get_cityjson_info(nlohmann::json& j) const { - //TEMP UNTIL ALL FUNCTIONS ARE IMPLEMENTED -} - -void TopoFeature::get_cityjson_semantics(nlohmann::json& g) const { - // TEMP until I figure what to do with this -} +void TopoFeature::get_cityjson_info(nlohmann::json& /*j*/) const {} +void TopoFeature::get_cityjson_semantics(nlohmann::json& /*g*/) const {} std::string TopoFeature::get_cityjson_primitive() const { - //TEMP UNTIL ALL FUNCTIONS ARE IMPLEMENTED - return "Nope"; + return ""; } \ No newline at end of file diff --git a/src/io.cpp b/src/io.cpp index 9364248..94344e4 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -402,10 +402,17 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { j["type"] = "CityJSON"; j["version"] = "2.0"; j["metadata"] = {}; - std::vector bbox = Boundary::get_domain_bbox(); - j["metadata"]["geographicalExtent"] = Boundary::get_domain_bbox(); - j["metadata"]["referenceSystem"] = "urn:ogc:def:crs:EPSG::7415"; + j["CityObjects"] = {}; + j["vertices"] = {}; + + // CityJSON transform and scale are hardcoded to 0 and 0.001 + j["transform"] = {}; + j["transform"]["scale"] = {0.001, 0.001, 0.001}; + j["transform"]["translate"] = {0, 0, 0}; + +// j["metadata"]["referenceSystem"] = "urn:ogc:def:crs:EPSG::7415"; // CRS is not defined due to transform by point of interest std::unordered_map dPts; + std::vector minMaxZ = {global::largnum, -global::largnum}; for (auto& f : allFeatures) { if (f->get_class() != BUILDING && f->get_class() != TERRAIN && @@ -421,7 +428,7 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { auto buildingDerived = std::dynamic_pointer_cast(f); if (buildingDerived) g["lod"] = buildingDerived->get_lod(); - IO::get_cityjson_geom(f->get_mesh(), g, dPts); + IO::get_cityjson_geom(f->get_mesh(), g, dPts, minMaxZ); //-- Get feature semantics f->get_cityjson_semantics(g); @@ -437,6 +444,10 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { j["CityObjects"][f->get_id()] = b; } } + auto bbox = Boundary::get_outer_bnd_bbox(); + bbox[2] = minMaxZ[0]; + bbox[5] = minMaxZ[1]; + j["metadata"]["geographicalExtent"] = bbox; //-- Vertices - store them in a vector to quickly sort std::vector thepts; @@ -497,14 +508,22 @@ void IO::get_stl_pts(Mesh& mesh, std::string& fs) { } } -void IO::get_cityjson_geom(const Mesh& mesh, nlohmann::json& g, std::unordered_map& dPts) { +void IO::get_cityjson_geom(const Mesh& mesh, + nlohmann::json& g, + std::unordered_map& dPts, + std::vector& minMaxZ) { + // CityJSON transform and scale are hardcoded to 0 and 0.001 + const double inverseScale = 1000.; // hardcoded scale for CityJSON is 0.001, so we are multiplying by 1000 to store it + const double inverseTranslate = 0.; // hardcoded translation + g["boundaries"]; for (auto face: mesh.faces()) { if (IO::is_degen(mesh, face)) continue; std::vector tempPoly; tempPoly.reserve(3); for (auto index: CGAL::vertices_around_face(mesh.halfedge(face), mesh)) { - std::string pt = gen_key_bucket(mesh.point(index)); + // store point as a string with CityJSON transformations + std::string pt = gen_key_bucket_int(mesh.point(index), inverseScale, inverseTranslate); auto it = dPts.find(pt); if (it == dPts.end()) { @@ -514,6 +533,10 @@ void IO::get_cityjson_geom(const Mesh& mesh, nlohmann::json& g, std::unordered_m } else { tempPoly.push_back(it->second); } + + // get the z value for the bounding box + minMaxZ[0] = std::min(minMaxZ[0], mesh.point(index).z()); + minMaxZ[1] = std::max(minMaxZ[1], mesh.point(index).z()); } g["boundaries"].push_back({tempPoly}); } @@ -627,4 +650,17 @@ std::string IO::gen_key_bucket(const T& p) { } //- Explicit template instantiation template std::string IO::gen_key_bucket(const Point_3& p); -template std::string IO::gen_key_bucket(const Vector_3& p); \ No newline at end of file +template std::string IO::gen_key_bucket(const Vector_3& p); + +template +std::string IO::gen_key_bucket_int(const T& p, const double inverseScale, const double inverseTranslate) { + std::stringstream ss; + ss << std::fixed << std::setprecision(0) + << (p.x() + inverseTranslate) * inverseScale << " " + << (p.y() + inverseTranslate) * inverseScale << " " + << (p.z() + inverseTranslate) * inverseScale; + return ss.str(); +} +//- Explicit template instantiation +template std::string IO::gen_key_bucket_int(const Point_3& p, const double inverseScale, const double inverseTransform); +template std::string IO::gen_key_bucket_int(const Vector_3& p, const double inverseScale, const double inverseTransform); diff --git a/src/io.h b/src/io.h index 4255798..a2b9a50 100644 --- a/src/io.h +++ b/src/io.h @@ -50,7 +50,10 @@ namespace IO { void get_obj_pts(const Mesh& mesh, std::string& fs, std::string& bs, std::unordered_map& dPts); void get_stl_pts(Mesh& mesh, std::string& fs); - void get_cityjson_geom(const Mesh& mesh, nlohmann::json& g, std::unordered_map& dPts); + void get_cityjson_geom(const Mesh& mesh, + nlohmann::json& g, + std::unordered_map& dPts, + std::vector& minMaxZ); bool not_same(std::vector idxLst); bool is_degen(const Mesh& mesh, Mesh::Face_index face); @@ -63,6 +66,7 @@ namespace IO { //-- Templated function template std::string gen_key_bucket(const T& p); + template std::string gen_key_bucket_int(const T& p, const double inverseScale, const double inverseTranslate); } #endif //CITY4CFD_IO_H \ No newline at end of file From 9aed8a1fa745346ed47ef7e0c6c9c62ee3efcfaa Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Wed, 5 Mar 2025 16:18:01 +0100 Subject: [PATCH 06/12] Cleanup --- src/ImportedBuilding.h | 3 --- src/PolyFeature.h | 3 --- src/SurfaceLayer.cpp | 2 -- src/SurfaceLayer.h | 1 - 4 files changed, 9 deletions(-) diff --git a/src/ImportedBuilding.h b/src/ImportedBuilding.h index 9d31dd3..eb2d21e 100644 --- a/src/ImportedBuilding.h +++ b/src/ImportedBuilding.h @@ -53,9 +53,6 @@ class ImportedBuilding : public Building { const int get_lod_idx() const; const bool is_appending() const; -// virtual void get_cityjson_info(nlohmann::json& b) const override; -// virtual void get_cityjson_semantics(nlohmann::json& g) const override; - protected: std::unordered_map m_ptMap; std::unique_ptr m_buildingJson; diff --git a/src/PolyFeature.h b/src/PolyFeature.h index f7c95af..ac5c86c 100644 --- a/src/PolyFeature.h +++ b/src/PolyFeature.h @@ -57,9 +57,6 @@ class PolyFeature : public TopoFeature { void calc_min_bbox(); void clear_feature(); - virtual void get_cityjson_info(nlohmann::json& b) const = 0; - virtual void get_cityjson_semantics(nlohmann::json& g) const = 0; - virtual std::string get_cityjson_primitive() const = 0; virtual TopoClass get_class() const = 0; virtual std::string get_class_name() const = 0; diff --git a/src/SurfaceLayer.cpp b/src/SurfaceLayer.cpp index e878e9e..9ce48c6 100644 --- a/src/SurfaceLayer.cpp +++ b/src/SurfaceLayer.cpp @@ -53,8 +53,6 @@ void SurfaceLayer::get_cityjson_info(nlohmann::json& b) const { b["type"] = "TINRelief"; } -void SurfaceLayer::get_cityjson_semantics(nlohmann::json& /*g*/) const { } - std::string SurfaceLayer::get_cityjson_primitive() const { return "CompositeSurface"; } diff --git a/src/SurfaceLayer.h b/src/SurfaceLayer.h index d6c14aa..d2ff98d 100644 --- a/src/SurfaceLayer.h +++ b/src/SurfaceLayer.h @@ -40,7 +40,6 @@ class SurfaceLayer : public PolyFeature { void check_feature_scope(const Polygon_2& bndPoly); virtual void get_cityjson_info(nlohmann::json& b) const override; - virtual void get_cityjson_semantics(nlohmann::json& g) const override; virtual std::string get_cityjson_primitive() const override; virtual TopoClass get_class() const override; virtual std::string get_class_name() const override; From 18fc500a7cd34e1b72b9f30618bad2afdaa7a469 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Wed, 5 Mar 2025 16:24:53 +0100 Subject: [PATCH 07/12] Store vertices as integers in cityjson --- src/io.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io.cpp b/src/io.cpp index 94344e4..b91db6c 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -458,7 +458,7 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { for (auto& p : thepts) { std::vector c; boost::split(c, p, boost::is_any_of(" ")); - j["vertices"].push_back({std::stod(c[0], NULL), std::stod(c[1], NULL), std::stod(c[2], NULL) }); + j["vertices"].push_back({std::stoi(c[0]), std::stoi(c[1]), std::stoi(c[2])}); } of.open(Config::get().outputFileName + ".city.json"); From 9ccddc57df28e65f61642cec2575088a792fe4f7 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Thu, 6 Mar 2025 10:32:11 +0100 Subject: [PATCH 08/12] Add lod specifier to all objects --- src/TopoFeature.cpp | 7 +++++++ src/TopoFeature.h | 6 ++++-- src/io.cpp | 4 +--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/TopoFeature.cpp b/src/TopoFeature.cpp index 0b0c6ef..b75c887 100644 --- a/src/TopoFeature.cpp +++ b/src/TopoFeature.cpp @@ -81,6 +81,13 @@ void TopoFeature::deactivate() { m_f_active = false; } +/* + * Default LoD specifier is 1.2 unless specified otherwise in the derived class + */ +std::string TopoFeature::get_lod() const { + return "1.2"; +} + void TopoFeature::get_cityjson_info(nlohmann::json& /*j*/) const {} void TopoFeature::get_cityjson_semantics(nlohmann::json& /*g*/) const {} diff --git a/src/TopoFeature.h b/src/TopoFeature.h index 819a2d9..d96c619 100644 --- a/src/TopoFeature.h +++ b/src/TopoFeature.h @@ -41,11 +41,13 @@ class TopoFeature { static void add_recon_region_output_layers(const int numLayers); static int get_num_output_layers(); + virtual TopoClass get_class() const = 0; + virtual std::string get_class_name() const = 0; + + virtual std::string get_lod() const; virtual void get_cityjson_info(nlohmann::json& b) const; virtual void get_cityjson_semantics(nlohmann::json& g) const; virtual std::string get_cityjson_primitive() const; - virtual TopoClass get_class() const = 0; - virtual std::string get_class_name() const = 0; Mesh& get_mesh(); const Mesh& get_mesh() const; diff --git a/src/io.cpp b/src/io.cpp index b91db6c..b4549a8 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -424,9 +424,7 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { //-- Get feature geometry nlohmann::json g; g["type"] = f->get_cityjson_primitive(); - // grab lod information for buildings - auto buildingDerived = std::dynamic_pointer_cast(f); - if (buildingDerived) g["lod"] = buildingDerived->get_lod(); + g["lod"] = f->get_lod(); IO::get_cityjson_geom(f->get_mesh(), g, dPts, minMaxZ); From 24ae825a3c35f86ec2e6a39cc6449baf959de349 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Thu, 6 Mar 2025 11:34:25 +0100 Subject: [PATCH 09/12] Refactor --- src/Building.cpp | 15 ++++++++------- src/Building.h | 4 ++-- src/ReconstructedBuilding.cpp | 10 ---------- src/ReconstructedBuilding.h | 1 - src/SurfaceLayer.cpp | 9 +++++---- src/SurfaceLayer.h | 4 ++-- src/Terrain.cpp | 9 +++++---- src/Terrain.h | 4 ++-- src/TopoFeature.cpp | 15 +++------------ src/TopoFeature.h | 5 ++--- src/io.cpp | 6 ++---- 11 files changed, 31 insertions(+), 51 deletions(-) diff --git a/src/Building.cpp b/src/Building.cpp index 564c07a..0b62a55 100644 --- a/src/Building.cpp +++ b/src/Building.cpp @@ -286,14 +286,19 @@ std::string Building::get_lod() const { return m_reconSettings->lod; } -void Building::get_cityjson_info(nlohmann::json& b) const { - b["type"] = "Building"; +void Building::get_cityjson_cityobj_info(nlohmann::json& f) const { + f["type"] = "Building"; // b["attributes"]; // get_cityjson_attributes(b, _attributes); // float hbase = z_to_float(this->get_height_base()); // float h = z_to_float(this->get_height()); // b["attributes"]["TerrainHeight"] = m_baseElevations.back(); // temp - will calculate avg for every footprint - b["attributes"]["measuredHeight"] = m_elevation - geomutils::avg(m_groundElevations[0]); + f["attributes"]["measuredHeight"] = m_elevation - geomutils::avg(m_groundElevations[0]); +} + +void Building::get_cityjson_geomobj_info(nlohmann::json& g) const { + g["type"] = "MultiSurface"; + g["lod"] = this->get_lod(); } void Building::get_cityjson_semantics(nlohmann::json& g) const { @@ -316,10 +321,6 @@ void Building::get_cityjson_semantics(nlohmann::json& g) const { } } -std::string Building::get_cityjson_primitive() const { - return "MultiSurface"; -} - TopoClass Building::get_class() const { return BUILDING; } diff --git a/src/Building.h b/src/Building.h index d4de847..4115116 100644 --- a/src/Building.h +++ b/src/Building.h @@ -67,9 +67,9 @@ class Building : public PolyFeature { PointSet3Ptr get_points() const; std::string get_lod() const; - virtual void get_cityjson_info(nlohmann::json& b) const override; + virtual void get_cityjson_cityobj_info(nlohmann::json& f) const override; + virtual void get_cityjson_geomobj_info(nlohmann::json& g) const override; virtual void get_cityjson_semantics(nlohmann::json& g) const override; - virtual std::string get_cityjson_primitive() const override; virtual TopoClass get_class() const override; virtual std::string get_class_name() const override; diff --git a/src/ReconstructedBuilding.cpp b/src/ReconstructedBuilding.cpp index 927e074..c0f705a 100644 --- a/src/ReconstructedBuilding.cpp +++ b/src/ReconstructedBuilding.cpp @@ -325,16 +325,6 @@ void ReconstructedBuilding::reconstruct_flat_terrain() { } } -void ReconstructedBuilding::get_cityjson_info(nlohmann::json& b) const { - b["type"] = "Building"; -// b["attributes"]; -// get_cityjson_attributes(b, _attributes); -// float hbase = z_to_float(this->get_height_base()); -// float h = z_to_float(this->get_height()); -// b["attributes"]["TerrainHeight"] = m_baseElevations.back(); // temp - will calculate avg for every footprint - b["attributes"]["measuredHeight"] = m_elevation - geomutils::avg(m_groundElevations[0]); -} - void ReconstructedBuilding::get_cityjson_semantics(nlohmann::json& g) const { // Temp for checking CGAL mesh properties // for now handle only LoD1.2 with semantics, LoD1.3 and LoD2.2 loses information // when making final repairs diff --git a/src/ReconstructedBuilding.h b/src/ReconstructedBuilding.h index 5598c6f..093c9c2 100644 --- a/src/ReconstructedBuilding.h +++ b/src/ReconstructedBuilding.h @@ -47,7 +47,6 @@ class ReconstructedBuilding : public Building { virtual void reconstruct() override; virtual void insert_terrain_point(const Point_3& pt) override; virtual void reconstruct_flat_terrain() override; - virtual void get_cityjson_info(nlohmann::json& b) const override; virtual void get_cityjson_semantics(nlohmann::json& g) const override; protected: diff --git a/src/SurfaceLayer.cpp b/src/SurfaceLayer.cpp index 9ce48c6..58ca63d 100644 --- a/src/SurfaceLayer.cpp +++ b/src/SurfaceLayer.cpp @@ -49,12 +49,13 @@ void SurfaceLayer::check_feature_scope(const Polygon_2& bndPoly) { } } -void SurfaceLayer::get_cityjson_info(nlohmann::json& b) const { - b["type"] = "TINRelief"; +void SurfaceLayer::get_cityjson_cityobj_info(nlohmann::json& f) const { + f["type"] = "TINRelief"; } -std::string SurfaceLayer::get_cityjson_primitive() const { - return "CompositeSurface"; +void SurfaceLayer::get_cityjson_geomobj_info(nlohmann::json& g) const { + g["type"] = "CompositeSurface"; + g["lod"] = "1.2"; } TopoClass SurfaceLayer::get_class() const { diff --git a/src/SurfaceLayer.h b/src/SurfaceLayer.h index d2ff98d..4d4d9cf 100644 --- a/src/SurfaceLayer.h +++ b/src/SurfaceLayer.h @@ -39,8 +39,8 @@ class SurfaceLayer : public PolyFeature { void check_feature_scope(const Polygon_2& bndPoly); - virtual void get_cityjson_info(nlohmann::json& b) const override; - virtual std::string get_cityjson_primitive() const override; + virtual void get_cityjson_cityobj_info(nlohmann::json& f) const override; + virtual void get_cityjson_geomobj_info(nlohmann::json& g) const override; virtual TopoClass get_class() const override; virtual std::string get_class_name() const override; diff --git a/src/Terrain.cpp b/src/Terrain.cpp index 2bfd1fa..4d3eb1c 100644 --- a/src/Terrain.cpp +++ b/src/Terrain.cpp @@ -316,13 +316,14 @@ void Terrain::check_layer(const Face_handle& fh, int surfaceLayer) { } */ -void Terrain::get_cityjson_info(nlohmann::json& b) const { - b["type"] = "TINRelief"; +void Terrain::get_cityjson_cityobj_info(nlohmann::json& f) const { + f["type"] = "TINRelief"; // b["attributes"]; // commented out until I have attributes to add } -std::string Terrain::get_cityjson_primitive() const { - return "CompositeSurface"; +void Terrain::get_cityjson_geomobj_info(nlohmann::json& g) const { + g["type"] = "CompositeSurface"; + g["lod"] = "1.2"; } CDT& Terrain::get_cdt() { diff --git a/src/Terrain.h b/src/Terrain.h index ef68d55..238c380 100644 --- a/src/Terrain.h +++ b/src/Terrain.h @@ -58,8 +58,8 @@ class Terrain : public TopoFeature { const SearchTree& get_mesh_search_tree() const; std::vector& get_extra_constrained_edges(); - void get_cityjson_info(nlohmann::json& b) const override; - std::string get_cityjson_primitive() const override; + void get_cityjson_geomobj_info(nlohmann::json& g) const override; + void get_cityjson_cityobj_info(nlohmann::json& f) const override; TopoClass get_class() const override; std::string get_class_name() const override; diff --git a/src/TopoFeature.cpp b/src/TopoFeature.cpp index b75c887..88c5ee0 100644 --- a/src/TopoFeature.cpp +++ b/src/TopoFeature.cpp @@ -81,17 +81,8 @@ void TopoFeature::deactivate() { m_f_active = false; } -/* - * Default LoD specifier is 1.2 unless specified otherwise in the derived class - */ -std::string TopoFeature::get_lod() const { - return "1.2"; -} - -void TopoFeature::get_cityjson_info(nlohmann::json& /*j*/) const {} +void TopoFeature::get_cityjson_cityobj_info(nlohmann::json& /* f */) const { } -void TopoFeature::get_cityjson_semantics(nlohmann::json& /*g*/) const {} +void TopoFeature::get_cityjson_geomobj_info(nlohmann::json& /* g */) const { } -std::string TopoFeature::get_cityjson_primitive() const { - return ""; -} \ No newline at end of file +void TopoFeature::get_cityjson_semantics(nlohmann::json& /* g */) const { } \ No newline at end of file diff --git a/src/TopoFeature.h b/src/TopoFeature.h index d96c619..9fa7f72 100644 --- a/src/TopoFeature.h +++ b/src/TopoFeature.h @@ -44,10 +44,9 @@ class TopoFeature { virtual TopoClass get_class() const = 0; virtual std::string get_class_name() const = 0; - virtual std::string get_lod() const; - virtual void get_cityjson_info(nlohmann::json& b) const; + virtual void get_cityjson_cityobj_info(nlohmann::json& f) const; + virtual void get_cityjson_geomobj_info(nlohmann::json& g) const; virtual void get_cityjson_semantics(nlohmann::json& g) const; - virtual std::string get_cityjson_primitive() const; Mesh& get_mesh(); const Mesh& get_mesh() const; diff --git a/src/io.cpp b/src/io.cpp index b4549a8..d16d18c 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -419,12 +419,11 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { f->get_class() != SURFACELAYER) continue; //-- Get feature info nlohmann::json b; - f->get_cityjson_info(b); + f->get_cityjson_cityobj_info(b); //-- Get feature geometry nlohmann::json g; - g["type"] = f->get_cityjson_primitive(); - g["lod"] = f->get_lod(); + f->get_cityjson_geomobj_info(g); IO::get_cityjson_geom(f->get_mesh(), g, dPts, minMaxZ); @@ -524,7 +523,6 @@ void IO::get_cityjson_geom(const Mesh& mesh, std::string pt = gen_key_bucket_int(mesh.point(index), inverseScale, inverseTranslate); auto it = dPts.find(pt); if (it == dPts.end()) { - tempPoly.push_back(dPts.size()); dPts[pt] = dPts.size(); From 0a4ec8ed3fdb90a18de219575c63e03d20dc45f2 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Thu, 6 Mar 2025 12:12:51 +0100 Subject: [PATCH 10/12] More robust floating point handling --- src/io.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io.cpp b/src/io.cpp index d16d18c..5c8b26d 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -651,10 +651,10 @@ template std::string IO::gen_key_bucket(const Vector_3& p); template std::string IO::gen_key_bucket_int(const T& p, const double inverseScale, const double inverseTranslate) { std::stringstream ss; - ss << std::fixed << std::setprecision(0) - << (p.x() + inverseTranslate) * inverseScale << " " - << (p.y() + inverseTranslate) * inverseScale << " " - << (p.z() + inverseTranslate) * inverseScale; + // round to integer before converting to string + ss << static_cast(std::round((p.x() + inverseTranslate) * inverseScale)) << " " + << static_cast(std::round((p.y() + inverseTranslate) * inverseScale)) << " " + << static_cast(std::round((p.z() + inverseTranslate) * inverseScale)); return ss.str(); } //- Explicit template instantiation From 9b2ca94ba9847fa942d28368f28cfb7ee759df0c Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Fri, 7 Mar 2025 14:48:38 +0100 Subject: [PATCH 11/12] Handle duplicate ids --- src/Building.cpp | 8 ++++---- src/ReconstructedBuilding.cpp | 11 ++++------- src/io.cpp | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Building.cpp b/src/Building.cpp index 0b62a55..eb200e8 100644 --- a/src/Building.cpp +++ b/src/Building.cpp @@ -309,15 +309,15 @@ void Building::get_cityjson_semantics(nlohmann::json& g) const { } else throw city4cfd_error("Semantic property map not found!"); std::unordered_map surfaceId; - surfaceId["RoofSurface"] = 0; g["semantics"]["surfaces"][0]["type"] = "RoofSurface"; - surfaceId["GroundSurface"] = 1; g["semantics"]["surfaces"][1]["type"] = "GroundSurface"; - surfaceId["WallSurface"] = 2; g["semantics"]["surfaces"][2]["type"] = "WallSurface"; + surfaceId["RoofSurface"] = 0; g["surfaces"][0]["type"] = "RoofSurface"; + surfaceId["GroundSurface"] = 1; g["surfaces"][1]["type"] = "GroundSurface"; + surfaceId["WallSurface"] = 2; g["surfaces"][2]["type"] = "WallSurface"; for (auto faceIdx : m_mesh.faces()) { auto it = surfaceId.find(semantics[faceIdx]); if (it == surfaceId.end()) throw city4cfd_error("Could not find semantic attribute!"); - g["semantics"]["values"][faceIdx.idx()] = it->second; + g["values"][faceIdx.idx()] = it->second; } } diff --git a/src/ReconstructedBuilding.cpp b/src/ReconstructedBuilding.cpp index c0f705a..709a014 100644 --- a/src/ReconstructedBuilding.cpp +++ b/src/ReconstructedBuilding.cpp @@ -336,18 +336,15 @@ void ReconstructedBuilding::get_cityjson_semantics(nlohmann::json& g) const { // } else throw city4cfd_error("Semantic property map not found!"); std::unordered_map surfaceId; - surfaceId["RoofSurface"] = 0; - g["semantics"]["surfaces"][0]["type"] = "RoofSurface"; - surfaceId["GroundSurface"] = 1; - g["semantics"]["surfaces"][1]["type"] = "GroundSurface"; - surfaceId["WallSurface"] = 2; - g["semantics"]["surfaces"][2]["type"] = "WallSurface"; + surfaceId["RoofSurface"] = 0; g["surfaces"][0]["type"] = "RoofSurface"; + surfaceId["GroundSurface"] = 1; g["surfaces"][1]["type"] = "GroundSurface"; + surfaceId["WallSurface"] = 2; g["surfaces"][2]["type"] = "WallSurface"; for (auto faceIdx: m_mesh.faces()) { auto it = surfaceId.find(semantics[faceIdx]); if (it == surfaceId.end()) throw city4cfd_error("Could not find semantic attribute!"); - g["semantics"]["values"][faceIdx.idx()] = it->second; + g["values"][faceIdx.idx()] = it->second; } } } diff --git a/src/io.cpp b/src/io.cpp index 5c8b26d..bc36fe1 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -428,17 +428,28 @@ void IO::output_cityjson(const OutputFeaturesPtr& allFeatures) { IO::get_cityjson_geom(f->get_mesh(), g, dPts, minMaxZ); //-- Get feature semantics - f->get_cityjson_semantics(g); + nlohmann::json s; + f->get_cityjson_semantics(s); + if (!s.empty()) g["semantics"] = s; //-- Append to main json struct b["geometry"].push_back(g); - // store terran and surface layers with the name, rest with the id + //-- Store terran and surface layers with the name, rest with the id if (f->get_class() == TERRAIN || f->get_class() == SURFACELAYER) { j["CityObjects"][Config::get().outputSurfaces[f->get_output_layer_id()]] = b; } else { - j["CityObjects"][f->get_id()] = b; + std::string uniqueID = f->get_id(); + int suffix = 0; + // check if the ID already exists and ensure unique ID + while (!j["CityObjects"][uniqueID].empty()) { + ++suffix; + uniqueID = f->get_id() + "_" + std::to_string(suffix); + } + j["CityObjects"][uniqueID] = b; + if (suffix > 0) + Config::write_to_log("Building ID: " + f->get_id() + " already exists. Stored as a new ID: " + uniqueID); } } auto bbox = Boundary::get_outer_bnd_bbox(); From 8092097954a35c67fbaf72ccb74097914a777292 Mon Sep 17 00:00:00 2001 From: Ivan Paden Date: Fri, 7 Mar 2025 15:12:12 +0100 Subject: [PATCH 12/12] Update changelog --- CHANGELOG.md | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc24630..78dcf5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## [Unreleased] +## [0.6.3] 2025-03-07 ### Fixed - CityJSON terrain output diff --git a/src/main.cpp b/src/main.cpp index 3853b7e..90550bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,7 @@ #include -std::string CITY4CFD_VERSION = "0.6.2+dev"; +std::string CITY4CFD_VERSION = "0.6.3"; void printWelcome() { auto logo{