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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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;
}