From 10ef0fa3d3c1bcb3be6556e43504a69a01992dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gelencs=C3=A9r=20Gerg=C5=91?= Date: Fri, 25 Feb 2022 20:54:44 +0100 Subject: [PATCH] CSV and JSON Writer, stdout --- .gitignore | 1 + .vscode/keybindings.json | 20 --- .vscode/launch.json | 67 -------- .vscode/settings.json | 26 ---- README.md | 15 +- include/CsvWriter.h | 241 +++++++++++++++++------------ include/JsonWriter.h | 216 ++++++++++++++++++++++++++ include/LasWriter.h | 10 +- libs/brotli/CMakeLists.txt | 4 - src/executable_extract_area.cpp | 150 ++++++++++++------ src/executable_extract_profile.cpp | 68 +++----- 11 files changed, 504 insertions(+), 314 deletions(-) delete mode 100644 .vscode/keybindings.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json create mode 100644 include/JsonWriter.h diff --git a/.gitignore b/.gitignore index dab25ca..7c36389 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build/ build/CPotree.sln +.vscode/ \ No newline at end of file diff --git a/.vscode/keybindings.json b/.vscode/keybindings.json deleted file mode 100644 index 459628a..0000000 --- a/.vscode/keybindings.json +++ /dev/null @@ -1,20 +0,0 @@ -// Place your key bindings in this file to overwrite the defaults -[ - { - "key": "ctrl+l", - "command": "editor.action.deleteLines", - "when": "editorTextFocus && !editorReadonly" - },{ - "key": "ctrl+shift+i", - "command": "editor.action.toggleRenderWhitespace" - },{ - "key": "ctrl+d", - "command": "editor.action.copyLinesDownAction" - },{ - "key": "alt+2", - "command": "type", - "args": { - "text": "`" - } - } -] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 07c2f76..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - // - // "D:/temp/cpotree/eclepens_2.0.2" -o "D:/temp/cpotree/cpotree_2.0.potree" --coordinates "{-265.573, -190.931}, {127.204, 182.649}, {244.797, -373.526}" --width 5 --min-level 0 --max-level 5 - // "D:/temp/cpotree/eclepens_2.0.2" -o "D:/temp/cpotree/cpotree_2.0.potree" --area "matrix(876.5565085281047, 520.4709535432061, 0, 0, -3.0347254435531394, 5.110964062515199, 0, 0, 0, 0, 394.6308697620615, 0, -11.199203993909487, 75.63904922819503, 71.4624555159539, 1) matrix(6.78446909827727, 5.896560383403058, 0, 0, -721.9792295699667, 830.6954315947436, 0, 0, 0, 0, 226.56029219339297, 0, -154.338764178767, -38.73286531197823, -0.7124356507727185, 1)" --output-attributes rgb --min-level 0 --max-level 8 - // - // "D:/temp/cpotree/sitn_dip_2.0" -o "D:/temp/cpotree/cpotree_2.0.potree" --coordinates "{2524740.130, 1197228.920, 1241.990}, {2524224.210, 1197350.780, 1213.580}, {2524150.880, 1197871.160, 1120.780}, {2524831.190, 1197878.490, 1135.780}" --width 5 --min-level 0 --max-level 5 - // - "version": "0.2.0", - "configurations": [ - { - "name": "(Windows) Launch", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/build/Release/extract_profile.exe", - //"program": "${workspaceFolder}/build/Release/CPotree.exe", - "args": [ - - // "D:/temp/cpotree/eclepens_2.0.0", - "D:/temp/cpotree/sitn_dip_2.0", - - - - // "-o", "stdout", - "-o", "D:/temp/cpotree/cpotree_2.0.potree", - // "-o", "D:/temp/cpotree/cpotree_2.0.laz", - - - //---------------------------------------------------- - // extract_profile - //---------------------------------------------------- - - //"--coordinates", "{-265.573, -190.931}, {127.204, 182.649}, {244.797, -373.526}", - //"--width", "1.0", - - "--coordinates", "{2524740.130, 1197228.920, 1241.990}, {2524224.210, 1197350.780, 1213.580}, {2524150.880, 1197871.160, 1120.780}, {2524831.190, 1197878.490, 1135.780}", - "--width", "1.0", - - //---------------------------------------------------- - // cpotree - //---------------------------------------------------- - - //"--area", - // eclepens - //"matrix(876.5565085281047, 520.4709535432061, 0, 0, -3.0347254435531394, 5.110964062515199, 0, 0, 0, 0, 394.6308697620615, 0, -11.199203993909487, 75.63904922819503, 71.4624555159539, 1) matrix(6.78446909827727, 5.896560383403058, 0, 0, -721.9792295699667, 830.6954315947436, 0, 0, 0, 0, 226.56029219339297, 0, -154.338764178767, -38.73286531197823, -0.7124356507727185, 1)", - //"profile(2.0123, [-265.573, -190.931], [127.204, 182.649], [244.797, -373.526])", - - //---------------------------------------------------- - // common - //---------------------------------------------------- - - // "--output-attributes", "rgb", "intensity", - "--min-level", "0", - "--max-level", "2", - - // "--get-candidates", - - ], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1affeb8..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "workbench.editor.enablePreview": false, - "files.associations": { - "*.vs": "cpp", - "*.fs": "cpp", - "functional": "cpp", - "vector": "cpp", - "unordered_map": "cpp", - "xstring": "cpp" - }, - "files.trimTrailingWhitespace": false, - "editor.fontSize": 28, - "editor.autoIndent": false, - "editor.detectIndentation": false, - "editor.insertSpaces": false, - "editor.minimap.enabled": false, - "editor.autoClosingBrackets": false, - "editor.formatOnType": false, - "editor.acceptSuggestionOnEnter": "off", - "editor.acceptSuggestionOnCommitCharacter": false, - "editor.mouseWheelZoom": true, - "editor.renderWhitespace": "all", - "workbench.tree.indent": 30, - "cmake.configureOnOpen": true, - "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", -} \ No newline at end of file diff --git a/README.md b/README.md index 3e23e9e..99258ea 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,23 @@ cmake ../ # Usage -Extract points with the elevation profile: +Minimal - // minimal ./extract_profile -o --coordinates "{x0, y1}, {x1, y}, ..." --width + ./extract_area -o --area "{tx,rz,ry,0,-rz,ty,rx,0,-ry,-rx,tz,0,dx,dy,dz,1}" + +Extract points in certain LOD level ranges - // Extract points in certain LOD level ranges ./extract_profile -o --coordinates "{x0, y1}, {x1, y}, ..." --width --min-level --max-level +Extract points with different attributes + + ./extract_profile -o --coordinates "{x0, y1}, {x1, y}, ..." --width --output-attributes --output-format + * __input__: A point cloud generated with PotreeConverter 2. -* __output__: Can be files ending with *.las, *.laz, *.potree or it can be "stdout". If stdout is specified, a potree format file will be printed directly to the console. +* __output__: Can be files ending with *.las, *.laz, *.csv, *.json, *.potree. If omitted, the result will be printed directly to the console. +* __format__: LAS, LAZ, CSV, JSON, POTREE (default). If omitted, the files endings will be used. +* __attributes__: Point attributes like: position, rgb, intensity, classification, ... Default: same as input point cloud * __min-level__, __max-level__: Level range including the min and max levels. Can be omitted to process all levels. diff --git a/include/CsvWriter.h b/include/CsvWriter.h index 395013a..5f78d6d 100644 --- a/include/CsvWriter.h +++ b/include/CsvWriter.h @@ -31,79 +31,7 @@ using std::function; using std::shared_ptr; using std::mutex; using std::lock_guard; -using std::ofstream; - - -vector> createAttributeHandlers(shared_ptr stream, Attributes& attributes, Attributes outputAttributes) { - - vector> handlers; - - { // STANDARD LAS ATTRIBUTES - - unordered_map> mapping; - - { // POSITION - int offset = attributes.getOffset("position"); - auto handler = [stream, offset, attributes](int64_t index, uint8_t* data) { - - int32_t X, Y, Z; - - memcpy(&X, data + index * attributes.bytes + offset + 0, 4); - memcpy(&Y, data + index * attributes.bytes + offset + 4, 4); - memcpy(&Z, data + index * attributes.bytes + offset + 8, 4); - - double x = double(X) * attributes.posScale.x + attributes.posOffset.x; - double y = double(Y) * attributes.posScale.y + attributes.posOffset.y; - double z = double(Z) * attributes.posScale.z + attributes.posOffset.z; - - *stream << x << " " << y << " " << z; - }; - - mapping["position"] = handler; - } - - { // RGB - int offset = attributes.getOffset("rgb"); - auto handler = [stream, offset, attributes](int64_t index, uint8_t* data) { - uint16_t rgb[3]; - - memcpy(&rgb, data + index * attributes.bytes + offset, 6); - - *stream << " " << rgb[0] << " " << rgb[1] << " " << rgb[2]; - }; - - mapping["rgb"] = handler; - } - - { // INTENSITY - int offset = attributes.getOffset("intensity"); - auto handler = [stream, offset, attributes](int64_t index, uint8_t* data) { - uint16_t intensity; - memcpy(&intensity, data + index * attributes.bytes + offset, 2); - - *stream << " " << intensity; - }; - - mapping["intensity"] = handler; - } - - - *stream << "#"; - for (auto& attribute : outputAttributes.list) { - - if (mapping.find(attribute.name) != mapping.end()) { - *stream << attribute.name << " "; - handlers.push_back(mapping[attribute.name]); - } - } - *stream << endl; - - } - - return handlers; -} - - +using std::ostream; struct CsvWriter : public Writer { @@ -114,22 +42,59 @@ struct CsvWriter : public Writer { //AABB aabb; + string delim; + string value_delim; + mutex mtx_write; - shared_ptr stream; + shared_ptr stream; - CsvWriter(string path, Attributes outputAttributes) { + CsvWriter(string path, Attributes outputAttributes, string delim = ",", string value_delim = ",") { this->path = path; this->outputAttributes = outputAttributes; + this->delim = delim; + this->value_delim = value_delim; - stream = make_shared(); - - stream->open(path); + if (path == "stdout") { + stream = std::shared_ptr(&cout, [](void*) {}); + } + else { + stream = make_shared(path); + } - auto digits = std::numeric_limits::max_digits10; + auto digits = 7; // std::numeric_limits::max_digits10; *stream << std::setprecision(digits); stream->setf(ios::fixed); + + // create header + for (int i = 0; i < outputAttributes.list.size(); i++) { + auto attribute = outputAttributes.list[i]; + + if (value_delim != delim) { + *stream << "\"" << attribute.name << "\""; + } + else { // extract attribute values as columns + if (attribute.name == "position") { + *stream << "x" << value_delim << "y" << value_delim << "z"; + } + else if (attribute.name == "position_projected_profile") { + *stream << "u" << value_delim << "v"; + } + else if (attribute.name == "rgb") { + *stream << "r" << value_delim << "g" << value_delim << "b"; + } + else { + for (int i = 0; i < attribute.numElements; i++) { + *stream << "\"" << attribute.name << (attribute.numElements > 1 ? " " + (i + 1) : "") << "\""; + } + } + } + + if (i < outputAttributes.list.size() - 1) { + *stream << delim; + } + } } void write(Node* node, shared_ptr points, int64_t numAccepted, int64_t numRejected) { @@ -137,33 +102,111 @@ struct CsvWriter : public Writer { lock_guard lock(mtx_write); auto inputAttributes = points->attributes; - auto handlers = createAttributeHandlers(stream, inputAttributes, outputAttributes); int64_t numPoints = points->numPoints; + for (int64_t i = 0; i < numPoints; i++) { + *stream << endl; // start new line + + bool first = true; + + for (auto& attribute : outputAttributes.list) { + if (!first) { + *stream << delim; // attribute delimiter + } + + if (value_delim != delim && attribute.numElements > 1) { + *stream << "\""; + } + + if (attribute.name == "position") { + dvec3 xyz = points->getPosition(i); + *stream << xyz.x << value_delim << xyz.y << value_delim << xyz.z; + } + else if (attribute.name == "position_projected_profile") { + auto i32 = points->attributeBuffersMap[attribute.name]->data_i32; + dvec2 xy = glm::dvec2( + i32[2 * i + 0] * points->attributes.posScale.x, // X -> projected on X-Y plane + i32[2 * i + 1] * points->attributes.posScale.z // Y -> Z + ); + *stream << xy.x << value_delim << xy.y; + } + else { + + for (int j = 0; j < attribute.numElements; j++) { + + if (attribute.type == AttributeType::INT8) { + auto i8 = points->attributeBuffersMap[attribute.name]->data_i8; + int8_t value = i8[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT16) { + auto i16 = points->attributeBuffersMap[attribute.name]->data_i16; + int16_t value = i16[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT32) { + auto i32 = points->attributeBuffersMap[attribute.name]->data_i32; + int32_t value = i32[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT64) { + auto i64 = points->attributeBuffersMap[attribute.name]->data_i64; + int64_t value = i64[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::UINT8) { + auto u8 = points->attributeBuffersMap[attribute.name]->data_u8; + uint8_t value = u8[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT16) { + auto u16 = points->attributeBuffersMap[attribute.name]->data_u16; + uint16_t value = u16[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT32) { + auto u32 = points->attributeBuffersMap[attribute.name]->data_u32; + uint32_t value = u32[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT64) { + auto u64 = points->attributeBuffersMap[attribute.name]->data_u64; + uint64_t value = u64[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::FLOAT) { + auto f32 = points->attributeBuffersMap[attribute.name]->data_f32; + float value = f32[attribute.numElements * i + j]; + *stream << value; + } + else if (attribute.type == AttributeType::DOUBLE) { + auto f64 = points->attributeBuffersMap[attribute.name]->data_f64; + double value = f64[attribute.numElements * i + j]; + *stream << value; + } + else { + *stream << ""; + } + + if (j < attribute.numElements - 1) { + *stream << value_delim; // multi attribute delimiter + } + } + } + + if (value_delim != delim && attribute.numElements > 1) { + *stream << "\""; + } + + first = false; + } - cout << "TODO" << endl; - exit(123); - - //int posOffset = inputAttributes.getOffset("position"); - - //dvec3 scale = inputAttributes.posScale; - //dvec3 offset = inputAttributes.posOffset; - - //for (int64_t i = 0; i < numPoints; i++) { - - // for (auto& handler : handlers) { - // handler(i, data->data_u8); - // } - - // if (i < numPoints - 1) { - // *stream << endl; - // } - //} + } } void close() { - stream->close(); + //stream->close(); stream = nullptr; } diff --git a/include/JsonWriter.h b/include/JsonWriter.h new file mode 100644 index 0000000..7806b59 --- /dev/null +++ b/include/JsonWriter.h @@ -0,0 +1,216 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "laszip/laszip_api.h" + +#include +#include +#include +#include + +#include "unsuck/unsuck.hpp" +#include "Attributes.h" +#include "Node.h" + +#include "Writer.h" + +using glm::dvec2; +using glm::dvec3; +using glm::dvec4; +using glm::dmat4; + +using std::vector; +using std::function; +using std::shared_ptr; +using std::mutex; +using std::lock_guard; +using std::ostream; + +struct JsonWriter : public Writer { + + string path; + Attributes outputAttributes; + + int64_t numWrittenPoints = 0; + + bool geojson; + int projection; + + //AABB aabb; + + mutex mtx_write; + + shared_ptr stream; + + JsonWriter(string path, Attributes outputAttributes, bool geojson = false, int projection = 4326) { + this->path = path; + this->outputAttributes = outputAttributes; + this->geojson = geojson; + this->projection = projection; + + if (path == "stdout") { + stream = std::shared_ptr(&cout, [](void*) {}); + } + else { + stream = make_shared(path); + } + + auto digits = 7; // std::numeric_limits::max_digits10; + + *stream << std::setprecision(digits); + stream->setf(ios::fixed); + + if (geojson) { + *stream << "{ \"type\": \"FeatureCollection\", \"crs\": { \"type\": \"name\", \"properties\": { \"name\": \"EPSG:" << projection << "\" } }, \"features\": "; + } + + *stream << "[" << endl; + } + + void write(Node* node, shared_ptr points, int64_t numAccepted, int64_t numRejected) { + + lock_guard lock(mtx_write); + + auto inputAttributes = points->attributes; + + int64_t numPoints = points->numPoints; + for (int64_t i = 0; i < numPoints; i++) { + + *stream << "{ "; + bool first = true; + + if (geojson) { + dvec3 xyz = points->getPosition(i); + *stream << "\"type\": \"Feature\", \"geometry\": { \"type\": \"Point\", \"coordinates\": ["; + *stream << xyz.x << ", " << xyz.y << ", " << xyz.z; + *stream << "] }, \"properties\": { "; + } + + for (auto& attribute : outputAttributes.list) { + if (geojson && attribute.name == "position") continue; // omit geometry from properties + + if (!first) { + *stream << ", "; + } + + *stream << "\"" << replaceAll(attribute.name, " ", "_") << "\": "; + + if (attribute.numElements > 1) { + *stream << "["; + } + + if (attribute.name == "position") { + dvec3 xyz = points->getPosition(i); + *stream << xyz.x << ", " << xyz.y << ", " << xyz.z; + } + else if (attribute.name == "position_projected_profile") { + auto i32 = points->attributeBuffersMap[attribute.name]->data_i32; + dvec2 xy = glm::dvec2( + i32[2 * i + 0] * points->attributes.posScale.x, // X -> projected on X-Y plane + i32[2 * i + 1] * points->attributes.posScale.z // Y -> Z + ); + *stream << xy.x << ", " << xy.y; + } + else { + + for (int j = 0; j < attribute.numElements; j++) { + + if (attribute.type == AttributeType::INT8) { + auto i8 = points->attributeBuffersMap[attribute.name]->data_i8; + int8_t value = i8[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT16) { + auto i16 = points->attributeBuffersMap[attribute.name]->data_i16; + int16_t value = i16[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT32) { + auto i32 = points->attributeBuffersMap[attribute.name]->data_i32; + int32_t value = i32[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::INT64) { + auto i64 = points->attributeBuffersMap[attribute.name]->data_i64; + int64_t value = i64[attribute.numElements * i + j]; + *stream << int(value); + } + else if (attribute.type == AttributeType::UINT8) { + auto u8 = points->attributeBuffersMap[attribute.name]->data_u8; + uint8_t value = u8[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT16) { + auto u16 = points->attributeBuffersMap[attribute.name]->data_u16; + uint16_t value = u16[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT32) { + auto u32 = points->attributeBuffersMap[attribute.name]->data_u32; + uint32_t value = u32[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::UINT64) { + auto u64 = points->attributeBuffersMap[attribute.name]->data_u64; + uint64_t value = u64[attribute.numElements * i + j]; + *stream << unsigned(value); + } + else if (attribute.type == AttributeType::FLOAT) { + auto f32 = points->attributeBuffersMap[attribute.name]->data_f32; + float value = f32[attribute.numElements * i + j]; + *stream << value; + } + else if (attribute.type == AttributeType::DOUBLE) { + auto f64 = points->attributeBuffersMap[attribute.name]->data_f64; + double value = f64[attribute.numElements * i + j]; + *stream << value; + } + else { + *stream << "null"; + } + + if (j < attribute.numElements - 1) { + *stream << ", "; + } + } + } + + if (attribute.numElements > 1) { + *stream << "]"; + } + + first = false; + } + + if (geojson) { + *stream << " }"; + } + + *stream << " }"; + + // if (i < numPoints - 1) {} not woking because of multiple threads + *stream << "," << endl; // next object + } + + } + + void close() { + *stream << "null" << endl; // remove last comma + *stream << "]"; + + if (geojson) { + *stream << " }"; + } + + //stream->close(); + stream = nullptr; + } + +}; \ No newline at end of file diff --git a/include/LasWriter.h b/include/LasWriter.h index 51d4bf2..5021bcd 100644 --- a/include/LasWriter.h +++ b/include/LasWriter.h @@ -119,7 +119,7 @@ struct LasWriter : public Writer { mutex mtx_write; - LasWriter(string path, dvec3 scale, dvec3 offset, Attributes outputAttributes) { + LasWriter(string path, dvec3 scale, dvec3 offset, Attributes outputAttributes, bool zip) { this->path = path; this->outputAttributes = outputAttributes; @@ -153,14 +153,18 @@ struct LasWriter : public Writer { header.extended_number_of_point_records = 111; - laszip_BOOL compress = path.ends_with(".laz") || path.ends_with(".LAZ"); + laszip_BOOL compress = zip; laszip_BOOL request_writer = 1; laszip_request_compatibility_mode(laszip_writer, request_writer); //laszip_set_chunk_size(laszip_writer, 50'000); laszip_set_header(laszip_writer, &header); - laszip_open_writer(laszip_writer, path.c_str(), compress); + if (path != "stdout") { + laszip_open_writer(laszip_writer, path.c_str(), compress); + } else { + laszip_open_writer_stream(laszip_writer, cout, compress, false); + } laszip_get_point_pointer(laszip_writer, &point); } diff --git a/libs/brotli/CMakeLists.txt b/libs/brotli/CMakeLists.txt index 4ff3401..dfa7e3b 100644 --- a/libs/brotli/CMakeLists.txt +++ b/libs/brotli/CMakeLists.txt @@ -289,10 +289,6 @@ if(NOT BROTLI_DISABLE_TESTS) enable_testing() set(ROUNDTRIP_INPUTS - tests/testdata/alice29.txt - tests/testdata/asyoulik.txt - tests/testdata/lcet10.txt - tests/testdata/plrabn12.txt c/enc/encode.c c/common/dictionary.h c/dec/decode.c) diff --git a/src/executable_extract_area.cpp b/src/executable_extract_area.cpp index bb8cac7..31f8b72 100644 --- a/src/executable_extract_area.cpp +++ b/src/executable_extract_area.cpp @@ -7,6 +7,9 @@ #include #include #include +#include + +#include "json/json.hpp" #include "pmath.h" #include "unsuck/unsuck.hpp" @@ -18,57 +21,95 @@ #include "PotreeLoader.h" #include "LasWriter.h" #include "CsvWriter.h" +#include "JsonWriter.h" #include "PotreeWriter.h" #include "Attributes.h" +using std::set; using std::string; using std::function; using std::shared_ptr; +using json = nlohmann::json; + struct AcceptedItem { Node* node; shared_ptr data; }; -Attributes computeAttributes(Arguments& args) { +Attributes computeAttributes(Arguments& args, vector sources) { vector list; + unordered_map map; - Attribute position("position", 12, 3, 4, AttributeType::INT32); - Attribute rgb("rgb", 6, 3, 2, AttributeType::UINT16); - Attribute intensity("intensity", 2, 1, 2, AttributeType::UINT16); + for (string path : sources) { + string metadataPath = path + "/metadata.json"; + string txtMetadata = readTextFile(metadataPath); + json jsMetadata = json::parse(txtMetadata); + + auto jsAttributes = jsMetadata["attributes"]; + for (auto jsAttribute : jsAttributes) { + + Attribute attribute; + + attribute.name = jsAttribute["name"]; + attribute.size = jsAttribute["size"]; + attribute.numElements = jsAttribute["numElements"]; + attribute.elementSize = jsAttribute["elementSize"]; + attribute.type = typenameToType(jsAttribute["type"]); + + attribute.min.x = jsAttribute["min"][0]; + attribute.max.x = jsAttribute["max"][0]; + + if (jsAttribute["min"].size() > 1) { + attribute.min.y = jsAttribute["min"][1]; + attribute.max.y = jsAttribute["max"][1]; + } - unordered_map mapping = { - {"position", position}, - {"rgb", rgb}, - {"rgba", rgb}, - {"intensity", intensity}, - }; + if (jsAttribute["min"].size() > 2) { + attribute.min.z = jsAttribute["min"][2]; + attribute.max.z = jsAttribute["max"][2]; + } - if (args.has("output-attributes")) { + bool alreadyAdded = map.find(attribute.name) != map.end(); + if (!alreadyAdded) { + list.push_back(attribute); + map[attribute.name] = attribute; + } - list.push_back(position); + } + + } + + vector chosen; + + if (args.has("output-attributes")) { vector attributeNames = args.get("output-attributes").as>(); - for (string attributeName : attributeNames) { + if (attributeNames[0] != "position") { + attributeNames.insert(attributeNames.begin(), "position"); + } - if (mapping.find(attributeName) != mapping.end()) { - list.push_back(mapping[attributeName]); - } else { - cout << "WARNING: could not find a handler for attribute '" << attributeName << "'. The attribute will be ignored."; + for (string attributeName : attributeNames) { + if (map.find(attributeName) != map.end()) { + chosen.push_back(map[attributeName]); } - +// else { +// cout << "WARNING: could not find a handler for attribute '" << attributeName << "'. The attribute will be ignored."; +// } } } else { + chosen = list; + } - list.push_back(position); - list.push_back(rgb); - list.push_back(intensity); + if (map.find("position_projected_profile") == map.end()) { + Attribute position_projected_profile("position_projected_profile", 8, 2, 4, AttributeType::INT32); + chosen.push_back(position_projected_profile); } - Attributes attributes(list); + Attributes attributes(chosen); return attributes; } @@ -86,7 +127,7 @@ vector curateSources(vector sources) { if (isMetadataFile) { string metadataFolder = fs::path(path).parent_path().string(); curated.push_back(metadataFolder); - } else if(isDirectory && hasMetadataFile) { + } else if (isDirectory && hasMetadataFile) { curated.push_back(path); } else { cout << "ERROR: not a valid potree file path '" << path << "'" << endl; @@ -107,36 +148,48 @@ int main(int argc, char** argv) { auto tStart = now(); - printElapsedTime("00", tStart); - Arguments args(argc, argv); - args.addArgument("source,i,", "input files"); - args.addArgument("output,o", "output file or directory, depending on target format"); - args.addArgument("area", "clip area"); - args.addArgument("output-format", "LAS, LAZ, POTREE"); - args.addArgument("min-level", ""); - args.addArgument("max-level", ""); - args.addArgument("output-attributes", ""); + args.addArgument("source,i,", "input files (path to metadata.json)"); + args.addArgument("output,o", "output file, depending on target format. When not set, the result will be written in the stdout."); + args.addArgument("area", "clip area, specified as unit cube matrix \"{tx,rz,ry,0,-rz,ty,rx,0,-ry,-rx,tz,0,dx,dy,dz,1}\""); + args.addArgument("output-format", "LAS, LAZ, CSV, JSON, POTREE (default"); + args.addArgument("min-level", "the result will contain points starting from this level"); + args.addArgument("max-level", "the result will contain points up to this level"); + args.addArgument("output-attributes", "position, rgb, intensity, classification, ... Default: same as input point cloud"); args.addArgument("get-candidates", "return number of candidate points"); + if (!args.has("area")) { + GENERATE_ERROR_MESSAGE << "missing argument: --area \"{tx,rz,ry,0,-rz,ty,rx,0,-ry,-rx,tz,0,dx,dy,dz,1}\"" << endl; + exit(123); + } + + string strArea = args.get("area").as(); vector sources = args.get("source").as>(); string targetpath = args.get("output").as(); + if (targetpath == "") targetpath = "stdout"; + string format = args.get("output-format").as(); int minLevel = args.get("min-level").as(0); int maxLevel = args.get("max-level").as(10'000); + strArea = replaceAll(strArea, " ", ""); + strArea = replaceAll(strArea, "},{", "),("); + strArea = replaceAll(strArea, "{", "matrix("); + strArea = replaceAll(strArea, "}", ")"); Area area = parseArea(strArea); sources = curateSources(sources); auto stats = computeStats(sources); - Attributes outputAttributes = computeAttributes(args); + Attributes outputAttributes = computeAttributes(args, sources); + outputAttributes.posScale = {0.001, 0.001, 0.001}; auto min = stats.aabb.min; auto max = stats.aabb.max; if (args.has("get-candidates")) { + int64_t numCandidates = 0; for (string path : sources) { numCandidates += getNumCandidates(path, area, minLevel, maxLevel); @@ -149,23 +202,23 @@ int main(int argc, char** argv) { shared_ptr writer; - if (iEndsWith(targetpath, "las") || iEndsWith(targetpath, "laz")) { - writer = make_shared(targetpath, scale, offset, outputAttributes); - } else if (iEndsWith(targetpath, "csv")) { - writer = make_shared(targetpath, outputAttributes); - } else if (iEndsWith(targetpath, "potree")) { + if (format == "LAS" || iEndsWith(targetpath, "las")) { + writer = make_shared(targetpath, scale, offset, outputAttributes, false); + } else if (format == "LAZ" || iEndsWith(targetpath, "laz")) { + writer = make_shared(targetpath, scale, offset, outputAttributes, true); + } else if (format == "JSON" || iEndsWith(targetpath, "json")) { + writer = make_shared(targetpath, outputAttributes, false); + } else if (format == "CSV" || iEndsWith(targetpath, "csv")) { + writer = make_shared(targetpath, outputAttributes, ",", ","); + } else if (format == "POTREE" || iEndsWith(targetpath, "potree")) { writer = make_shared(targetpath, scale, offset, outputAttributes); - } else if(targetpath == "stdout"){ - cout << "TODO: " << __FILE__ << ":" << __LINE__ << endl; - //writer = make_shared(targetpath, scale, offset, outputAttributes); } else { cout << "ERROR: unkown output format, extension not known: " << targetpath << endl; } - int64_t totalAccepted = 0; int64_t totalRejected = 0; - for(string path : sources){ + for (string path : sources) { filterPointcloud(path, area, minLevel, maxLevel, [&writer, tStart, &totalAccepted, &totalRejected](Node* node, shared_ptr points, int64_t numAccepted, int64_t numRejected){ @@ -173,17 +226,24 @@ int main(int argc, char** argv) { totalRejected += numRejected; writer->write(node, points, numAccepted, numRejected); + + //{ // DEBUG MESSAGE + // stringstream ss; + // ss << std::this_thread::get_id() << ": loadPoints() end" << endl; + // cout << ss.str(); + //} + }); }; - cout << "#accepted: " << totalAccepted << ", #rejected: " << totalRejected << endl; + //cout << "#accepted: " << totalAccepted << ", #rejected: " << totalRejected << endl; writer->close(); } - printElapsedTime("duration", tStart); + //printElapsedTime("duration", tStart); return 0; diff --git a/src/executable_extract_profile.cpp b/src/executable_extract_profile.cpp index 143edab..b1044f2 100644 --- a/src/executable_extract_profile.cpp +++ b/src/executable_extract_profile.cpp @@ -21,6 +21,7 @@ #include "PotreeLoader.h" #include "LasWriter.h" #include "CsvWriter.h" +#include "JsonWriter.h" #include "PotreeWriter.h" #include "Attributes.h" @@ -80,26 +81,10 @@ Attributes computeAttributes(Arguments& args, vector sources) { } - - //vector list; - - //Attribute position("position", 12, 3, 4, AttributeType::INT32); - //Attribute rgb("rgb", 6, 3, 2, AttributeType::UINT16); - //Attribute intensity("intensity", 2, 1, 2, AttributeType::UINT16); - //Attribute position_projected_profile("position_projected_profile", 8, 2, 4, AttributeType::INT32); - - //unordered_map mapping = { - // {"position", position}, - // {"rgb", rgb}, - // {"rgba", rgb}, - // {"intensity", intensity}, - //}; - vector chosen; if (args.has("output-attributes")) { - vector attributeNames = args.get("output-attributes").as>(); if (attributeNames[0] != "position") { @@ -110,24 +95,11 @@ Attributes computeAttributes(Arguments& args, vector sources) { if (map.find(attributeName) != map.end()) { chosen.push_back(map[attributeName]); } +// else { +// cout << "WARNING: could not find a handler for attribute '" << attributeName << "'. The attribute will be ignored."; +// } } - - - //list.push_back(position); - - //vector attributeNames = args.get("output-attributes").as>(); - - //for (string attributeName : attributeNames) { - - // if (mapping.find(attributeName) != mapping.end()) { - // list.push_back(mapping[attributeName]); - // } else { - // cout << "WARNING: could not find a handler for attribute '" << attributeName << "'. The attribute will be ignored."; - // } - - //} - } else { chosen = list; } @@ -205,14 +177,14 @@ int main(int argc, char** argv) { Arguments args(argc, argv); - args.addArgument("source,i,", "input files"); - args.addArgument("output,o", "output file or directory, depending on target format"); - args.addArgument("coordinates", "coordinates of the profile segments. in the form \"{x0,y0},{x1,y1},...\""); + args.addArgument("source,i,", "input files (path to metadata.json)"); + args.addArgument("output,o", "output file, depending on target format. When not set, the result will be written in the stdout."); + args.addArgument("coordinates", "coordinates of the profile segments, in the form \"{x0,y0},{x1,y1},...\""); args.addArgument("width", "width of the profile"); - args.addArgument("output-format", "LAS, LAZ, POTREE"); - args.addArgument("min-level", ""); - args.addArgument("max-level", ""); - args.addArgument("output-attributes", ""); + args.addArgument("output-format", "LAS, LAZ, CSV, JSON, POTREE (default)"); + args.addArgument("min-level", "the result will contain points starting from this level"); + args.addArgument("max-level", "the result will contain points up to this level"); + args.addArgument("output-attributes", "position, position_projected_profile, rgb, intensity, classification, ... Default: same as input point cloud"); args.addArgument("get-candidates", "return number of candidate points"); if (!args.has("coordinates")) { @@ -228,6 +200,8 @@ int main(int argc, char** argv) { string strCoordinates = args.get("coordinates").as(); vector sources = args.get("source").as>(); string targetpath = args.get("output").as(); + if (targetpath == "") targetpath = "stdout"; + string format = args.get("output-format").as(); double width = args.get("width").as(); int minLevel = args.get("min-level").as(0); int maxLevel = args.get("max-level").as(10'000); @@ -259,14 +233,16 @@ int main(int argc, char** argv) { shared_ptr writer; - if (iEndsWith(targetpath, "las") || iEndsWith(targetpath, "laz")) { - writer = make_shared(targetpath, scale, offset, outputAttributes); - } else if (iEndsWith(targetpath, "csv")) { - writer = make_shared(targetpath, outputAttributes); - } else if (iEndsWith(targetpath, "potree")) { + if (format == "LAS" || iEndsWith(targetpath, "las")) { + writer = make_shared(targetpath, scale, offset, outputAttributes, false); + } else if (format == "LAZ" || iEndsWith(targetpath, "laz")) { + writer = make_shared(targetpath, scale, offset, outputAttributes, true); + } else if (format == "JSON" || iEndsWith(targetpath, "json")) { + writer = make_shared(targetpath, outputAttributes, false); + } else if (format == "CSV" || iEndsWith(targetpath, "csv")) { + writer = make_shared(targetpath, outputAttributes, ",", ","); + } else if (format == "POTREE" || iEndsWith(targetpath, "potree")) { writer = make_shared(targetpath, scale, offset, outputAttributes); - } else if (targetpath == "stdout") { - writer = make_shared("stdout", scale, offset, outputAttributes); } else { cout << "ERROR: unkown output format, extension not known: " << targetpath << endl; }