Skip to content

File config_input.cpp

File List > config_input > config_input.cpp

Go to the documentation of this file

#include "config_input.hpp"

#include <fstream>

#include "assert/assert.hpp"

#define JSON_DIAGNOSTICS 1
#ifdef _MSC_VER
#pragma warning(push, 0)
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#include <nlohmann/json.hpp>
#pragma GCC diagnostic pop
#ifdef _MSC_VER
#pragma warning(pop)
#endif

using json = nlohmann::json;

#define SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...)                                                    \
  template <typename BasicJsonType>                                                              \
  inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) {                                    \
    static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");               \
    static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                          \
    auto it = std::find_if(std::begin(m), std::end(m),                                           \
                           [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool {     \
                             return ej_pair.first == e;                                          \
                           });                                                                   \
    if (it == std::end(m)) throw std::invalid_argument("unknown enum value");                    \
    j = it->second;                                                                              \
  }                                                                                              \
  template <typename BasicJsonType>                                                              \
  inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) {                                  \
    static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");               \
    static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                          \
    auto it = std::find_if(std::begin(m), std::end(m),                                           \
                           [&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool {    \
                             return ej_pair.second == j;                                         \
                           });                                                                   \
    if (it == std::end(m)) throw std::invalid_argument("unknown json value: " + std::string(j)); \
    e = it->first;                                                                               \
  }

namespace nlohmann {

template <>
struct adl_serializer<GridConfig> {
  static GridConfig from_json(const json& j) {
    return GridConfig{j.value("bin_resolution", 1.0), j.value("downsample_factor", 3u)};
  }

  static void to_json(json& j, GridConfig gc) {
    j["bin_resolution"] = gc.bin_resolution;
    j["downsample_factor"] = gc.downsample_factor;
  }
};

template <>
struct adl_serializer<GroundConfig> {
  static GroundConfig from_json(const json& j) {
    return GroundConfig{j.value("outlier_removal_height_diff", 1.0),
                        j.value("min_ground_intensity", 100),
                        j.value("max_ground_intensity", 1000)};
  }

  static void to_json(json& j, GroundConfig gc) {
    j["outlier_removal_height_diff"] = gc.outlier_removal_height_diff;
    j["min_ground_intensity"] = gc.min_ground_intensity;
    j["max_ground_intensity"] = gc.max_ground_intensity;
  }
};

template <>
struct adl_serializer<RGBColor> {
  static RGBColor from_json(const json& j) { return RGBColor(j[0], j[1], j[2], j[3]); }

  static void to_json(json& j, RGBColor c) {
    j = {c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()};
  }
};

template <>
struct adl_serializer<CMYKColor> {
  static CMYKColor from_json(const json& j) { return CMYKColor(j[0], j[1], j[2], j[3]); }

  static void to_json(json& j, CMYKColor c) {
    j = {c.getCyan(), c.getMagenta(), c.getYellow(), c.getBlack()};
  }
};

template <>
struct adl_serializer<ColorVariant> {
  static ColorVariant from_json(const json& j) {
    if (j.is_string()) {
      return COLOR_MAP.at(j.get<std::string>());
    } else {
      const auto& color = j.items().begin();
      if (color.key() == "rgb") {
        return color.value().get<RGBColor>();
      } else {
        Assert(color.key() == "cmyk", "Invalid color type");
        return color.value().get<CMYKColor>();
      }
    }
  }

  static void to_json(json& j, ColorVariant cv) {
    if (std::holds_alternative<RGBColor>(cv)) {
      j = json({{"rgb", std::get<RGBColor>(cv)}});
    } else {
      j = json({{"cmyk", std::get<CMYKColor>(cv)}});
    }
  }
};

template <>
struct adl_serializer<ContourConfig> {
  static ContourConfig from_json(const json& j) {
    return ContourConfig{j.value("interval", 1.0), j.value("min_points", 5u),
                         j.value("color", json("brown")).get<ColorVariant>(),
                         j.value("width", 0.14)};
  }

  static void to_json(json& j, ContourConfig cc) {
    j["interval"] = cc.interval;
    j["min_points"] = cc.min_points;
    j["color"] = cc.color;
    j["width"] = cc.width;
  }
};

template <>
struct adl_serializer<CanopyConfig> {
  static CanopyConfig from_json(const json& j) {
    return CanopyConfig{j.value("min_height", 2.5), j.value("max_height", 100.0),
                        j.value("blocking_threshold", 0.1)};
  }

  static void to_json(json& j, CanopyConfig vc) {
    j["min_height"] = vc.min_height;
    j["max_height"] = vc.max_height;
    j["blocking_threshold"] = vc.blocking_threshold;
  }
};

template <>
struct adl_serializer<BlockingThresholdColorPair> {
  static BlockingThresholdColorPair from_json(const json& j) {
    return BlockingThresholdColorPair{j.value("blocking_threshold", 0.1),
                                      j.value("color", json("white")).get<ColorVariant>()};
  }

  static void to_json(json& j, BlockingThresholdColorPair btc) {
    j["blocking_threshold"] = btc.blocking_threshold;
    j["color"] = btc.color;
  }
};
template <>
struct adl_serializer<VegeHeightConfig> {
  static VegeHeightConfig from_json(const json& j) {
    return VegeHeightConfig{
        j.value("name", "Vegetation"), j.value("min_height", 2.5), j.value("max_height", 100.0),
        j.value("colors", json({})).get<std::vector<BlockingThresholdColorPair>>()};
  }

  static void to_json(json& j, VegeHeightConfig vhc) {
    j["name"] = vhc.name;
    j["min_height"] = vhc.min_height;
    j["max_height"] = vhc.max_height;
    j["colors"] = vhc.colors;
  }
};

template <>
struct adl_serializer<VegeConfig> {
  static VegeConfig from_json(const json& j) {
    return VegeConfig{j.value("background_color", json("white")).get<ColorVariant>(),
                      j.value("height_configs", json({})).get<std::vector<VegeHeightConfig>>()};
  }

  static void to_json(json& j, VegeConfig vc) {
    j["background_color"] = vc.background_color;
    j["height_configs"] = vc.height_configs;
  }
};
template <>
struct adl_serializer<RenderConfig> {
  static RenderConfig from_json(const json& j) {
    return RenderConfig{j.value("scale", 10000.0), j.value("dpi", 600.0)};
  }

  static void to_json(json& j, RenderConfig rc) {
    j["scale"] = rc.scale;
    j["dpi"] = rc.dpi;
  }
};

template <>
struct adl_serializer<WaterConfig> {
  static WaterConfig from_json(const json& j) {
    return WaterConfig{.catchment = j.value("catchment", 0.05),
                       .color = j.value("color", json("blue")),
                       .width = j.value("width", 0.18)};
  }

  static void to_json(json& j, WaterConfig cc) {
    j["catchment"] = cc.catchment;
    j["color"] = cc.color;
    j["width"] = cc.width;
  }
};

template <>
struct adl_serializer<WaterConfigs> {
  static WaterConfigs from_json(const json& j) {
    return WaterConfigs{j.get<std::map<std::string, WaterConfig>>()};
  }

  static void to_json(json& j, WaterConfigs cc) { j = cc.configs; }
};

template <>
struct adl_serializer<ContourConfigs> {
  static ContourConfigs from_json(const json& j) {
    return ContourConfigs(j.get<std::map<std::string, ContourConfig>>());
  }

  static void to_json(json& j, ContourConfigs cc) { j = cc.configs; }
};

template <>
struct adl_serializer<BuildingsConfig> {
  static BuildingsConfig from_json(const json& j) {
    return BuildingsConfig{j.value("color", json("black")).get<ColorVariant>()};
  }

  static void to_json(json& j, BuildingsConfig bc) { j["color"] = bc.color; }
};

SERIALIZE_ENUM_STRICT(ProcessingStep, {
                                          {ProcessingStep::Tiles, "tiles"},
                                          {ProcessingStep::Combine, "combine"},
                                      })

template <>
struct adl_serializer<Config> {
  static Config from_json(const json& j) {
    Config config;
    config.grid = j.value("grid", json({})).get<GridConfig>();
    config.ground = j.value("ground", json({})).get<GroundConfig>();
    config.contours = j.value("contours", json({})).get<ContourConfigs>();
    config.water = j.value("water", json({})).get<WaterConfigs>();
    config.vege = j.value("vege", json({})).get<VegeConfig>();
    config.render = j.value("render", json({})).get<RenderConfig>();
    config.buildings = j.value("buildings", json({})).get<BuildingsConfig>();
    config.las_files =
        j.value("las_files", json(std::vector<std::string>())).get<std::vector<fs::path>>();
    config.processing_steps = j.value("steps", json({"tiles"})).get<std::set<ProcessingStep>>();
    config.output_directory = j.value("output_directory", "out");
    config.border_width = j.value("border_width", 100.0);
    config.relative_path_to_config = "";
    return config;
  }

  static void to_json(json& j, const Config& gc) {
    j["grid"] = gc.grid;
    j["ground"] = gc.ground;
    j["contours"] = gc.contours;
    j["water"] = gc.water;
    j["vege"] = gc.vege;
    j["render"] = gc.render;
    j["colors"] = json({{"primitive", COLOR_MAP}});
    j["buildings"] = gc.buildings;
    j["las_files"] = gc.las_files;
    j["steps"] = gc.processing_steps;
    j["output_directory"] = gc.output_directory;
    j["border_width"] = gc.border_width;
  }
};

}  // namespace nlohmann

std::ostream& operator<<(std::ostream& os, const Config& config) {
  os << "Config:\n";
  os << json(config).dump(4);
  return os;
}

void Config::write_to_file(const fs::path& filename) const {
  std::ofstream file(filename);
  if (!file.is_open()) {
    std::cerr << "Failed to open config file " << filename << std::endl;
    throw std::filesystem::filesystem_error("Failed to open config file " + filename.string(),
                                            std::make_error_code(std::errc::io_error));
  }
  json j = *this;
  j.erase("relative_path_to_config");
  file << j.dump(4);
  file.close();
}

Config Config::FromFile(const fs::path& filename) {
  std::ifstream file(filename);
  if (!file.is_open()) {
    std::cerr << "Failed to open config file " << filename << std::endl;
    throw std::filesystem::filesystem_error("Failed to open config file " + filename.string(),
                                            std::make_error_code(std::errc::io_error));
  }
  json j;
  try {
    j = json::parse(file, nullptr, true, true);
  } catch (const json::exception& e) {
    std::cerr << "JSON parsing error: " << e.what() << "\n";
    throw e;
  }
  file.close();
  for (const auto& color : j["colors"]["primitive"].items()) {
    COLOR_MAP[color.key()] = color.value().get<ColorVariant>();
  }
  for (const auto& color : j["colors"]["composite"].items()) {
    CMYKColor composite;
    for (const auto& component : color.value().items()) {
      composite =
          composite + to_cmyk(COLOR_MAP.at(component.key())) * component.value().get<double>();
    }
    COLOR_MAP[color.key()] = composite;
  }
  Config c = j.get<Config>();
  c.relative_path_to_config = filename.parent_path();
  return c;
}

std::ostream& operator<<(std::ostream& os, const ProcessingStep& step) { return os << json(step); }