/** * @RDE_HEADER@ * TacticalAppearance * $Id$ * $HeadURL$ * $Author$ */ // Own #include "tactical_appearance.h" #include #include #include #include #include #include // operator<< #include // ESRI #include #include #include #include #include // Qt #include namespace SituationMap { class TacticalAppearance::Private { public: Private() = default; ~Private() = default; // ESRI data: MapImpl *m_mapImpl = nullptr; QString m_logicalLayerName; Esri::ArcGISRuntime::GraphicsOverlay *m_graphicOverlayTactical = nullptr; AdvancedSymbology::SymbolDictionary *m_dictionary = nullptr; AdvancedSymbology::SymbolResultWatcher m_symbolResultWatcher; // OWN data: SimpleValue m_uniqueDesignation; SimpleValue m_symbol; AdvancedSymbology::SymbolDataToRender m_symDataToRender; GraphicalObjectPictureMarker *m_pictureMarker = nullptr; }; // class TacticalAppearance::Private //----------------------------------------------------------------------------- TacticalAppearance::TacticalAppearance(MapImpl *mapImpl, const QString &logicalLayerName, const QString &debugInfo, const UniqueMapId &uniqueMapId) : BaseClass(mapImpl->getEsriGeoView(), nullptr, debugInfo, uniqueMapId), m_logger(getLogger("TacticalAppearance")), d(new Private) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << ", logicalLayerName='" << logicalLayerName << "'"; d->m_symDataToRender.sizeL = 100; d->m_mapImpl = mapImpl; d->m_logicalLayerName = logicalLayerName; if (!m_graphicsLayer && d->m_mapImpl) m_graphicsLayer = d->m_mapImpl->getGraphicsLayer(d->m_logicalLayerName); if (!m_graphicsLayer) { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " getGraphicsLayer failed"; } if (d->m_mapImpl) d->m_dictionary = &(d->m_mapImpl->symbolDictionary()); // lambda: // m_symbolResultWatcher is canceled on destruction. // So it`s safe that no signal will be fired afterwards. // To avoid CLang warnings use m_symbolResultWatcher as context (3rd para), although it´s not intended. // Better idea? QObject::connect(&d->m_symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, &d->m_symbolResultWatcher, [this]() { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " resultReady..."; QSharedPointer symResult = d->m_symbolResultWatcher.symbolResult(); if (! symResult) return; // should NEVER happen! (Which object should have triggered the signal???) if (d->m_pictureMarker) { d->m_pictureMarker->setImage(symResult->symbolImage().image()); d->m_pictureMarker->updateView(); } }); TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } TacticalAppearance::~TacticalAppearance() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; // abort pending call to SymbolDictionary::findSymbol: d->m_symbolResultWatcher.cancel(); delete d->m_pictureMarker; d->m_pictureMarker = nullptr; removeGraphicFromGraphicOverlayTactical(); delete d; TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } void TacticalAppearance::removeGraphicFromGraphicOverlayTactical() { if (m_logger.isTraceEnabled()) { if (m_graphic) { Esri::ArcGISRuntime::GraphicsOverlay *gOverlay = m_graphic->graphicsOverlay(); if (gOverlay) TRACE_LOG(m_logger) << LOGFUNCTION << " START debugInfo='" << m_debugInfo << "'" << " name = '" << gOverlay->overlayId() << "'"; } } if (m_graphic && d->m_graphicOverlayTactical && d->m_graphicOverlayTactical->graphics()) d->m_graphicOverlayTactical->graphics()->removeOne(m_graphic); delete m_graphic; m_graphic = nullptr; } bool TacticalAppearance::useDictionaryRenderMode(const AdvancedSymbology::GeometryType &geomType) const { bool usePictureMarker = isSinglePoint() && (AdvancedSymbology::GeometryType::Point == geomType || AdvancedSymbology::GeometryType::Undefined == geomType); return !usePictureMarker || symbol().useAlwaysDictionaryRenderer; } // bool TacticalAppearance::useDictionaryRenderMode() const bool TacticalAppearance::create() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << " m_visibility = " << m_visibility; bool success = true; // // Prepare / create : // // a) singlePoint / exactly one coordinate -> use pictureMarker // or // b) multiPoint / more than one coordinate -> m_graphic / DictionaryRenderer /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This assumption doesn't work any more because some polyline and polygon symbols will have only one * anchor point and the real geometry is defined by additional attributes like radius, with, length ... * * JT 2016/12/20 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if (m_graphicsLayer && m_visibility.value()) { const SymbolData &symbolData_ref = d->m_symbol.value(); const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(symbolData_ref.sidc); const AdvancedSymbology::SymbolProperties &symbolProperties = d->m_dictionary->symbolProperties(baseSymbolCode); AdvancedSymbology::GeometryType geomType = symbolProperties.geometryType(); if (useDictionaryRenderMode(geomType)) { if (!d->m_graphicOverlayTactical) d->m_graphicOverlayTactical = d->m_mapImpl->getGraphicsLayerDict(d->m_logicalLayerName); if (!d->m_graphicOverlayTactical) { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " getGraphicsLayerDict failed"; 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; // Adding an empty graphic leads to Segmentation fault. // (in Esri_runtimecore::Map_renderer::Dictionary_renderer::derive_geometry) bool successGraphic = addUpdateGraphic(); // CHECK if (!successGraphic) ERROR_LOG(m_logger) << LOGFUNCTION << " addUpdateGraphic failed, debugInfo='" << m_debugInfo << "'" << " uniqueMapId=" << m_uniqueMapId; } // if (!m_graphic) } else { // m_graphic is needless now: removeGraphicFromGraphicOverlayTactical(); } } // : Prepare / create bool positionsEmpty = m_positions.value().isEmpty(); if (positionsEmpty) ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " positions empty"; success = success && !positionsEmpty; if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " create failed"; TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; return success; } void TacticalAppearance::createPictureMarker() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; if (!d->m_pictureMarker && d->m_mapImpl) { d->m_pictureMarker = new GraphicalObjectPictureMarker(d->m_mapImpl->getEsriGeoView() , m_graphicsLayer , m_debugInfo + QLatin1String("#PicMarkTacticalAppearance") , m_uniqueMapId); } TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } void TacticalAppearance::removePictureMarker() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'"; delete d->m_pictureMarker; d->m_pictureMarker = nullptr; TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'"; } QString TacticalAppearance::uniqueDesignation() const { return d->m_uniqueDesignation.value(); } void TacticalAppearance::setUniqueDesignation(const QString &uniqueDesignation) { d->m_uniqueDesignation = uniqueDesignation; } SymbolImage TacticalAppearance::symbolImage() const { SymbolImage symImage; if (d->m_pictureMarker) { symImage.setImage(d->m_pictureMarker->image()); symImage.setValid(!symImage.image().isNull()); } return symImage; } SymbolData TacticalAppearance::symbol() const { return d->m_symbol.value(); } void TacticalAppearance::setSymbol(const SymbolData &symbol) { d->m_symbol = symbol; } bool TacticalAppearance::changed() { return BaseClass::changed() || d->m_uniqueDesignation.changed() || d->m_symbol.changed(); } void TacticalAppearance::resetChanged() { BaseClass::resetChanged(); d->m_uniqueDesignation.reset(); d->m_symbol.reset(); } Esri::ArcGISRuntime::Geometry TacticalAppearance::getGraphicGeometry() const { if (d->m_pictureMarker) return d->m_pictureMarker->getGraphicGeometry(); if (m_graphic) return m_graphic->geometry(); ERROR_LOG(m_logger) << LOGFUNCTION << "Geometry is undefined"; return Esri::ArcGISRuntime::Geometry(); } // Esri::ArcGISRuntime::Geometry TacticalAppearance::getGraphicGeometry() const bool TacticalAppearance::updateViewImpl() { TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << " symbol='" << d->m_symbol.value().sidc << "'" << " visibility=" << m_visibility << " selected=" << m_selected << " origin=" << this->origin() << " scale=" << m_scale.value(); const SymbolData &symbolData_ref = d->m_symbol.value(); const QString &baseSymbolCode = AdvancedSymbology::SidcBuilderBase::concreteSidcToBaseSymbolCode(symbolData_ref.sidc); const AdvancedSymbology::SymbolProperties &symbolProperties = d->m_dictionary->symbolProperties(baseSymbolCode); AdvancedSymbology::GeometryType geomType = symbolProperties.geometryType(); if (geomType == AdvancedSymbology::GeometryType::Undefined) { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " GeometryType::Undefined, symbolProperties failed!?!"; return false; } TRACE_LOG(m_logger) << LOGFUNCTION << " START, debugInfo='" << m_debugInfo << "'" << ", geomType=" << geomType << ", useDictionaryRenderMode(geomType)=" << useDictionaryRenderMode(geomType); bool success = create(); if (!success) { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " create failed"; return false; } if (useDictionaryRenderMode(geomType)) { // real work: if (m_positions.changed() || d->m_symbol.changed() || m_visibility.changed()) { auto buildPointClusterGeometry = [this]() -> Esri::ArcGISRuntime::Geometry { QSharedPointer builder(new Esri::ArcGISRuntime::MultipointBuilder(spatialReference())); Esri::ArcGISRuntime::PointCollection points(spatialReference()); const auto posCont = positions(); for (const auto &pos : posCont) { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " addPoint:" << pos.x() << " - " << pos.y(); points.addPoint(pos.x(), pos.y()); } builder->setPoints(&points); return builder->toGeometry(); }; // buildPointClusterGeometry Esri::ArcGISRuntime::Geometry geom; switch (geomType) { case AdvancedSymbology::GeometryType::Undefined : break; case AdvancedSymbology::GeometryType::Point : case AdvancedSymbology::GeometryType::Polyline : { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " m_positions.count()=" << m_positions.value().count(); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * If a PolylineBuilder is used here to create symbol geometry the construction appropriate symbol standard (MIL2525C / MIL2525D / App6) * will not be done and the symbol is always a simple polyline. Using MultipointBuilder to build symbol geometry the result is based on * underlying symbol standard. * 2016/12/9 JT * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ geom = buildPointClusterGeometry(); } break; case AdvancedSymbology::GeometryType::Polygon : { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " m_positions.count()=" << m_positions.value().count(); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Some symbols have polygon geometry and could have only one or two positions. In case of less than 3 positions do the same as for * polylines and use MultipointBuilder to create the symbol geometry. In other case use PolygonBuilder. * 2016/12/13 JT * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if (positions().size() > 2) { QSharedPointer builder(new Esri::ArcGISRuntime::PolygonBuilder(spatialReference())); const auto posCont = positions(); for (const auto &pos : posCont) { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " addPoint:" << pos.x() << " - " << pos.y(); builder->addPoint(pos.x(), pos.y()); } geom = builder->toGeometry(); } else geom = buildPointClusterGeometry(); } break; } // switch if (geom.isEmpty()) { ERROR_LOG(m_logger) << LOGFUNCTION << " Geometry NOT OK -> fallback symbol" << " (geometryType was " << geomType << ")"; createPictureMarker(); d->m_pictureMarker->setDebugInfo(m_debugInfo + QLatin1String("#fallback")); int size = symbolData_ref.size; if (size < 10) size = 10; if (size > 100) size = 100; d->m_pictureMarker->setMarkerSymbol(MarkerSymbolType::Diamond, size, symbolData_ref.color); d->m_pictureMarker->setOrigin(this->origin()); d->m_pictureMarker->setDrawOrder(m_drawOrder.value()); d->m_pictureMarker->updateView(); success = false; } else // geom is not empty { removePictureMarker(); if (m_graphic) { success = true; // move to graphic: m_graphic->setGeometry(geom); // prepare rawAttributes for DictionaryRenderer: QVariantMap rawAttributes = d->m_symbol.value().attributeMap; // build from scratch to honor removed attributes: // (Otherwise the will remain wrongly) rawAttributes[QLatin1String(SituationMap::UniqueMapIdAttribute)] = m_uniqueMapId; rawAttributes[QLatin1String(SituationMap::IsPickableAttribute)] = m_pickable.value(); // set pickable attribute JT 2016/12/21 rawAttributes[QLatin1String("_type")] = QLatin1String("position_report"); rawAttributes[QLatin1String("_action")] = QLatin1String("update"); rawAttributes[QLatin1String("sidc")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of MIL2525 rawAttributes[QLatin1String("sic")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of APP6 Esri::ArcGISRuntime::AttributeListModel * attributeModel = m_graphic->attributes(); if (attributeModel) attributeModel->setAttributesMap(rawAttributes); else ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no AttributeListModel"; // // prepare rawAttributes for DictionaryRenderer: // QVariantMap rawAttributes = d->m_symbol.value().attributeMap; // rawAttributes[QLatin1String(SituationMap::IsPickableAttribute)] = m_pickable.value(); // set pickable attribute JT 2016/12/21 // rawAttributes[QLatin1String("_type")] = QLatin1String("position_report"); // rawAttributes[QLatin1String("_action")] = QLatin1String("update"); // rawAttributes[QLatin1String("sidc")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of MIL2525 // rawAttributes[QLatin1String("sic")] = AdvancedSymbology::SidcCodec::decodeRawSidc(d->m_symbol.value().sidc); // in case of APP6 // // move to graphic: // m_graphic->setGeometry(geom); // /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // * Replace: // * m_graphic->attributes()->setAttributesMap(rawAttributes); // * // * because it doesn't work anymore in ArcGISRuntime V100.2 // * JT, 2018/01/24 // * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // Esri::ArcGISRuntime::AttributeListModel * attributeModel = m_graphic->attributes(); // if (attributeModel) // { // if (!attributeModel->isEmpty()) // { // for (auto it = rawAttributes.cbegin(); it != rawAttributes.cend(); ++it) // { // const QString &attrKey = it.key(); // if (attributeModel->containsAttribute(attrKey)) // { // attributeModel->replaceAttribute(attrKey, rawAttributes[attrKey]); // continue; // } // attributeModel->insertAttribute(attrKey, rawAttributes[attrKey]); // } // } // else attributeModel->setAttributesMap(rawAttributes); // } // 2021-08, N. Thoden // While switching back to the behaviour that one logical layer has // 1st getGraphicsLayer and 2nd getGraphicsLayerDict [GUEZE-496], // the tactical appereance of DangerArea and similar disappears. // Reason: m_graphicsLayer and m_graphicOverlayTactical are different now! // It seems like that in former versions (< V100.8) the graphic could // be added to more than one Esri layer. // Now (V100.8) adding to a second layer fails. // But we can remove the m_graphic from the default m_graphicsLayer // before adding the the desired one: m_graphicOverlayTactical if (m_graphicsLayer->graphics()->contains(m_graphic)) m_graphicsLayer->graphics()->removeOne(m_graphic); // add to graphicOverlayTactical: if (! d->m_graphicOverlayTactical->graphics()->contains(m_graphic)) d->m_graphicOverlayTactical->graphics()->append(m_graphic); bool successGraphic = d->m_graphicOverlayTactical->graphics()->contains(m_graphic); if (!successGraphic) ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " m_graphic not added to m_graphicOverlayTactical"; } else { DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " no m_graphic"; // success = false; success = true; // if the DictionaryRenderer is used, m_graphic is null and that is ok. } } // if (geom.isEmpty()) } } else // !useDictionaryRenderMode(geomType) { createPictureMarker(); d->m_pictureMarker->setOrigin(this->origin()); // real work: DEBUG_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " update geometry"; if (d->m_symbol.changed() || m_scale.changed()) { if (d->m_dictionary) { int widthPixel = SituationMap::SymbolSizeLdefault; widthPixel *= symbolData_ref.size * m_scale.value(); d->m_symDataToRender.sidc = symbolData_ref.sidc; d->m_symDataToRender.fillColor = symbolData_ref.color; d->m_symDataToRender.fillColorReplace = true; d->m_symDataToRender.sizeL = widthPixel; if (d->m_pictureMarker->image().isNull()) d->m_pictureMarker->setImage(d->m_dictionary->temporaryImage(d->m_symDataToRender.sizeL)); d->m_symbolResultWatcher.setSymbolResult(d->m_dictionary->fetchSymbol(d->m_symDataToRender)); success = true; } else { ERROR_LOG(m_logger) << LOGFUNCTION << " debugInfo='" << m_debugInfo << "'" << " visibility=" << m_visibility << " no dictionary"; } } if (d->m_pictureMarker) { d->m_pictureMarker->setDrawOrder(m_drawOrder.value()); d->m_pictureMarker->setVisibility(m_visibility.value()); d->m_pictureMarker->setSelected(m_selected.value()); d->m_pictureMarker->setPickable(m_pickable.value()); success = success && d->m_pictureMarker->updateView(); } } // else !useDictionaryRenderMode(geomType) if (d->m_pictureMarker) { d->m_pictureMarker->setOrigin(this->origin()); // to avoid incorrect number of positions.. d->m_pictureMarker->setVisibility(m_visibility.value()); d->m_pictureMarker->setSelected(m_selected.value()); success = d->m_pictureMarker->updateView() && success; } // in case of no pictureMarker: if (m_graphic) { m_graphic->setZIndex(m_drawOrder.value()); m_graphic->setVisible(m_visibility.value()); m_graphic->setSelected(m_selected.value()); } // reset flags d->m_symbol.reset(); if (!success) ERROR_LOG(m_logger) << LOGFUNCTION << " failure, debugInfo='" << m_debugInfo << "'"; TRACE_LOG(m_logger) << LOGFUNCTION << " END, debugInfo='" << m_debugInfo << "'" << " success = " << success << " visibility=" << m_visibility; return success; } } // namespace SituationMap