You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmData/nmLayerGeometry.cpp

159 lines
8.4 KiB
C++

#include "nmLayerGeometry.h"
#include "nmDataLayer.h"
#include <QtCore/qnumeric.h>
#include <cfloat>
nmLayerGeometryRow::nmLayerGeometryRow()
: m_dTopDepth(0.0), m_dBottomDepth(0.0), m_dThickness(0.0), m_bLegacyGap(false)
{
}
bool nmLayerGeometry::fromLayers(const QVector<nmDataLayer*>& layers, nmLayerGeometry& result)
{
// 先收齐所有层再赋给输出,避免某一层为空时留下只复制了一部分的结果。
// 此处只负责快照,不自动修复旧数据;打开窗口不应改变工程中原有的边界。
nmLayerGeometry copy; // 待输出的独立快照,全部层读取成功后才替换 result。
for (int i = 0; i < layers.size(); ++i) {
const nmDataLayer* layer = layers.at(i); // 借用的持久化层对象,本函数不修改或释放它。
if (!layer) return false;
nmLayerGeometryRow row; // 当前层的米制值记录,名称默认值仅补在快照中。
row.m_sName = layer->getName();
// 旧工程缺少名称时先在快照中补齐,插入/删除行后名称不再随行号漂移。
// 当前数据和原始快照采用同样规则比较,因此仅打开再确定不会触发写入。
if (row.m_sName.isEmpty()) row.m_sName = QString("Layer%1").arg(i + 1);
row.m_clrLayer = layer->getColor();
row.m_dTopDepth = layer->getTop();
row.m_dBottomDepth = layer->getBottom();
row.m_dThickness = layer->getThickness();
row.m_bLegacyGap = layer->getIsChecked();
copy.m_vecLayers.append(row);
}
result = copy;
return true;
}
bool nmLayerGeometry::isValid() const
{
if (m_vecLayers.isEmpty()) return false;
double sum = 0.0; // 米制厚度累计值,仅用于检测总厚度是否溢出,不包含间隙。
for (int i = 0; i < m_vecLayers.size(); ++i) {
const nmLayerGeometryRow& row = m_vecLayers.at(i);
if (!qIsFinite(row.m_dTopDepth) || !qIsFinite(row.m_dBottomDepth) || !qIsFinite(row.m_dThickness)
|| row.m_dThickness <= 0.0 || row.m_dBottomDepth <= row.m_dTopDepth) return false;
// 只容忍浮点加减的舍入误差,不能把不一致的厚度和边界当作有效几何提交。
// 按深度量级估计的浮点运算误差,单位 m;不代表业务上允许的层厚偏差。
const double tolerance = 8.0 * DBL_EPSILON
* qMax(1.0, qMax(qAbs(row.m_dTopDepth), qAbs(row.m_dBottomDepth)));
if (qAbs((row.m_dBottomDepth - row.m_dTopDepth) - row.m_dThickness) > tolerance) return false;
if (i > 0 && row.m_dTopDepth < m_vecLayers.at(i - 1).m_dBottomDepth) return false;
sum += row.m_dThickness;
if (!qIsFinite(sum)) return false;
}
// 厚度总和可能有限,但连同较大历史间隙后的整体跨度仍可能溢出。
// 预览按整体跨度计算比例,因此也必须单独检查这个差值。
return qIsFinite(m_vecLayers.last().m_dBottomDepth - m_vecLayers.first().m_dTopDepth);
}
double nmLayerGeometry::totalThickness() const
{
// 不能用末层底深减首层顶深代替求和,否则旧工程的间隙会被算成储层厚度。
double total = 0.0; // 各层自身厚度之和,单位 m,与整体顶底深度跨度不同。
for (int i = 0; i < m_vecLayers.size(); ++i) total += m_vecLayers.at(i).m_dThickness;
return total;
}
bool nmLayerGeometry::equals(const nmLayerGeometry& other, bool geometryOnly) const
{
// 精确比较用于判断是否真的发生修改;容差只属于几何合法性检查,不能掩盖外部编辑。
if (m_vecLayers.size() != other.m_vecLayers.size()) return false;
for (int i = 0; i < m_vecLayers.size(); ++i) {
const nmLayerGeometryRow& a = m_vecLayers.at(i); // 当前快照中同一物理位置的层。
const nmLayerGeometryRow& b = other.m_vecLayers.at(i); // 待比较快照中的对应层,不转移所有权。
if (a.m_dTopDepth != b.m_dTopDepth || a.m_dBottomDepth != b.m_dBottomDepth || a.m_dThickness != b.m_dThickness
|| a.m_bLegacyGap != b.m_bLegacyGap) return false;
if (!geometryOnly && (a.m_sName != b.m_sName || a.m_clrLayer != b.m_clrLayer)) return false;
}
return true;
}
QVector<double> nmLayerGeometry::gaps() const
{
// 每项对应该层“上方”的实际距离;m_bLegacyGap 则描述该层“下方”是否分离。
// 两者方向不同,重排后由 relayout() 统一恢复旧格式标志。
QVector<double> result(m_vecLayers.size(), 0.0); // 各层上方间距,单位 m;首项无上一层,固定为零。
for (int i = 1; i < m_vecLayers.size(); ++i)
result[i] = m_vecLayers.at(i).m_dTopDepth - m_vecLayers.at(i - 1).m_dBottomDepth;
return result;
}
bool nmLayerGeometry::relayout(double firstTop, const QVector<double>& offsets)
{
// 按层顺序递推,最后统一验证;公开修改方法始终在临时副本上调用此函数。
// 即使中途遇到负间距、无效厚度或加法溢出,调用方的原几何也不会被部分改写。
if (m_vecLayers.isEmpty() || offsets.size() != m_vecLayers.size() || !qIsFinite(firstTop)) return false;
double bottom = firstTop; // 递推中上一层的米制底深,首轮以指定顶深作为起点。
for (int i = 0; i < m_vecLayers.size(); ++i) {
if (!qIsFinite(offsets.at(i)) || offsets.at(i) < 0.0) return false;
m_vecLayers[i].m_dTopDepth = i == 0 ? firstTop : bottom + offsets.at(i);
m_vecLayers[i].m_dBottomDepth = m_vecLayers.at(i).m_dTopDepth + m_vecLayers.at(i).m_dThickness;
// 发生几何编辑后同步旧格式标志,使旧版读取程序仍能识别实际间隙。
m_vecLayers[i].m_bLegacyGap = i + 1 < offsets.size() && offsets.at(i + 1) > 0.0;
bottom = m_vecLayers.at(i).m_dBottomDepth;
}
return isValid();
}
bool nmLayerGeometry::setFirstTop(double top)
{
if (m_vecLayers.isEmpty()) return false;
// 相同输入不重算,避免无操作时反复加减产生新的浮点舍入误差。
if (top == m_vecLayers.first().m_dTopDepth) return isValid();
nmLayerGeometry copy(*this); // 修改顶深的试算副本,重排成功后再整体替换自身。
if (!copy.relayout(top, gaps())) return false;
*this = copy;
return true;
}
bool nmLayerGeometry::setThickness(int row, double thickness)
{
if (row < 0 || row >= m_vecLayers.size() || !qIsFinite(thickness) || thickness <= 0.0) return false;
// 原值未变且已有几何合法时直接完成;旧边界不一致时仍允许通过重排尝试修复。
if (thickness == m_vecLayers.at(row).m_dThickness && isValid()) return true;
nmLayerGeometry copy(*this); // 修改厚度的试算副本,避免后续边界溢出时留下部分修改。
copy.m_vecLayers[row].m_dThickness = thickness;
if (!copy.relayout(m_vecLayers.first().m_dTopDepth, gaps())) return false;
*this = copy;
return true;
}
bool nmLayerGeometry::insertLayer(int row, const QString& name, double thickness, const QColor& color)
{
if (row < 0 || row > m_vecLayers.size() || !isValid()) return false;
nmLayerGeometry copy(*this); // 插入操作的候选几何,合法性检查通过前不影响原层列表。
QVector<double> offsets = gaps(); // 按层索引排列的米制上方间距,随新层插入同步移位。
// 新层上方间距为零;原插入位置那层的历史间距随其一起后移,不被新层吞掉。
offsets.insert(row, 0.0);
nmLayerGeometryRow layer; // 待插入层;名称、厚度和颜色先赋值,顶底深度交给重排计算。
layer.m_sName = name;
layer.m_dThickness = thickness;
layer.m_clrLayer = color;
copy.m_vecLayers.insert(row, layer);
if (!copy.relayout(m_vecLayers.first().m_dTopDepth, offsets)) return false;
*this = copy;
return true;
}
bool nmLayerGeometry::removeLayer(int row)
{
if (m_vecLayers.size() <= 1 || row < 0 || row >= m_vecLayers.size()) return false;
nmLayerGeometry copy(*this); // 删除操作的候选几何,保证失败时原层列表不丢数据。
QVector<double> offsets = gaps(); // 删除前的米制上方间距,合并相邻间隙后与剩余层重新对齐。
// 删除中间层时保留两侧原有间隙;删除首层后仍从原首层顶深开始。
if (row > 0 && row + 1 < offsets.size()) offsets[row + 1] += offsets.at(row);
offsets.remove(row);
copy.m_vecLayers.remove(row);
if (!copy.relayout(m_vecLayers.first().m_dTopDepth, offsets)) return false;
*this = copy;
return true;
}