diff --git a/apps/roofer-app/config.hpp b/apps/roofer-app/config.hpp index 7550ae72..93107f2b 100644 --- a/apps/roofer-app/config.hpp +++ b/apps/roofer-app/config.hpp @@ -97,6 +97,9 @@ struct RooferConfig { // crop output bool split_cjseq = false; + bool omit_metadata = false; + std::optional cj_scale; + std::optional cj_translate; std::string building_toml_file_spec = "{path}/objects/{bid}/config_{pc_name}.toml"; std::string building_las_file_spec = @@ -415,6 +418,15 @@ class ConfigParameterByReference : public ConfigParameter { return fmt::format("[{},{},{},{}]", _value->pmin[0], _value->pmin[1], _value->pmax[0], _value->pmax[1]); } + } else if constexpr (std::is_same_v) { + return fmt::format("[{},{}]", _value[0], _value[1]); + } else if constexpr (std::is_same_v>) { + if (!_value.has_value()) { + return "[]"; + } else { + return fmt::format("[{},{},{}]", (*_value)[0], (*_value)[1], + (*_value)[2]); + } } else { return fmt::format("{}", _value); } @@ -423,11 +435,11 @@ class ConfigParameterByReference : public ConfigParameter { std::list::iterator set( std::list& args, std::list::iterator it) override { - if (it == args.end()) { - throw std::runtime_error("Missing argument for parameter."); - } else if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { _value = !(_value); return it; + } else if (it == args.end()) { + throw std::runtime_error("Missing argument for parameter"); } else if constexpr (std::is_same_v || std::is_same_v>) { _value = std::stoi(*it); @@ -475,6 +487,20 @@ class ConfigParameterByReference : public ConfigParameter { it = args.erase(it); _value = arr; return it; + } else if constexpr (std::is_same_v>) { + roofer::arr3d arr; + // Check if there are enough arguments + if (std::distance(it, args.end()) < 3) { + throw std::runtime_error("Not enough arguments, need 3."); + } + arr[0] = std::stod(*it); + it = args.erase(it); + arr[1] = std::stod(*it); + it = args.erase(it); + arr[2] = std::stod(*it); + it = args.erase(it); + _value = arr; + return it; } else { static_assert(!std::is_same_v, "Unsupported type for ConfigParameterByReference::set()"); @@ -484,8 +510,31 @@ class ConfigParameterByReference : public ConfigParameter { void set_from_toml(const toml::table& table, const std::string& name) override { if constexpr (std::is_same_v>) { - throw std::runtime_error("Failed to read value for " + name + - " from config file."); + if (const toml::array* a = table[name].as_array()) { + if (a->size() == 2 && + (a->is_homogeneous(toml::node_type::floating_point) || + a->is_homogeneous(toml::node_type::integer))) { + _value = roofer::arr2f{*a->get(0)->value(), + *a->get(1)->value()}; + } else { + throw std::runtime_error("Failed to read value for " + name + + " from config file."); + } + } + } else if constexpr (std::is_same_v>>) { + if (const toml::array* a = table[name].as_array()) { + if (a->size() == 3 && + (a->is_homogeneous(toml::node_type::floating_point) || + a->is_homogeneous(toml::node_type::integer))) { + _value = roofer::arr3d{*a->get(0)->value(), + *a->get(1)->value(), + *a->get(2)->value()}; + } else { + throw std::runtime_error("Failed to read value for " + name + + " from config file."); + } + } } else if constexpr (std::is_same_v>>) { if (const toml::array* a = table[name].as_array()) { @@ -523,6 +572,8 @@ class ConfigParameterByReference : public ConfigParameter { return ""; } else if constexpr (std::is_same_v) { return ""; + } else if constexpr (std::is_same_v>) { + return ""; } else { static_assert(!std::is_same_v, "Unsupported type for " @@ -623,6 +674,12 @@ struct RooferConfigHandler { "Output CityJSONSequence file for each building [default: one file per " "output tile]", _cfg.split_cjseq, {}); + add("omit-metadata", "Omit metadata in CityJSON output", _cfg.omit_metadata, + {}); + add("cj-scale", "Scaling applied to CityJSON output vertices", + _cfg.cj_scale, {}); + add("cj-translate", "Translation applied to CityJSON output vertices", + _cfg.cj_translate, {}); addr("plane-detect-k", "plane detect k", _cfg.rec.plane_detect_k, {roofer::v::HigherThan(0)}); addr("plane-detect-min-points", "plane detect min points", diff --git a/apps/roofer-app/example_full.toml b/apps/roofer-app/example_full.toml index 1ecc3189..e475c0f6 100644 --- a/apps/roofer-app/example_full.toml +++ b/apps/roofer-app/example_full.toml @@ -38,6 +38,12 @@ lod = 22 ## Output options # Output CityJSONSequence file for each building [default: one file per output tile] split-cjseq = false +# Omit metadata from output CityJSON +omit-metadata = true +# Manually override CityJSON transform translation +cj-translate = [171800.0,472700.0,0.0] +# Manually override CityJSON transform scale +cj-scale = [0.01,0.01,0.01] output-directory = 'output-directory' diff --git a/apps/roofer-app/roofer-app.cpp b/apps/roofer-app/roofer-app.cpp index 93c0c2b3..bf2356d9 100644 --- a/apps/roofer-app/roofer-app.cpp +++ b/apps/roofer-app/roofer-app.cpp @@ -998,7 +998,13 @@ int main(int argc, const char* argv[]) { roofer::io::createCityJsonWriter(*building_tile.proj_helper); CityJsonWriter->written_features_count = serialized_buildings_cnt; CityJsonWriter->identifier_attribute = roofer_cfg.id_attribute; - if (building_tile.proj_helper->data_offset.has_value()) { + // user provided offset + if (roofer_cfg.cj_translate.has_value()) { + CityJsonWriter->translate_x_ = (*roofer_cfg.cj_translate)[0]; + CityJsonWriter->translate_y_ = (*roofer_cfg.cj_translate)[1]; + CityJsonWriter->translate_z_ = (*roofer_cfg.cj_translate)[2]; + // auto offset from data + } else if (building_tile.proj_helper->data_offset.has_value()) { CityJsonWriter->translate_x_ = (*building_tile.proj_helper->data_offset)[0]; CityJsonWriter->translate_y_ = @@ -1010,9 +1016,15 @@ int main(int argc, const char* argv[]) { "Tile {} has no data offset, cannot write to cityjson", building_tile.id)); } - CityJsonWriter->scale_x_ = 0.01; - CityJsonWriter->scale_y_ = 0.01; - CityJsonWriter->scale_z_ = 0.01; + if (roofer_cfg.cj_scale.has_value()) { + CityJsonWriter->scale_x_ = (*roofer_cfg.cj_scale)[0]; + CityJsonWriter->scale_y_ = (*roofer_cfg.cj_scale)[1]; + CityJsonWriter->scale_z_ = (*roofer_cfg.cj_scale)[2]; + } else { + CityJsonWriter->scale_x_ = 0.01; + CityJsonWriter->scale_y_ = 0.01; + CityJsonWriter->scale_z_ = 0.01; + } std::ofstream ofs; if (!roofer_cfg.split_cjseq) { @@ -1021,19 +1033,23 @@ int main(int argc, const char* argv[]) { fmt::format("tile_{:05d}.city.jsonl", building_tile.id); fs::create_directories(jsonl_tile_path.parent_path()); ofs.open(jsonl_tile_path); - CityJsonWriter->write_metadata( - ofs, project_srs.get(), building_tile.extent, - {.identifier = std::to_string(building_tile.id)}); + if (!roofer_cfg.omit_metadata) + CityJsonWriter->write_metadata( + ofs, project_srs.get(), building_tile.extent, + {.identifier = std::to_string(building_tile.id)}); } else { - std::string metadata_json_file = - fmt::format(fmt::runtime(roofer_cfg.metadata_json_file_spec), - fmt::arg("path", roofer_cfg.output_path)); - fs::create_directories(fs::path(metadata_json_file).parent_path()); - ofs.open(metadata_json_file); - CityJsonWriter->write_metadata( - ofs, project_srs.get(), building_tile.extent, - {.identifier = std::to_string(building_tile.id)}); - ofs.close(); + if (!roofer_cfg.omit_metadata) { + std::string metadata_json_file = + fmt::format(fmt::runtime(roofer_cfg.metadata_json_file_spec), + fmt::arg("path", roofer_cfg.output_path)); + fs::create_directories( + fs::path(metadata_json_file).parent_path()); + ofs.open(metadata_json_file); + CityJsonWriter->write_metadata( + ofs, project_srs.get(), building_tile.extent, + {.identifier = std::to_string(building_tile.id)}); + ofs.close(); + } } for (auto& building : building_tile.buildings) { diff --git a/docs/cli_application.rst b/docs/cli_application.rst index 0cb4c1c1..00ed50b5 100644 --- a/docs/cli_application.rst +++ b/docs/cli_application.rst @@ -80,6 +80,18 @@ Options Output CityJSONSequence file for each building [default: one file per output tile] +.. option:: --cj-translate + + Specify manually the translation applied to CityJSON output vertices + +.. option:: --cj-scale + + Specify manually the scaling applied to CityJSON output vertices + +.. option:: --omit-metadata + + Omit metadata from output CityJSON + .. option:: --filter Specify WHERE clause in OGR SQL to select specfic features from diff --git a/include/roofer/io/CityJsonWriter.hpp b/include/roofer/io/CityJsonWriter.hpp index 12463d84..4721455a 100644 --- a/include/roofer/io/CityJsonWriter.hpp +++ b/include/roofer/io/CityJsonWriter.hpp @@ -52,12 +52,12 @@ namespace roofer::io { vec1s key_options; - float translate_x_ = 0.; - float translate_y_ = 0.; - float translate_z_ = 0.; - float scale_x_ = 1.; - float scale_y_ = 1.; - float scale_z_ = 1.; + double translate_x_ = 0.; + double translate_y_ = 0.; + double translate_z_ = 0.; + double scale_x_ = 0.01; + double scale_y_ = 0.01; + double scale_z_ = 0.01; roofer::misc::projHelperInterface& pjHelper; diff --git a/src/extra/io/CityJsonWriter.cpp b/src/extra/io/CityJsonWriter.cpp index 347cf194..1bb274d4 100644 --- a/src/extra/io/CityJsonWriter.cpp +++ b/src/extra/io/CityJsonWriter.cpp @@ -431,13 +431,10 @@ namespace roofer::io { }; std::vector> vertices_int; - double _offset_x = translate_x_; - double _offset_y = translate_y_; - double _offset_z = translate_z_; for (auto& vertex : vertex_vec) { - vertices_int.push_back({int((vertex[0] - _offset_x) / scale_x_), - int((vertex[1] - _offset_y) / scale_y_), - int((vertex[2] - _offset_z) / scale_z_)}); + vertices_int.push_back({int((vertex[0] - translate_x_) / scale_x_), + int((vertex[1] - translate_y_) / scale_y_), + int((vertex[2] - translate_z_) / scale_z_)}); } outputJSON["vertices"] = vertices_int;