Adds CPP implementatinon

This commit is contained in:
Erwin Nindl 2021-06-26 09:15:57 +02:00
parent f6e691d3db
commit c72e925ac2
11 changed files with 461 additions and 32 deletions

View File

@ -1,9 +1,20 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.11)
project(EsriAssignment)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/ArthurSonzogni/nlohmann_json_cmake_fetchcontent
GIT_TAG v3.9.1)
FetchContent_GetProperties(json)
if(NOT json_POPULATED)
FetchContent_Populate(json)
add_subdirectory(${json_SOURCE_DIR} ${json_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
find_package(Protobuf REQUIRED)
include_directories(${PROTOBUF_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
@ -11,4 +22,3 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(protocol_buffers_definitions)
include_directories(src)
add_subdirectory(src)

View File

@ -1,6 +1,10 @@
set(assignment_SOURCES
main.cpp)
main.cpp
calc.cpp
json_output.cpp
proto_loader.cpp)
add_executable(main ${assignment_SOURCES})
target_link_libraries(main proto ${PROTOBUF_LIBRARY})
target_link_libraries(main PRIVATE proto ${PROTOBUF_LIBRARY})
target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json)

70
src/calc.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "calc.hpp"
#include <algorithm>
namespace calc {
std::pair<Position, Position>
position_segment(std::vector<Position> const& position, double time)
{
auto second = std::lower_bound(position.begin(), position.end(), time,
[](Position const& pos, double t) {
return pos.time < t;
});
if (second == position.begin()) { // corner case
second++;
}
return std::make_pair(*(second-1), *second);
}
void interpolate_positions(std::vector<Magnetic>& magnetics, std::vector<Position> const& truth)
{
for (auto& mag : magnetics) {
auto const truth_seg = position_segment(truth, mag.time);
for (size_t i = 0; i < mag.position.size(); i++) {
mag.position[i] =
(truth_seg.second.position[i] - truth_seg.first.position[i]) /
(truth_seg.second.time - truth_seg.first.time) *
(mag.time - truth_seg.first.time) + truth_seg.first.position[i];
}
}
}
void crop_temporal_inersection(std::vector<Magnetic>& magnetics, std::vector<Position> const& truth)
{
auto const mag_first = std::lower_bound(
magnetics.begin(), magnetics.end(), truth[0].time,
[](Magnetic const& mag, double t) {
return mag.time < t;
});
magnetics.erase(magnetics.begin(), mag_first);
auto const mag_last = std::upper_bound(
magnetics.begin(), magnetics.end(), truth.back().time,
[](double t, Magnetic const& mag) {
return t < mag.time;
});
magnetics.erase(mag_last, magnetics.end());
}
std::tuple<double, double, double, double>
spatial_extent(std::vector<Magnetic> const& mag)
{
auto [x_min, x_max] = std::minmax_element(
std::cbegin(mag), std::cend(mag),
[](Magnetic const& a, Magnetic const& b) {
return a.position[0] < b.position[0];
});
auto [y_min, y_max] = std::minmax_element(
std::cbegin(mag), std::cend(mag),
[](Magnetic const& a, Magnetic const& b) {
return a.position[1] < b.position[1];
});
return std::make_tuple(x_min->position[0], y_min->position[1], x_max->position[0], y_max->position[1]);
}
}

47
src/calc.hpp Normal file
View File

@ -0,0 +1,47 @@
#ifndef CALC_HPP_
#define CALC_HPP_
#include "data.hpp"
#include <cmath>
#include <numeric>
#include <vector>
namespace calc {
template <typename iter_t>
double norm(iter_t first, iter_t last)
{
return std::sqrt(std::inner_product(first, last, first, 0.0));
}
std::pair<Position, Position>
position_segment(std::vector<Position> const& position, double time);
void interpolate_positions(std::vector<Magnetic>& magnetics, std::vector<Position> const& truth);
void crop_temporal_inersection(std::vector<Magnetic>& magnetics, std::vector<Position> const& truth);
std::tuple<double, double, double, double>
spatial_extent(std::vector<Magnetic> const& mag);
template<typename grid_t, typename avg_grid_t>
void average_grid(grid_t& grid, avg_grid_t& average_grid)
{
for (size_t x = 0; x < grid.size_x(); x++) {
for (size_t y = 0; y < grid.size_y(); y++) {
auto const& cell = grid[x][y];
if (cell.size() > 0) {
double const sum = std::accumulate(cell.cbegin(), cell.cend(), 0.0);
size_t const n = cell.size();
average_grid[x][y] = sum / n;
}
}
}
}
} // namespace calc
#endif // CALC_HPP_

31
src/data.hpp Normal file
View File

@ -0,0 +1,31 @@
#ifndef DATA_HPP_
#define DATA_HPP_
#include <array>
#include <vector>
using Coordinate = std::array<double, 2>;
struct Magnetic {
Magnetic(double t, double x, double y, double z) :
time(t), field({x, y, z}) {}
Magnetic(double t, double x, double y) :
time(t), position({x, y}) {}
double time;
std::array<double, 3> field;
Coordinate position;
};
struct Position {
Position(double t, double x, double y) :
time(t), position({x, y}) {}
double time;
Coordinate position;
};
using Magnetics = std::vector<Magnetic>;
using Positions = std::vector<Position>;
#endif /* DATA_HPP_ */

22
src/json_output.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "json_output.hpp"
#include <iomanip>
#include <fstream>
JsonOutput::JsonOutput(char const* file_path) :
filePath_(file_path)
{}
bool JsonOutput::write()
{
std::ofstream outfile(filePath_);
if (!outfile) {
return false;
}
outfile << std::setw(2);
outfile << jsonData_ << std::endl;
return true;
}

24
src/json_output.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef JSONOUTPUT_HPP_
#define JSONOUTPUT_HPP_
#include <nlohmann/json.hpp>
class JsonOutput {
public:
explicit JsonOutput(char const* file_path);
~JsonOutput() = default;
template<typename T>
void add(char const* key, T const& obj)
{
jsonData_[key] = obj;
}
bool write();
private:
char const* filePath_;
::nlohmann::json jsonData_;
};
#endif // JSONOUTPUT_HPP_

View File

@ -1,40 +1,107 @@
#include <fstream>
#include "calc.hpp"
#include "json_output.hpp"
#include "proto_loader.hpp"
#include "spatial_grid.hpp"
#include <iostream>
#include "protocol_buffers_definitions/recordings.pb.h"
#include <limits>
#include <string>
#include <utility>
#include <vector>
using namespace ::indoors::proto;
static void print_usage()
{
std::cout << "ESRI C++ code assignment, E. Nindl 2021\n\n";
std::cout << "Usage:\n";
std::cout << " executable <input-file[s]> <json-ouput-file>\n";
}
std::tuple<double, double, double, double>
total_spatial_extent(std::vector<Magnetics> const& magnetics_recs) {
double x_min = std::numeric_limits<double>::max();
double y_min = std::numeric_limits<double>::max();
double x_max = std::numeric_limits<double>::min();
double y_max = std::numeric_limits<double>::min();
for (auto const& magnetics : magnetics_recs) {
auto const [x_min_i, y_min_i, x_max_i, y_max_i] = ::calc::spatial_extent(magnetics);
x_min = std::min(x_min, x_min_i);
y_min = std::min(y_min, y_min_i);
x_max = std::max(x_max, x_max_i);
y_max = std::max(y_max, y_max_i);
}
return std::make_tuple(x_min, y_min, x_max, y_max);
}
static bool write_output(std::string const& output_file, SpatialGrid<double>& grid)
{
JsonOutput json_output(output_file.c_str());
json_output.add("start_x", grid.start_x());
json_output.add("start_y", grid.start_y());
json_output.add("spacing", grid.spacing);
json_output.add("data", grid.data());
return json_output.write();
}
int main(int argc, char* argv[])
{
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
std::cout << "ESRI C++ code assignment\n";
Recording recording;
Position positions;
std::fstream input(argv[1], std::ios::in | std::ios::binary);
if (!recording.ParseFromIstream(&input)) {
std::cerr << "Failed to parse input file.\n";
return -1;
if (argc < 3) {
print_usage();
return 1;
}
std::cout.precision(17);
for (int i=0; i < recording.magnetics_size(); i++) {
auto const& s = recording.magnetics(i);
std::cout << s.t() << ": [" << s.x() << ", " << s.y() << ", " << s.z() << "]\n";
}
for (int i=0; i < recording.positions_size(); i++) {
auto const& s = recording.positions(i);
std::cout << s.t() << ": [" << s.x() << ", " << s.y() << "]\n";
std::string const output_file{argv[argc - 1]};
std::vector<std::string> input_files;
for (uint32_t i = 1; i < (argc - 1); i++) {
input_files.push_back(argv[i]);
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
std::vector<Magnetics> magnetics_recs;
std::vector<Positions> groundtruth_recs;
try {
for (auto const& input_file : input_files) {
ProtoLoader loader(input_file.c_str());
magnetics_recs.push_back(loader.magnetics());
groundtruth_recs.push_back(loader.groundtruth());
}
} catch (std::exception& e) {
std::cerr << "Error loading input: " << e.what() << "\n";
return 1;
}
for (size_t rec_id = 0; rec_id < magnetics_recs.size(); rec_id++) {
Magnetics& magnetics = magnetics_recs[rec_id];
Positions& groundtruth = groundtruth_recs[rec_id];
::calc::crop_temporal_inersection(magnetics, groundtruth);
if (magnetics.size() == 0) {
std::cerr << "Error in groundtruth data\n";
return 1;
}
::calc::interpolate_positions(magnetics, groundtruth);
}
auto const [x_min, y_min, x_max, y_max] = total_spatial_extent(magnetics_recs);
SpatialGrid<std::vector<double>> grid(x_min, y_min, x_max, y_max);
for (size_t rec_id = 0; rec_id < magnetics_recs.size(); rec_id++) {
Magnetics& magnetics = magnetics_recs[rec_id];
Positions& groundtruth = groundtruth_recs[rec_id];
for (auto& mag : magnetics) {
auto& cell = grid.at(mag.position);
double const magnitude = ::calc::norm(mag.field.cbegin(), mag.field.cend());
cell.push_back(magnitude);
}
}
SpatialGrid<double> average_grid(x_min, y_min, x_max, y_max);
calc::average_grid(grid, average_grid);
if (!write_output(output_file, average_grid)) {
std::cout << "Error writing output file: " << output_file << "\n";
return 1;
}
return 0;
}

61
src/proto_loader.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "proto_loader.hpp"
#include "protocol_buffers_definitions/recordings.pb.h"
#include <fstream>
#include <stdexcept>
ProtoLoader::ProtoLoader(char const* f) :
filePath_(f)
{
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
std::fstream input(filePath_, std::ios::in | std::ios::binary);
if (!recording_.ParseFromIstream(&input)) {
throw std::runtime_error("Failed to parse input file");
}
}
ProtoLoader::~ProtoLoader()
{
// Optional: Delete all global objects allocated by libprotobuf.
::google::protobuf::ShutdownProtobufLibrary();
}
std::vector<Magnetic> ProtoLoader::magnetics() const
{
double t_prev = 0;
std::vector<Magnetic> magnetics;
for (int i = 0; i < recording_.magnetics_size(); i++) {
auto const& s = recording_.magnetics(i);
double const t = s.t();
if (t_prev > t) {
throw std::runtime_error("Magnetics data not sorted by time");
}
magnetics.emplace_back(t, s.x(), s.y(), s.z());
t_prev = t;
}
return magnetics;
}
std::vector<Position> ProtoLoader::groundtruth() const
{
double t_prev = 0;
std::vector<Position> groundtruth;
for (int i=0; i < recording_.positions_size(); i++) {
auto const& s = recording_.positions(i);
double const t = s.t();
if ((t - t_prev) < dt_min) {
throw std::runtime_error("Groundtruth data invalid");
}
groundtruth.emplace_back(t, s.x(), s.y());
t_prev = t;
}
return groundtruth;
}

24
src/proto_loader.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef PROTOLOADER_HPP_
#define PROTOLOADER_HPP_
#include "data.hpp"
#include "protocol_buffers_definitions/recordings.pb.h"
#include <vector>
class ProtoLoader {
public:
explicit ProtoLoader(char const* file_path);
~ProtoLoader();
std::vector<Magnetic> magnetics() const;
std::vector<Position> groundtruth() const;
static constexpr double dt_min{1e-3};
private:
char const* filePath_;
::indoors::proto::Recording recording_;
};
#endif /* PROTOLOADER_HPP_ */

69
src/spatial_grid.hpp Normal file
View File

@ -0,0 +1,69 @@
#ifndef SPATIALGRID_HPP_
#define SPATIALGRID_HPP_
#include <array>
#include <cmath>
#include <cstdint>
#include <vector>
template<typename value_t>
class SpatialGrid {
public:
SpatialGrid() = delete;
explicit SpatialGrid(double x_start, double y_start, double x_end, double y_end) :
offset_({static_cast<size_t>(x_start / spacing), static_cast<size_t>(y_start / spacing)}),
size_x_(static_cast<size_t>(std::ceil(x_end / spacing)) - offset_[0]),
size_y_(static_cast<size_t>(std::ceil(y_end / spacing)) - offset_[1]),
grid_(size_x_, std::vector<value_t>(size_y_))
{}
~SpatialGrid() = default;
value_t& at(double x, double y)
{
size_t const id_x{static_cast<size_t>(x / spacing) - offset_[0]};
size_t const id_y{static_cast<size_t>(y / spacing) - offset_[1]};
return grid_[id_x][id_y];
}
value_t& at(std::array<double, 2> pos)
{
return at(pos[0], pos[1]);
}
std::vector<value_t>& operator[](size_t idx)
{
return grid_[idx];
}
size_t size_x() const
{
return grid_.size();
}
size_t size_y() const
{
return grid_[0].size();
}
size_t start_x() const noexcept
{
return offset_[0] * spacing;
}
size_t start_y() const noexcept
{
return offset_[1] * spacing;
}
std::vector<std::vector<value_t>>& data() {
return grid_;
}
static constexpr size_t spacing{5};
private:
size_t const offset_[2];
size_t const size_x_;
size_t const size_y_;
std::vector<std::vector<value_t>> grid_;
};
#endif /* SPATIALGRID_HPP_ */