500 lines
17 KiB
C++
Executable File
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
|
|
|