/** ** ** @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 #include #include #include #include #include #include #include #include #include #include #include #include namespace SituationMap { class Map; class GeometryModel; namespace RB { class RubberbandTransform; } } // namespace SituationMap namespace PlanEditor { const unsigned int RubberbandMinPixelSize = 20; template class GeometryPlacer : public QObject { public: static RubberbandClass *create(PlanEditor *planEditor , SituationMap::Map *map , SituationMap::GeometryModel *geoModel , const QList &uidList , QObject *parent); bool isValid() const { return (m_previewGeoModel != nullptr); } void changeStyleAttributes(); private: GeometryPlacer(PlanEditor *planEditor, SituationMap::Map *map, SituationMap::GeometryModel *, const QList &, 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 &v); PlanEditor *m_planEditor; QPointer m_map; SituationMap::GeometryModel *m_previewGeoModel; SituationMap::GeometryModelRenderer *m_previewGeoModelRenderer; QList m_uidList; RubberbandClass *m_rubberband; Logger &m_logger; }; // template class GeometryPlacer template RubberbandClass *GeometryPlacer::create(PlanEditor *planEditor , SituationMap::Map *map , SituationMap::GeometryModel *geoModel , const QList &uidList , QObject *parent) { if (!std::is_base_of::value) return nullptr; GeometryPlacer *gP = new GeometryPlacer(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::create() static const char *const PreviewLayer = "PlanEditorPreview"; template GeometryPlacer::GeometryPlacer(PlanEditor *planEditor , SituationMap::Map *map , SituationMap::GeometryModel *geoModel , const QList &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::infinity(); qreal xmax = -std::numeric_limits::infinity(); qreal ymin = std::numeric_limits::infinity(); qreal ymax = -std::numeric_limits::infinity(); m_uidList.reserve(uidList.size()); for (SituationMap::UniqueMapId uid : uidList) { const SituationMap::GeometryModel::Node &modelPrimitive = geoModel->findPrimitive(uid); if (modelPrimitive.isValid()) { const QList &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(map->rubberbandFactory().create(parent)); if (m_rubberband) { map->layerFactory().addOverlayLayer(QLatin1String(PreviewLayer), QLatin1String(PreviewLayer)); m_previewGeoModelRenderer = map->createGeometryModelRenderer(QLatin1String(PreviewLayer), m_previewGeoModel); m_rubberband->setAutoDelete(true); QVector 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 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 void GeometryPlacer::adjustEllipse(SituationMap::GeometryModel *geoModel, const SituationMap::GeometryModel::Node &p, const QList &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 void GeometryPlacer::changeStyleAttributes() { ChangeStyleVisitor doChangeStyle(m_planEditor, m_previewGeoModel); doChangeStyle.traverse(); m_previewGeoModelRenderer->render(); m_map->getQGraphicsView()->viewport()->update(); // onUpdateView(); } // template void GeometryPlacer::changeStyleAttributes(const QString & undoToken) template void GeometryPlacer::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 vertexList = m_previewGeoModel->getPrimitivePoints(modelPrimitive); QList::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 void GeometryPlacer::applyTransform( SituationMap::GeometryModel * geoModel ) template void GeometryPlacer::abortTransform() { delete this; } // template void GeometryPlacer::abortTransform() template void GeometryPlacer::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 void GeometryPlacer::previewTransform( const SituationMap::MathUtils::Matrix2D & mat ) template void GeometryPlacer::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 vertexList = m_previewGeoModel->getPrimitivePoints(n); if (!vertexList.isEmpty()) { QList::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 void GeometryPlacer::previewTransform( SitautionMap::Map *, const SituationMap::MathUtils::Matrix2D &, SituationMap::GeometryModel::Node & ) template GeometryPlacer::~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 GeometryPlacer::~GeometryPlacer() } // namespace PlanEditor