/** * @RDE_HEADER@ * AbstractGraphicalObject * $Id$ * $HeadURL$ * $Author$ */ // Own #include "abstract_graphical_object.h" #include // operator<< // ESRI #include #include #include #include // Qt #include #include #include #include #include namespace SituationMap { AbstractGraphicalObject::AbstractGraphicalObject(const Esri::ArcGISRuntime::GeoView *geoView , Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer , const QString &debugInfo , const UniqueMapId &uniqueMapId) : m_geoView(geoView) , m_graphicsLayer(graphicsLayer) , m_uniqueMapId(uniqueMapId) , m_debugInfo(debugInfo) , m_logger(getLogger("AbstractGraphicalObject")) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; m_pickable = (m_uniqueMapId != INVALID_MAP_ID); TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } AbstractGraphicalObject::~AbstractGraphicalObject() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << ", m_graphicsLayer-overlayId=" << (m_graphicsLayer ? m_graphicsLayer->overlayId() : QString(QStringLiteral("unknown"))); #if ACRGIS_VERSION >= 0x640F00 // 100.15.0 // removeOne ist helpful to let ModuleTest succeed: 'situationMapModuleTest checkOverlayHandler3 none' if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) m_graphicsLayer->graphics()->removeOne(m_graphic); delete m_graphic; m_graphic = nullptr; #else /* // cleanup // to make sure, the the graphic is removed from internal container BEFORE // graphic is deleted, follow the documentation: use Layer::removeGraphic // (instead 'delete m_graphic') if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) m_graphicsLayer->graphics()->removeOne(m_graphic); if (m_graphic) { delete m_graphic; m_graphic = nullptr; } */ // Race condition between removeOne() and delete m_graphic : // BUG-000116867 is for the deadlock // BUG-000117464 is for the crash // Use following workaround until ESRI will release the fix (with 100.5!?) if (m_graphic && m_graphicsLayer && m_graphicsLayer->graphics()) { const int idx = m_graphicsLayer->graphics()->indexOf(m_graphic); if (idx < 0) ERROR_LOG(m_logger) << LOGFUNCTION << " indexOf failed, debugInfo='" << m_debugInfo << "'"; auto graphicCopy = m_graphic; QObject::connect (m_graphicsLayer->graphics(), &Esri::ArcGISRuntime::GraphicListModel::graphicRemoved, m_graphic, [idx, graphicCopy](int index) { if (idx == index) delete graphicCopy; }); m_graphicsLayer->graphics()->removeOne(m_graphic); } // : Race condition between removeOne() and delete m_graphic // m_esriSimpleLineSymbol AND m_esriSimpleFillSymbol will be destroyed m_graphic #endif // ACRGIS_VERSION >= 0x640F00 TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } Esri::ArcGISRuntime::Graphic *AbstractGraphicalObject::graphic() { return m_graphic; } Esri::ArcGISRuntime::GraphicsOverlay *AbstractGraphicalObject::graphicsLayer() { return m_graphicsLayer; } Esri::ArcGISRuntime::Geometry AbstractGraphicalObject::getGraphicGeometry() const { if (! m_graphic) return Esri::ArcGISRuntime::Geometry(); return m_graphic->geometry(); } bool AbstractGraphicalObject::updateView() { if (!changed()) return true; bool success = updateViewImpl(); resetChanged(); return success; } QString AbstractGraphicalObject::debugInfo() const { return m_debugInfo; } void AbstractGraphicalObject::setDebugInfo(const QString &debugInfo) { m_debugInfo = debugInfo; } int AbstractGraphicalObject::drawOrder() const { return m_drawOrder.value(); } void AbstractGraphicalObject::setDrawOrder(int drawOrder) { m_drawOrder = drawOrder; // if (m_graphic) // m_graphic->setDrawOrder(m_drawOrder.value()); } QString AbstractGraphicalObject::toString() const { QString str; if (!m_debugInfo.isEmpty()) str.append(QStringLiteral("debugInfo=%1").arg(m_debugInfo)); if (m_graphicsLayer) str.append(QStringLiteral(" layer='%1'").arg(m_graphicsLayer->objectName())); str.append(QStringLiteral(" m_drawOrder='%1'").arg(m_drawOrder.value())); if (m_graphic) str.append(QStringLiteral(" zIndex='%1'").arg(m_graphic->zIndex())); str.append(QStringLiteral(" visibility='%1'").arg(m_visibility.value())); str.append(QStringLiteral(" selected='%1'").arg(m_selected.value())); str.append(QStringLiteral(" scale='%1'").arg(m_scale.value())); str.append(QStringLiteral(" positions.count='%1'").arg(m_positions.value().count())); return str; } UniqueMapId AbstractGraphicalObject::uniqueMapId() const { return m_uniqueMapId; } void AbstractGraphicalObject::setUniqueMapId(UniqueMapId uniqueId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << uniqueId; m_uniqueMapId = uniqueId; // use attribute to store application obj id (SimWorld) at graphic if (m_graphic) { // use attributeModel to store application obj id (SimWorld) at graphic Esri::ArcGISRuntime::AttributeListModel *attListModel = m_graphic->attributes(); if (attListModel) attListModel->insertAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute), m_uniqueMapId); else ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; } TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << uniqueId; } Point3D AbstractGraphicalObject::origin() const { return m_positions.value().value(0); // value => if index is out of range -> default object } void AbstractGraphicalObject::setOrigin(const Point3D &origin) { // TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" // << " origin=" << origin; if (m_positions.value().isEmpty()) m_positions.refValue().append(origin); else { // https://stackoverflow.com/a/1573715/2352507 // Any comparison(except with "!=") with NaN returns false. // NaN != NaN -> true!!! if (m_positions.value().value(0) != origin) // NaN != NaN -> true!!! { DEBUG_LOG(m_logger) << LOGFUNCTION << " value(0) = " << m_positions.value().value(0) << " origin = " << origin; m_positions.refValue().clear(); m_positions.refValue().append(origin); } } // TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" // << " origin=" << origin // << " changed=" << m_positions.changed() // << " SIZE=" << m_positions.value().size(); } void AbstractGraphicalObject::setOrigin(const QPointF &origin) { setOrigin(Point3D(origin.x(), origin.y(), .0)); } PositionCont AbstractGraphicalObject::positions() const { return m_positions.value(); } void AbstractGraphicalObject::setPositions(const PositionCont &posCont) { m_positions = posCont; // TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" // << " changed=" << m_positions.changed(); } bool AbstractGraphicalObject::isSinglePoint() const { return m_positions.value().count() == 1; } bool AbstractGraphicalObject::isMultiPoint() const { return m_positions.value().count() > 1; } bool AbstractGraphicalObject::isArea() const { return m_positions.value().count() > 2; } LineStyle AbstractGraphicalObject::lineStyle() const { return m_lineStyle.value(); } LineStyle &AbstractGraphicalObject::rLineStyle() { return m_lineStyle.refValue(); } void AbstractGraphicalObject::setLineStyle(const LineStyle &lineStyle) { m_lineStyle = lineStyle; } FillStyle AbstractGraphicalObject::fillStyle() const { return m_fillStyle.value(); } FillStyle &AbstractGraphicalObject::rFillStyle() { return m_fillStyle.refValue(); } void AbstractGraphicalObject::setFillStyle(const FillStyle &fillStyle) { m_fillStyle = fillStyle; } bool AbstractGraphicalObject::visibility() const { return m_visibility.value(); } void AbstractGraphicalObject::setVisibility(bool visibility) { m_visibility = visibility; TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " visibility=" << m_visibility; } bool AbstractGraphicalObject::selected() const { return m_selected.value(); } void AbstractGraphicalObject::setSelected(bool selected) { m_selected = selected; } bool AbstractGraphicalObject::pickable() const { return m_pickable.value(); } void AbstractGraphicalObject::setPickable(bool pickable) { // allow pickable only if m_uniqueMapId is valid: m_pickable = pickable && (m_uniqueMapId != INVALID_MAP_ID); TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " pickable=" << m_pickable; } float AbstractGraphicalObject::scale() const { return m_scale.value(); } void AbstractGraphicalObject::setScale(float scale) { m_scale = scale; } bool AbstractGraphicalObject::addUpdateGraphic() { TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " updateGraphic"; bool successGraphic = true; if (! m_graphic) { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no m_graphic -> return true"; return true; } // append just once :-) if (! m_graphicsLayer->graphics()->contains(m_graphic)) { m_graphicsLayer->graphics()->append(m_graphic); successGraphic = m_graphicsLayer->graphics()->contains(m_graphic); } TRACE_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " successGraphic=" << successGraphic; return successGraphic; } bool AbstractGraphicalObject::create() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << " uniqueMapId =" << m_uniqueMapId << " origin =" << this->origin() << " visibility=" << m_visibility; bool success = true; // some checks: if (!m_graphicsLayer) { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no graphicsLayer"; return false; } if (!m_graphic) { m_graphic = new Esri::ArcGISRuntime::Graphic(); m_graphic->setObjectName(m_debugInfo); m_graphic->setZIndex(m_drawOrder.value()); // use attributeModel to store application obj id (SimWorld) at graphic Esri::ArcGISRuntime::AttributeListModel *attLisModel = m_graphic->attributes(); if (attLisModel) attLisModel->insertAttribute(QLatin1String(SituationMap::UniqueMapIdAttribute), m_uniqueMapId); else { ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; } } TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" << " success=" << success; return success; } bool AbstractGraphicalObject::updateViewImpl() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId << " origin=" << this->origin() << " visibility=" << m_visibility << " selected=" << m_selected << " drawOrder=" << m_drawOrder << " pickable=" << m_pickable << " pickableCHG=" << m_pickable.changed(); if (QApplication::instance()->thread()->currentThreadId() != QThread::currentThreadId()) { ERROR_LOG(m_logger) << LOGFUNCTION << " wrong Thread, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; abort(); } if (m_graphic) { m_graphic->setZIndex(m_drawOrder.value()); m_graphic->setVisible(m_visibility.value()); m_graphic->setSelected(m_selected.value()); // Picking: if (m_pickable.changed()) { // use attributeModel to store m_pickable at graphic Esri::ArcGISRuntime::AttributeListModel *attLisModel = m_graphic->attributes(); if (attLisModel) attLisModel->insertAttribute(QLatin1String(SituationMap::IsPickableAttribute), m_pickable.value()); else { ERROR_LOG(m_logger) << LOGFUNCTION << " no AttributeListModel, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; } } m_pickable.reset(); } TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" << " uniqueMapId =" << m_uniqueMapId; return true; } bool AbstractGraphicalObject::assignLineStyleToGraphic() { if (!m_graphic) return false; const LineStyle &ls = m_lineStyle.value(); if (!m_esriSimpleLineSymbol) { m_esriSimpleLineSymbol = new Esri::ArcGISRuntime::SimpleLineSymbol(convert(ls.linePattern()), ls.lineColor(), ls.lineWidth() * m_scale.value(), m_graphic); } else { m_esriSimpleLineSymbol->setStyle(convert(ls.linePattern())); m_esriSimpleLineSymbol->setColor(ls.lineColor()); m_esriSimpleLineSymbol->setWidth(ls.lineWidth() * m_scale.value()); } m_graphic->setSymbol(m_esriSimpleLineSymbol.data()); return true; } bool AbstractGraphicalObject::assignFillStyleToGraphic() { if (!m_graphic) return false; const LineStyle &ls = m_lineStyle.value(); const FillStyle &fs = m_fillStyle.value(); // 1. the Line if (!m_esriSimpleLineSymbol) { m_esriSimpleLineSymbol = new Esri::ArcGISRuntime::SimpleLineSymbol(convert(ls.linePattern()), ls.lineColor(), ls.lineWidth() * m_scale.value(), m_graphic); } else { m_esriSimpleLineSymbol->setStyle(convert(ls.linePattern())); m_esriSimpleLineSymbol->setColor(ls.lineColor()); m_esriSimpleLineSymbol->setWidth(ls.lineWidth() * m_scale.value()); } // 1. the Fill if (!m_esriSimpleFillSymbol) { m_esriSimpleFillSymbol = new Esri::ArcGISRuntime::SimpleFillSymbol(convert(fs.fillPattern()), fs.fillColor(), m_esriSimpleLineSymbol, m_graphic); } else { m_esriSimpleFillSymbol->setStyle(convert(fs.fillPattern())); m_esriSimpleFillSymbol->setColor(fs.fillColor()); m_esriSimpleFillSymbol->setOutline(m_esriSimpleLineSymbol); } m_graphic->setSymbol(m_esriSimpleFillSymbol.data()); return true; } Esri::ArcGISRuntime::SimpleLineSymbolStyle AbstractGraphicalObject::convert(const LinePattern linePattern) { switch (linePattern) { case LinePattern::Dash: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Dash; case LinePattern::DashDot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::DashDot; case LinePattern::DashDotDot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::DashDotDot; case LinePattern::Dot: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Dot; case LinePattern::Null: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Null; case LinePattern::Solid: return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid; } // switch return Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid; } Esri::ArcGISRuntime::SimpleFillSymbolStyle AbstractGraphicalObject::convert(const FillPattern fillPattern) { switch (fillPattern) { case FillPattern::BackwardDiagonal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::BackwardDiagonal; case FillPattern::Cross: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Cross; case FillPattern::DiagonalCross: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::DiagonalCross; case FillPattern::ForwardDiagonal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::ForwardDiagonal; case FillPattern::Horizontal: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Horizontal; case FillPattern::Null: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Null; case FillPattern::Solid: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid; case FillPattern::Vertical: return Esri::ArcGISRuntime::SimpleFillSymbolStyle::Vertical; } // switch return Esri::ArcGISRuntime::SimpleFillSymbolStyle::BackwardDiagonal; } int AbstractGraphicalObject::countOfPolygonFirstPart(const Esri::ArcGISRuntime::Multipart &polylinePolygon) { if (polylinePolygon.isEmpty() || polylinePolygon.parts().isEmpty()) return -1; return polylinePolygon.parts().part(0).pointCount(); } const Esri::ArcGISRuntime::SpatialReference AbstractGraphicalObject::spatialReference() const { return m_geoView->spatialReference(); } } // namespace SituationMap