Adds CPP implementatinon
This commit is contained in:
parent
f6e691d3db
commit
c72e925ac2
@ -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)
|
||||
|
||||
|
@ -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
70
src/calc.cpp
Normal 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
47
src/calc.hpp
Normal 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
31
src/data.hpp
Normal 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
22
src/json_output.cpp
Normal 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
24
src/json_output.hpp
Normal 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_
|
123
src/main.cpp
123
src/main.cpp
@ -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
61
src/proto_loader.cpp
Normal 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
24
src/proto_loader.hpp
Normal 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
69
src/spatial_grid.hpp
Normal 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_ */
|
Loading…
Reference in New Issue
Block a user