/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see . *
* *
********************************************************************************/
#include "geolocation.h"
#include "geolocation_transform.h"
#include "../ifcparse/exception.h"
#include "../ifcparse/file.h"
#include "../ifcparse/instance_data.h"
#include "placement.h"
#include "pset.h"
#include "schema_dispatch.i"
#include
#include
#include
#include
namespace {
template
struct is_optional : std::false_type {};
template
struct is_optional> : std::true_type {};
template
struct is_ifc4_or_higher : std::false_type {};
template
struct is_ifc4_or_higher> : std::true_type {};
template
struct has_map_conversion_scaled : std::false_type {};
template
struct has_map_conversion_scaled> : std::true_type {};
template
struct has_factor_x : std::false_type {};
template
struct has_factor_x().FactorX())>> : std::true_type {};
template
struct has_rigid_operation : std::false_type {};
template
struct has_rigid_operation> : std::true_type {};
[[noreturn]] void unsupported_schema(const std::string& name) {
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
}
double numeric_property(const property_map& properties,
const std::string& name,
double fallback) {
const auto found = properties.find(name);
if (found == properties.end()) {
return fallback;
}
if (const auto value = found->second.get_if()) {
return *value;
}
if (const auto value = found->second.get_if()) {
return static_cast(*value);
}
return fallback;
}
double selected_number(const express::base& selected, double fallback) {
if (!selected) {
return fallback;
}
const auto value = selected.get_attribute_value(0);
if (value.isNull()) {
return fallback;
}
if (value.type() == ifcopenshell::Argument_DOUBLE) {
return static_cast(value);
}
if (value.type() == ifcopenshell::Argument_INT) {
return static_cast(static_cast(value));
}
return fallback;
}
template
double optional_number(const T& value, double fallback) {
if constexpr (is_optional::value) {
return value.value_or(fallback);
} else {
return value;
}
}
template
std::optional get_helmert_transformation_parameters_s(ifcopenshell::file* ifc_file) {
HelmertTransformation result;
if constexpr (!is_ifc4_or_higher::value) {
const auto projects = ifc_file->template instances_by_type();
if (projects.empty()) {
return std::nullopt;
}
const auto conversion = get_pset(projects.front(), "ePSet_MapConversion");
if (!conversion) {
return std::nullopt;
}
const auto* properties = conversion->template get_if();
if (!properties) {
return std::nullopt;
}
result.e = numeric_property(*properties, "Eastings", 0.0);
result.n = numeric_property(*properties, "Northings", 0.0);
result.h = numeric_property(*properties, "OrthogonalHeight", 0.0);
result.xaa = numeric_property(*properties, "XAxisAbscissa", 0.0);
result.xao = numeric_property(*properties, "XAxisOrdinate", 0.0);
result.scale = numeric_property(*properties, "Scale", 1.0);
} else {
const auto conversions =
ifc_file->template instances_by_type();
if (conversions.empty()) {
return std::nullopt;
}
const auto& conversion = conversions.front();
if (auto map_conversion = conversion.template as()) {
result.e = map_conversion.Eastings();
result.n = map_conversion.Northings();
result.h = map_conversion.OrthogonalHeight();
result.xaa = map_conversion.XAxisAbscissa().value_or(0.0);
result.xao = map_conversion.XAxisOrdinate().value_or(0.0);
result.scale = map_conversion.Scale().value_or(1.0);
if constexpr (has_map_conversion_scaled::value) {
if (auto scaled = conversion.template as()) {
if constexpr (has_factor_x::value) {
result.factor_x = scaled.FactorX();
result.factor_y = scaled.FactorY();
result.factor_z = scaled.FactorZ();
} else {
result.factor_x = scaled.ScaleX();
result.factor_y = scaled.ScaleY();
result.factor_z = scaled.ScaleZ();
}
}
}
} else if constexpr (has_rigid_operation::value) {
if (auto rigid = conversion.template as()) {
result.e = selected_number(rigid.FirstCoordinate().concrete(), 0.0);
result.n = selected_number(rigid.SecondCoordinate().concrete(), 0.0);
result.h = optional_number(rigid.Height(), 0.0);
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
if (result.scale == 0.0) {
result.scale = 1.0;
}
if (result.xaa == 0.0 && result.xao == 0.0) {
result.xaa = 1.0;
}
return result;
}
template
std::optional get_wcs_s(ifcopenshell::file* ifc_file) {
const auto contexts =
ifc_file->template instances_by_type_excl_subtypes();
express::base wcs;
for (const auto& context : contexts) {
const auto placement = context.WorldCoordinateSystem();
if (!placement) {
continue;
}
wcs = placement.concrete();
if (context.ContextType() == std::optional("Model")) {
break;
}
}
if (!wcs) {
return std::nullopt;
}
return get_axis2_placement(wcs);
}
template
std::optional get_map_unit_s(ifcopenshell::file* ifc_file) {
if constexpr (!is_ifc4_or_higher::value) {
return std::nullopt;
} else {
const auto operations =
ifc_file->template instances_by_type();
if (operations.empty()) {
return std::nullopt;
}
const auto target = operations.front().TargetCRS();
const auto projected = target.template as();
if (!projected) {
return std::nullopt;
}
const auto unit = projected.MapUnit();
if (!unit) {
return std::nullopt;
}
return unit;
}
}
} // namespace
std::optional
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_helmert_transformation_parameters_s(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional get_wcs(ifcopenshell::file* ifc_file) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_wcs_s(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
const Eigen::Matrix4d& matrix,
bool should_return_in_map_units) {
auto params = get_helmert_transformation_parameters(ifc_file);
if (!params) {
return matrix;
}
Eigen::Matrix4d m = matrix;
if (auto wcs = get_wcs(ifc_file)) {
m = wcs->inverse() * m;
}
Eigen::Matrix4d result = local_to_global(m, *params);
if (!should_return_in_map_units) {
result(0, 3) /= params->scale;
result(1, 3) /= params->scale;
result(2, 3) /= params->scale;
}
return result;
}
std::optional get_map_unit(ifcopenshell::file* ifc_file) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_map_unit_s(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}