diff --git a/Applications/FileIO/CsvInterface.cpp b/Applications/FileIO/CsvInterface.cpp index ce2878960cf3a51b5ce117d150be8905acfd5bad..9d75c49cba0dd434b7c1076c588d79de68b04b26 100644 --- a/Applications/FileIO/CsvInterface.cpp +++ b/Applications/FileIO/CsvInterface.cpp @@ -14,12 +14,20 @@ #include "CsvInterface.h" #include <algorithm> +#include <iostream> +#include <numeric> #include <stdexcept> #include "GeoLib/Point.h" namespace FileIO { +CsvInterface::CsvInterface() +: _writeCsvHeader(true) +{ +} + + int CsvInterface::readPoints(std::string const& fname, char delim, std::vector<GeoLib::Point*> &points) { @@ -181,4 +189,64 @@ std::size_t CsvInterface::findColumn(std::string const& line, char delim, std::s return count; } +void CsvInterface::addIndexVectorForWriting(std::size_t s) +{ + std::vector<int> idx_vec(s); + std::iota(idx_vec.begin(), idx_vec.end(), 0); + addVectorForWriting("Index", idx_vec); +} + +bool CsvInterface::write() +{ + if (_data.empty()) + { + ERR ("CsvInterface::write() - No data to write."); + return false; + } + + std::size_t const n_vecs (_data.size()); + std::size_t const vec_size (getVectorSize(0)); + + if (_writeCsvHeader) + { + _out << _vec_names[0]; + for (std::size_t i=1; i<n_vecs; ++i) + _out << "\t" << _vec_names[i]; + _out << "\n"; + } + + for (std::size_t j=0; j<vec_size; ++j) + { + writeValue(0,j); + for (std::size_t i=1; i<n_vecs; ++i) + { + _out << "\t"; + writeValue(i,j); + } + _out << "\n"; + } + return true; +} + +std::size_t CsvInterface::getVectorSize(std::size_t idx) const +{ + if (_data[idx].type() == typeid(std::vector<std::string>)) + return boost::any_cast<std::vector<std::string>>(_data[idx]).size(); + else if (_data[idx].type() == typeid(std::vector<double>)) + return boost::any_cast<std::vector<double>>(_data[idx]).size(); + else if (_data[idx].type() == typeid(std::vector<int>)) + return boost::any_cast<std::vector<int>>(_data[idx]).size(); + return 0; +} + +void CsvInterface::writeValue(std::size_t vec_idx, std::size_t in_vec_idx) +{ + if (_data[vec_idx].type() == typeid(std::vector<std::string>)) + _out << boost::any_cast<std::vector<std::string>>(_data[vec_idx])[in_vec_idx]; + else if (_data[vec_idx].type() == typeid(std::vector<double>)) + _out << boost::any_cast<std::vector<double>>(_data[vec_idx])[in_vec_idx]; + else if (_data[vec_idx].type() == typeid(std::vector<int>)) + _out << boost::any_cast<std::vector<int>>(_data[vec_idx])[in_vec_idx]; +} + } // end namespace FileIO diff --git a/Applications/FileIO/CsvInterface.h b/Applications/FileIO/CsvInterface.h index e78597c96e5eb72235f4e02fb1fe883096c19601..cf157b73cb73f664a62bb5416820b6188c22b4ea 100644 --- a/Applications/FileIO/CsvInterface.h +++ b/Applications/FileIO/CsvInterface.h @@ -15,6 +15,7 @@ #define CSVINTERFACE_H_ #include <logog/include/logog.hpp> +#include <boost/any.hpp> #include <array> #include <fstream> @@ -22,10 +23,12 @@ #include <limits> #include <list> #include <string> +#include <typeinfo> #include <vector> #include "BaseLib/StringTools.h" +#include "BaseLib/IO/Writer.h" namespace GeoLib { class Point; @@ -36,9 +39,50 @@ namespace FileIO { /** * Interface for reading CSV file formats. */ -class CsvInterface { +class CsvInterface : public BaseLib::IO::Writer +{ public: + /// Contructor (only needed for writing files) + CsvInterface(); + + /// Returns the number of vectors currently staged for writing. + std::size_t getNArrays() const { return _vec_names.size(); } + + /// Adds an index vector of size s to the CSV file + void addIndexVectorForWriting(std::size_t s); + + /// Stores if the CSV file to be written should include a header or not. + void setCsvHeader(bool write_header) { _writeCsvHeader = write_header; } + + /// Adds a data vector to the CSV file. All data vectors have to have the same size. + /// Vectors will be written in the same sequence they have been added to the interface. + template<typename T> + bool addVectorForWriting(std::string const& vec_name, std::vector<T> const& vec) + { + static_assert(std::is_same<T, std::string>::value + || std::is_same<T, double>::value + || std::is_same<T, int>::value, + "CsvInterface can only write vectors of strings, doubles or ints."); + + if (!_data.empty()) + { + std::size_t const vec_size (getVectorSize(0)); + if (vec_size != vec.size()) + { + ERR ("Vector size does not match existing data (should be %d).", vec_size); + return false; + } + } + + _vec_names.push_back(vec_name); + _data.push_back(vec); + return true; + } + + /// Writes the CSV file. + bool write(); + /** * Reads 3D points from a CSV file. It is assumed that the file has a header * specifying a name for each of the columns. The first three columns will be @@ -178,6 +222,20 @@ private: /// Returns the number of the column with column_name (or std::numeric_limits::max() if no such column has been found). static std::size_t findColumn(std::string const& line, char delim, std::string const& column_name); + + /// Returns the size of the vector with the given index + std::size_t getVectorSize(std::size_t idx) const; + + /** + * Writes a value from a vector to the file. + * \param vec_idx Index of the vector + * \param in_vec_idx Entry in the selected vector + */ + void writeValue(std::size_t vec_idx, std::size_t in_vec_idx); + + bool _writeCsvHeader; + std::vector<std::string> _vec_names; + std::vector< boost::any > _data; }; } // FileIO diff --git a/Tests/FileIO/TestCsvWriter.cpp b/Tests/FileIO/TestCsvWriter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8db81e3796e132167b6fe56171c7c8ff0a0d3325 --- /dev/null +++ b/Tests/FileIO/TestCsvWriter.cpp @@ -0,0 +1,81 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + * + */ + +#include <cstdio> + +#include "gtest/gtest.h" + +#include <string> +#include <vector> + +#include "BaseLib/BuildInfo.h" +#include "Applications/FileIO/CsvInterface.h" + +TEST(CsvWriter, WriteReadTest) +{ + std::string test_file(BaseLib::BuildInfo::tests_tmp_path + "TestData.csv"); + + std::vector<std::string> str_vec {"Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet" }; + std::vector<int> int_vec { 1, 2, 4, 8, 16, 32, 64 }; + std::vector<double> dbl_vec; + std::srand ( static_cast<unsigned>(std::time(nullptr)) ); + for (std::size_t i=0; i<int_vec.size(); ++i) + dbl_vec.push_back(static_cast<double>(std::rand()) / RAND_MAX); + + FileIO::CsvInterface csv; + bool added; + std::vector<std::string> vec_names { "String Vector", "Int Vector", "Double Vector"}; + added = csv.addVectorForWriting(vec_names[0], str_vec); + ASSERT_EQ(true, added); + added = csv.addVectorForWriting(vec_names[1], int_vec); + ASSERT_EQ(true, added); + added = csv.addVectorForWriting(vec_names[2], dbl_vec); + ASSERT_EQ(true, added); + int_vec.push_back(128); + added = csv.addVectorForWriting(vec_names[1], int_vec); + ASSERT_EQ(false, added); + ASSERT_EQ(3, csv.getNArrays()); + csv.addIndexVectorForWriting(str_vec.size()); + ASSERT_EQ(4, csv.getNArrays()); + int result = csv.writeToFile(test_file); + ASSERT_EQ(1, result); + + std::vector<std::string> str_result; + result = FileIO::CsvInterface::readColumn<std::string>(test_file, '\t', str_result, vec_names[0]); + ASSERT_EQ(0, result); + ASSERT_EQ(str_vec.size(), str_result.size()); + + std::vector<int> idx_result; + result = FileIO::CsvInterface::readColumn<int>(test_file, '\t', idx_result, "Index"); + ASSERT_EQ(0, result); + ASSERT_EQ(dbl_vec.size(), idx_result.size()); + + std::vector<int> int_result; + result = FileIO::CsvInterface::readColumn<int>(test_file, '\t', int_result, vec_names[1]); + ASSERT_EQ(0, result); + // testing for vector length -1 because it had increased previously when testing size requirements + ASSERT_EQ(int_vec.size()-1, int_result.size()); + + std::vector<double> dbl_result; + result = FileIO::CsvInterface::readColumn<double>(test_file, '\t', dbl_result, vec_names[2]); + ASSERT_EQ(0, result); + ASSERT_EQ(dbl_vec.size(), dbl_result.size()); + + for (std::size_t i=0; i<str_vec.size(); ++i) + { + ASSERT_EQ(str_vec[i], str_result[i]); + ASSERT_EQ(int_vec[i], int_result[i]); + ASSERT_NEAR(dbl_vec[i], dbl_result[i], 10 * std::numeric_limits<double>::epsilon()); + ASSERT_EQ(static_cast<int>(i), idx_result[i]); + } + + std::remove(test_file.c_str()); +} +