Files
qt5-legatus-legacy/Lts/Plugins/PlanEditor/geometry_placer.h
T
2026-08-13 18:33:10 +02:00

500 lines
17 KiB
C++
Executable File

/**
**
** @RDE_HEADER@
** Class to translate, scale and rotate geoemtry.
** $Id$
** $HeadURL$
** $Author$
**
** -----------------------------------------------------------------------------------------------
**
** Hardware-Environment: Intel-PC
** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5
**
** Language ........: C++ 11
**
** -----------------------------------------------------------------------------------------------
**
** Author ...........: Jörg Turla, SPD G, Tel. 3132
**
** -----------------------------------------------------------------------------------------------
**
** Notes .........:
**
** 01.07.2016, JT : First edition for use with Qt5
**
** -----------------------------------------------------------------------------------------------
*/
#pragma once
#include "plan_editor.h"
#include "logging.h"
#include <Map/situation_map_types.h>
#include <Map/coord_translation_functor.h>
#include <Map/map.h>
#include <Map/geometry_model_renderer.h>
#include <Rubberbands/rubberband_factory.h>
#include <GeometryModel/geometry_model_factory.h>
#include <GeometryModel/geometry_node_visitor.h>
#include <MathUtils/math_const.h>
#include <MathUtils/simple_helper.h>
#include <MathUtils/matrix2d.h>
#include <QtCore/QList>
#include <QtWidgets/QGraphicsView>
#include <stdexcept>
namespace SituationMap
{
class Map;
class GeometryModel;
namespace RB { class RubberbandTransform; }
} // namespace SituationMap
namespace PlanEditor
{
const unsigned int RubberbandMinPixelSize = 20;
template<class RubberbandClass> class GeometryPlacer : public QObject
{
public:
static RubberbandClass *create(PlanEditor *planEditor
, SituationMap::Map *map
, SituationMap::GeometryModel *geoModel
, const QList<SituationMap::UniqueMapId> &uidList
, QObject *parent);
bool isValid() const { return (m_previewGeoModel != nullptr); }
void changeStyleAttributes();
private:
GeometryPlacer(PlanEditor *planEditor, SituationMap::Map *map, SituationMap::GeometryModel *, const QList<SituationMap::UniqueMapId> &, QObject *parent);
virtual ~GeometryPlacer();
void applyTransform(SituationMap::GeometryModel *geoModel);
void abortTransform();
void previewTransform(SituationMap::Map *map, const SituationMap::MathUtils::Matrix2D &mat);
void previewTransform(SituationMap::Map *map, const SituationMap::MathUtils::Matrix2D &mat, const SituationMap::GeometryModel::Node &node);
void adjustEllipse(SituationMap::GeometryModel *geoModel, const SituationMap::GeometryModel::Node &p, const QList<QPointF> &v);
PlanEditor *m_planEditor;
QPointer<SituationMap::Map> m_map;
SituationMap::GeometryModel *m_previewGeoModel;
SituationMap::GeometryModelRenderer *m_previewGeoModelRenderer;
QList<SituationMap::UniqueMapId> m_uidList;
RubberbandClass *m_rubberband;
Logger &m_logger;
}; // template<class RubberbandClass> class GeometryPlacer
template<class RubberbandClass> RubberbandClass *GeometryPlacer<RubberbandClass>::create(PlanEditor *planEditor
, SituationMap::Map *map
, SituationMap::GeometryModel *geoModel
, const QList<SituationMap::UniqueMapId> &uidList
, QObject *parent)
{
if (!std::is_base_of<SituationMap::RB::RubberbandTransform, RubberbandClass>::value) return nullptr;
GeometryPlacer<RubberbandClass> *gP = new GeometryPlacer<RubberbandClass>(planEditor, map, geoModel, uidList, parent);
if (gP == nullptr) return nullptr;
if (!gP->isValid())
{
delete gP;
gP = nullptr;
return nullptr;
}
return gP->m_rubberband;
} // RubberbandClass * GeometryPlacer<RubberbandClass>::create()
static const char *const PreviewLayer = "PlanEditorPreview";
template<class RubberbandClass> GeometryPlacer<RubberbandClass>::GeometryPlacer(PlanEditor *planEditor
, SituationMap::Map *map
, SituationMap::GeometryModel *geoModel
, const QList<SituationMap::UniqueMapId> &uidList
, QObject *parent)
: m_planEditor(planEditor)
, m_map(map)
, m_previewGeoModel(nullptr)
, m_previewGeoModelRenderer(nullptr)
, m_uidList()
, m_rubberband(nullptr)
, m_logger(getLogger("PlanEditor-GeometryPlacer"))
{
if (uidList.isEmpty()) return;
// first get bounding box of all selected features
m_previewGeoModel = SituationMap::GeometryModelFactory::singleton()->create();
qreal xmin = std::numeric_limits<qreal>::infinity();
qreal xmax = -std::numeric_limits<qreal>::infinity();
qreal ymin = std::numeric_limits<qreal>::infinity();
qreal ymax = -std::numeric_limits<qreal>::infinity();
m_uidList.reserve(uidList.size());
for (SituationMap::UniqueMapId uid : uidList)
{
const SituationMap::GeometryModel::Node &modelPrimitive = geoModel->findPrimitive(uid);
if (modelPrimitive.isValid())
{
const QList<QPointF> &vertexList = geoModel->getPrimitivePoints(modelPrimitive);
for (const QPointF &p : vertexList )
{
if (p.x() < xmin) xmin = p.x();
if (p.x() > xmax) xmax = p.x();
if (p.y() < ymin) ymin = p.y();
if (p.y() > ymax) ymax = p.y();
}
if (!vertexList.empty())
{
m_uidList << uid;
m_previewGeoModel->copy(modelPrimitive);
}
}
}
if (m_uidList.isEmpty())
{
WARNING_LOG(m_logger) << LOGFUNCTION << "Object list of GeometryPlacer is empty.";
return;
}
if ((!std::isfinite(xmin)) && (!std::isfinite(ymin)) && (!std::isfinite(xmax)) && (!std::isfinite(ymax)))
{
// Huch, oh, oh, oh
// This should not happen, can't get a valid bounding box
ERROR_LOG(m_logger) << LOGFUNCTION << "Can't determine bounding box of selected features";
#ifdef LTS_DEBUG
// We want to find the reason why something is going wrong here. Let's go on with
// debugging here. Do it only in case of LTS_DEBUG is defined. Keep in mind that
// NDEBUG is all ways defined because we compile release with debug information for
// debugging too. As result the assert macro doesn't work here.
throw std::range_error("Infinte bounding box, abort GeometryPlacer"); // Juhu, let's crash here.
#else
return;
#endif
}
previewTransform(map, SituationMap::MathUtils::Matrix2D());
QPointF minCorner(xmin, ymin);
QPointF maxCorner(xmax, ymax);
minCorner = map->geoCoordinateToViewPortWindowPoint(minCorner);
maxCorner = map->geoCoordinateToViewPortWindowPoint(maxCorner);
qreal pixelWidth = maxCorner.x() - minCorner.x();
qreal pixelHeight = maxCorner.y() - minCorner.y();
bool isMinSize = false;
if (fabs(pixelWidth) < RubberbandMinPixelSize)
{
qreal centerX = (minCorner.x() + maxCorner.x()) * 0.5;
qreal dx = RubberbandMinPixelSize * 0.5;
dx = (pixelWidth < 0) ? -dx : dx;
minCorner.setX(centerX - dx);
maxCorner.setX(centerX + dx);
isMinSize = true;
}
if (fabs(pixelHeight) < RubberbandMinPixelSize)
{
qreal centerY = (minCorner.y() + maxCorner.y()) * 0.5;
qreal dy = RubberbandMinPixelSize * 0.5;
dy = (pixelHeight < 0) ? -dy : dy;
minCorner.setY(centerY - dy);
maxCorner.setY(centerY + dy);
isMinSize = true;
}
if (isMinSize)
{
minCorner = map->viewPortWindowPointToGeoCoordinate(minCorner);
maxCorner = map->viewPortWindowPointToGeoCoordinate(maxCorner);
xmin = minCorner.x();
ymin = minCorner.y();
xmax = maxCorner.x();
ymax = maxCorner.y();
}
m_rubberband = dynamic_cast<RubberbandClass *>(map->rubberbandFactory().create<RubberbandClass>(parent));
if (m_rubberband)
{
map->layerFactory().addOverlayLayer(QLatin1String(PreviewLayer), QLatin1String(PreviewLayer));
m_previewGeoModelRenderer = map->createGeometryModelRenderer(QLatin1String(PreviewLayer), m_previewGeoModel);
m_rubberband->setAutoDelete(true);
QVector<QPointF> vertexVector(4);
vertexVector[0] = QPointF(xmin, ymin);
vertexVector[1] = QPointF(xmax, ymin);
vertexVector[2] = QPointF(xmax, ymax);
vertexVector[3] = QPointF(xmin, ymax);
m_rubberband->rectangleVerticies(vertexVector);
QObject::connect(m_rubberband, &SituationMap::RB::Rubberband::apply, this, [this, geoModel]() { applyTransform(geoModel); });
QObject::connect(m_rubberband, &SituationMap::RB::Rubberband::abort, this, [this]() { abortTransform(); });
QObject::connect(m_rubberband, &SituationMap::RB::Rubberband::finished, this, [this]() { delete this; });
QObject::connect(m_rubberband, &SituationMap::RB::RubberbandTransform::updatePreview
, this, [this, map](SituationMap::RB::Rubberband *, const SituationMap::MathUtils::Matrix2D & mat) { previewTransform(map, mat); });
m_rubberband->setUserData(this);
}
else
{
delete m_previewGeoModel;
m_previewGeoModel = nullptr;
}
} // template<class RubberbandClass> GeometryPlacer::GeometryPlayer( SituationMap::GeometryModel & )
class ChangeStyleVisitor : public SituationMap::GeometryNodeVisitor
{
PlanEditor *m_planEditor;
bool apply(SituationMap::GeometryModel *, const SituationMap::GeometryModel::Node &node, SituationMap::GeometryModel::PrimitiveType nodeType) override
{
switch (nodeType)
{
case SituationMap::GeometryModel::PrimitiveType::MapDefaults : break;
case SituationMap::GeometryModel::PrimitiveType::Node : break;
case SituationMap::GeometryModel::PrimitiveType::RootNode : break;
case SituationMap::GeometryModel::PrimitiveType::Unknown : break;
default: m_planEditor->addStyleAttributes(node, false);
}
return true;
} // bool apply( SituationMap::GeometryModel * geoModel, const SituationMap::GeometryModel::Node & node, SituationMap::GeometryModel::PrimitiveType nodeType)
public:
ChangeStyleVisitor(PlanEditor *planEditor, SituationMap::GeometryModel *model)
: SituationMap::GeometryNodeVisitor(model), m_planEditor(planEditor)
{;}
~ChangeStyleVisitor() = default;
}; // ChangeStyleVisitor
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::adjustEllipse(SituationMap::GeometryModel *geoModel, const SituationMap::GeometryModel::Node &p, const QList<QPointF> &v)
{
auto distance = [](const QPointF & v0, const QPointF & v1) -> double
{
QPointF dv = v1 - v0;
return sqrt(dv.x() * dv.x() + dv.y() * dv.y());
};
SituationMap::CoordTranslationFunctor *proj = m_map->coordTranslationFunctor();
QPointF centerPoint(v.first());
for (int i = 1; i < 4; ++i) centerPoint += v.at(i);
centerPoint /= 4;
const QPointF &v0 = (proj) ? proj->geographicToMap(v[0]) : v[0]; // ToDo: optimize calculation here
const QPointF &v1 = (proj) ? proj->geographicToMap(v[1]) : v[1];
const QPointF &v2 = (proj) ? proj->geographicToMap(v[2]) : v[2];
double diameterX = distance(v0, v1);
double diameterY = distance(v1, v2);
double phiSin = (v1.y() - v0.y()) / diameterX;
double phiCos = (v1.x() - v0.x()) / diameterX;
double phi = SituationMap::MathUtils::getMathAngle(QPointF(phiCos, phiSin)) * 180.0 / MathConst::pi;
geoModel->setPrimitiveAttribute(p, SituationMap::GeometryModel::Attribute::Center, QVariant(centerPoint));
geoModel->setPrimitiveAttribute(p, SituationMap::GeometryModel::Attribute::Width, QVariant(diameterX));
geoModel->setPrimitiveAttribute(p, SituationMap::GeometryModel::Attribute::Height, QVariant(diameterY));
geoModel->setPrimitiveAttribute(p, SituationMap::GeometryModel::Attribute::Rotate, QVariant(phi));
}; // adjustEllipse
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::changeStyleAttributes()
{
ChangeStyleVisitor doChangeStyle(m_planEditor, m_previewGeoModel);
doChangeStyle.traverse();
m_previewGeoModelRenderer->render();
m_map->getQGraphicsView()->viewport()->update(); // onUpdateView();
} // template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::changeStyleAttributes(const QString & undoToken)
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::applyTransform(SituationMap::GeometryModel *geoModel)
{
SituationMap::CoordTranslationFunctor *proj = m_map->coordTranslationFunctor();
geoModel->startUndoBlock();
{
const SituationMap::MathUtils::Matrix2D &mat = m_rubberband->transformMatrix();
for (SituationMap::UniqueMapId uid : qAsConst(m_uidList))
{
const SituationMap::GeometryModel::Node &modelPrimitive = geoModel->findPrimitive(uid);
if (modelPrimitive.isValid())
{
QList<QPointF> vertexList = m_previewGeoModel->getPrimitivePoints(modelPrimitive);
QList<QPointF>::iterator vit = vertexList.begin();
if (proj)
{
for (; vit != vertexList.end(); ++vit)
{
const QPointF &pt = proj->geographicToMap(*vit);
*vit = proj->mapToGeographic(mat.apply(pt));
}
}
else
{
for (; vit != vertexList.end(); ++vit)
*vit = mat.apply(*vit);
}
geoModel->setPrimitivePoints(modelPrimitive, vertexList);
if ((geoModel->getType(modelPrimitive) == SituationMap::GeometryModel::PrimitiveType::Ellipse) && (vertexList.size() == 4))
adjustEllipse(geoModel, modelPrimitive, vertexList);
}
}
}
geoModel->endUndoBlock(QObject::tr("apply transform geometry", TR_UNDO_TOKEN));
delete this;
} // template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::applyTransform( SituationMap::GeometryModel * geoModel )
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::abortTransform()
{
delete this;
} // template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::abortTransform()
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::previewTransform(SituationMap::Map *map, const SituationMap::MathUtils::Matrix2D &mat)
{
if (mat.isIdentity()) return;
previewTransform(map, mat, m_previewGeoModel->getRootNode());
m_previewGeoModelRenderer->render();
map->getQGraphicsView()->viewport()->update(); // onUpdateView();
} // template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::previewTransform( const SituationMap::MathUtils::Matrix2D & mat )
template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::previewTransform(SituationMap::Map *map
, const SituationMap::MathUtils::Matrix2D &mat
, const SituationMap::GeometryModel::Node &node)
{
if (mat.isIdentity()) return;
SituationMap::GeometryModel::Node n(node);
SituationMap::CoordTranslationFunctor *proj = m_map->coordTranslationFunctor();
while (n.isValid())
{
SituationMap::GeometryModel::PrimitiveType primitiveType = m_previewGeoModel->getType(n);
if (n.hasChildNodes() && ((primitiveType == SituationMap::GeometryModel::PrimitiveType::RootNode) || (primitiveType == SituationMap::GeometryModel::PrimitiveType::Node)))
previewTransform(map, mat, n.firstChildNode());
else
{
QList<QPointF> vertexList = m_previewGeoModel->getPrimitivePoints(n);
if (!vertexList.isEmpty())
{
QList<QPointF>::iterator vit = vertexList.begin();
if (proj)
{
for (; vit != vertexList.end(); ++vit)
{
const QPointF &pt = proj->geographicToMap(*vit);
*vit = proj->mapToGeographic(mat.apply(pt));
}
}
else
{
for (; vit != vertexList.end(); ++vit)
*vit = mat.apply(*vit);
}
m_previewGeoModel->setPrimitivePoints(n, vertexList);
if ((m_previewGeoModel->getType(n) == SituationMap::GeometryModel::PrimitiveType::Ellipse) && (vertexList.size() == 4))
adjustEllipse(m_previewGeoModel, n, vertexList);
}
}
n = n.next();
}
} // template<class RubberbandClass> void GeometryPlacer<RubberbandClass>::previewTransform( SitautionMap::Map *, const SituationMap::MathUtils::Matrix2D &, SituationMap::GeometryModel::Node & )
template<class RubberbandClass> GeometryPlacer<RubberbandClass>::~GeometryPlacer()
{
if (m_map)
{
delete (m_previewGeoModel);
delete (m_previewGeoModelRenderer);
if (m_rubberband)
m_map->layerFactory().removeOverlayLayer(QLatin1String(PreviewLayer));
m_map->update();
}
m_previewGeoModel = nullptr;
m_previewGeoModelRenderer = nullptr;
m_rubberband = nullptr;
} // template<class RubberbandClass> GeometryPlacer<RubberbandClass>::~GeometryPlacer()
} // namespace PlanEditor