From 6ad89e68c6e422eef3b41fb73ed1f30a43dd20f7 Mon Sep 17 00:00:00 2001 From: Jan Tschada Date: Fri, 22 Nov 2024 16:57:30 +0100 Subject: [PATCH] Added selected heat risk graphics --- .../UrbanHeatAnalyzer/HeatRiskListModel.cpp | 65 ++++++++++++++++++- .../UrbanHeatAnalyzer/HeatRiskListModel.h | 17 +++++ .../UrbanHeatAnalyzer/UrbanHeatAnalyzer.cpp | 59 +++++++++++++++-- .../UrbanHeatAnalyzer/UrbanHeatAnalyzer.h | 4 ++ .../qml/UrbanHeatAnalyzerForm.qml | 4 ++ .../UrbanHeatAnalyzer/qml/main.qml | 14 +++- 6 files changed, 155 insertions(+), 8 deletions(-) diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.cpp b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.cpp index ac3bb9c..a46ef9b 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.cpp +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.cpp @@ -16,6 +16,8 @@ #include "AttributeListModel.h" #include "Feature.h" +#include "Geometry.h" +#include "GeometryEngine.h" using namespace Esri::ArcGISRuntime; @@ -26,6 +28,32 @@ HeatRiskAnalysisGroup::HeatRiskAnalysisGroup(double heatRiskIndex) } +bool HeatRiskAnalysisGroup::operator==(const HeatRiskAnalysisGroup &other) const +{ + if (m_heatRiskIndex != other.m_heatRiskIndex) + { + return false; + } + + if (m_heatRiskFeatures.count() != other.m_heatRiskFeatures.count()) + { + return false; + } + + // Compare features just using the geometry + for (auto index=0; index < m_heatRiskFeatures.count(); index++) + { + auto feature = m_heatRiskFeatures[index]; + auto otherFeature = other.m_heatRiskFeatures[index]; + if (!feature->geometry().equalsWithTolerance(otherFeature->geometry(), 0.001)) + { + return false; + } + } + + return true; +} + QString HeatRiskAnalysisGroup::name() const { if (m_heatRiskFeatures.empty()) @@ -47,6 +75,17 @@ void HeatRiskAnalysisGroup::addFeature(Feature *heatRiskFeature) m_heatRiskFeatures.append(heatRiskFeature); } +Geometry HeatRiskAnalysisGroup::areaOfInterest() +{ + QList geometries; + for (auto heatRiskFeature : m_heatRiskFeatures) + { + geometries.append(heatRiskFeature->geometry()); + } + + return GeometryEngine::unionOf(geometries); +} + HeatRiskListModel::HeatRiskListModel(QObject *parent) : QAbstractListModel{parent} @@ -66,7 +105,31 @@ void HeatRiskListModel::loadAnalysisGroups(const QList &a void HeatRiskListModel::select(int index) { - qDebug() << index; + if (m_selectedIndex != index) + { + m_selectedIndex = index; + emit selectedHeatRiskItemChanged(); + } +} + +HeatRiskAnalysisGroup* HeatRiskListModel::selectedGroup() const +{ + if (m_selectedIndex < 0 || m_analysisGroups.count() <= m_selectedIndex) + { + return nullptr; + } + + return &const_cast(this)->m_analysisGroups[m_selectedIndex]; +} + +void HeatRiskListModel::setSelectedGroup(HeatRiskAnalysisGroup *group) +{ + auto index_of = m_analysisGroups.indexOf(*group); + if (m_selectedIndex != index_of) + { + m_selectedIndex = index_of; + emit selectedHeatRiskItemChanged(); + } } int HeatRiskListModel::rowCount(const QModelIndex &parent) const { diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.h b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.h index c6d909b..54d2847 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.h +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/HeatRiskListModel.h @@ -20,6 +20,7 @@ namespace Esri::ArcGISRuntime { class Feature; +class Geometry; } // namespace Esri::ArcGISRuntime @@ -32,6 +33,9 @@ class HeatRiskAnalysisGroup double heatRiskIndex() const; void addFeature(Esri::ArcGISRuntime::Feature *heatRiskFeature); + Esri::ArcGISRuntime::Geometry areaOfInterest(); + + bool operator==(const HeatRiskAnalysisGroup &other) const; private: double m_heatRiskIndex; @@ -42,6 +46,11 @@ class HeatRiskAnalysisGroup class HeatRiskListModel : public QAbstractListModel { Q_OBJECT + + Q_PROPERTY(HeatRiskAnalysisGroup *selectedGroup + READ selectedGroup WRITE setSelectedGroup + NOTIFY selectedHeatRiskItemChanged) + public: enum HeatRiskRoles { NameRole = Qt::UserRole + 1, @@ -56,11 +65,19 @@ class HeatRiskListModel : public QAbstractListModel int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + HeatRiskAnalysisGroup *selectedGroup() const; + +signals: + void selectedHeatRiskItemChanged(); + protected: QHash roleNames() const; private: + void setSelectedGroup(HeatRiskAnalysisGroup *group); + QList m_analysisGroups; + int m_selectedIndex = -1; }; #endif // HEATRISKLISTMODEL_H diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.cpp b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.cpp index 8875524..ee8f20e 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.cpp +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.cpp @@ -36,6 +36,10 @@ #include "FeatureQueryResult.h" #include "Geodatabase.h" #include "GeodatabaseFeatureTable.h" +#include "Graphic.h" +#include "GraphicListModel.h" +#include "GraphicsOverlay.h" +#include "GraphicsOverlayListModel.h" #include "LayerListModel.h" #include "MapTypes.h" #include "OrderBy.h" @@ -44,11 +48,15 @@ #include "Scene.h" #include "SceneQuickView.h" #include "ServiceFeatureTable.h" +#include "SimpleFillSymbol.h" +#include "SimpleRenderer.h" #include "SpatialReference.h" #include "Surface.h" +#include "SymbolTypes.h" #include "VectorTileCache.h" #include "Viewpoint.h" +#include #include #include @@ -59,6 +67,7 @@ using namespace Esri::ArcGISRuntime; UrbanHeatAnalyzer::UrbanHeatAnalyzer(QObject *parent /* = nullptr */) : QObject(parent) , m_scene(new Scene(BasemapStyle::OsmStandard, this)) + , m_heatRiskOverlay(new GraphicsOverlay(this)) { // create a new scene layer from OSM Buildings rest service ArcGISSceneLayer *osmSceneLayer = new ArcGISSceneLayer( @@ -110,6 +119,17 @@ UrbanHeatAnalyzer::UrbanHeatAnalyzer(QObject *parent /* = nullptr */) loadHeatRiskFeatures(); }); m_scene->operationalLayers()->append(tiledLayer); + + // define the heat risk overlay rendering + SimpleFillSymbol *heatRiskFillSymbol = new SimpleFillSymbol( + SimpleFillSymbolStyle::Solid, + QColor("cyan"), + this); + SimpleRenderer *heatRiskRenderer = new SimpleRenderer( + heatRiskFillSymbol, + this); + m_heatRiskOverlay->setRenderer(heatRiskRenderer); + m_heatRiskOverlay->setOpacity(0.75f); } UrbanHeatAnalyzer::~UrbanHeatAnalyzer() {} @@ -130,6 +150,9 @@ void UrbanHeatAnalyzer::setSceneView(SceneQuickView *sceneView) m_sceneView = sceneView; m_sceneView->setArcGISScene(m_scene); + // add the graphics overlay + m_sceneView->graphicsOverlays()->append(m_heatRiskOverlay); + emit sceneViewChanged(); } @@ -141,6 +164,27 @@ void UrbanHeatAnalyzer::setHeatRiskListModel(HeatRiskListModel *heatRiskListMode } m_heatRiskListModel = heatRiskListModel; + connect(m_heatRiskListModel, &HeatRiskListModel::selectedHeatRiskItemChanged, this, [this]() + { + HeatRiskAnalysisGroup *riskGroup = m_heatRiskListModel->selectedGroup(); + if (!riskGroup) + { + return; + } + + if (m_sceneView) + { + // Zoom to area of interest + Geometry areaOfInterest = riskGroup->areaOfInterest(); + Viewpoint newViewpoint = Viewpoint(areaOfInterest); + m_sceneView->setViewpointAsync(newViewpoint, 2.5); + + // Add a new graphic visualizing the area of interest + m_heatRiskOverlay->graphics()->clear(); + Graphic *heatRiskArea = new Graphic(areaOfInterest, this); + m_heatRiskOverlay->graphics()->append(heatRiskArea); + } + }); } void UrbanHeatAnalyzer::loadHeatRiskFeatures() @@ -154,9 +198,6 @@ void UrbanHeatAnalyzer::loadHeatRiskFeatures() GeodatabaseFeatureTable *featureTable = gdb->geodatabaseFeatureTable("HRI_Bonn"); connect(featureTable, &GeodatabaseFeatureTable::doneLoading, this, [featureTable, this]() { - qint64 featureCount = featureTable->numberOfFeatures(); - qDebug() << featureCount; - // Execute the analysis QueryParameters analysisParameters; QList orderByFields; @@ -168,7 +209,7 @@ void UrbanHeatAnalyzer::loadHeatRiskFeatures() { // Reference raw pointer auto queryResult = std::unique_ptr(rawQueryResult); - if (queryResult && !queryResult->iterator().hasNext()) + if (!queryResult && !queryResult->iterator().hasNext()) { // No results or invalid pointer return; @@ -216,6 +257,16 @@ void UrbanHeatAnalyzer::loadHeatRiskFeatures() gdb->load(); } +void UrbanHeatAnalyzer::clearOverlay() +{ + if (!m_heatRiskOverlay) + { + return; + } + + m_heatRiskOverlay->graphics()->clear(); +} + void UrbanHeatAnalyzer::printCamera() { auto camera = m_sceneView->currentViewpointCamera(); diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.h b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.h index 27cd4db..1548c14 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.h +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/UrbanHeatAnalyzer.h @@ -26,6 +26,7 @@ class HeatRiskListModel; namespace Esri::ArcGISRuntime { +class GraphicsOverlay; class Scene; class SceneQuickView; } // namespace Esri::ArcGISRuntime @@ -49,6 +50,7 @@ class UrbanHeatAnalyzer : public QObject explicit UrbanHeatAnalyzer(QObject *parent = nullptr); ~UrbanHeatAnalyzer() override; + Q_INVOKABLE void clearOverlay(); Q_INVOKABLE void printCamera(); signals: @@ -65,6 +67,8 @@ class UrbanHeatAnalyzer : public QObject Esri::ArcGISRuntime::Scene *m_scene = nullptr; Esri::ArcGISRuntime::SceneQuickView *m_sceneView = nullptr; HeatRiskListModel *m_heatRiskListModel = nullptr; + Esri::ArcGISRuntime::GraphicsOverlay *m_heatRiskOverlay = nullptr; + }; #endif // URBANHEATANALYZER_H diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/UrbanHeatAnalyzerForm.qml b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/UrbanHeatAnalyzerForm.qml index 0ab2aff..f71213a 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/UrbanHeatAnalyzerForm.qml +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/UrbanHeatAnalyzerForm.qml @@ -29,6 +29,10 @@ Item { // Define a property for HeatRiskListModel property HeatRiskListModel heatRiskListModel: riskModel + function clearOverlay() { + model.clearOverlay(); + } + function printCamera() { model.printCamera(); } diff --git a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/main.qml b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/main.qml index 45cd463..a79c841 100644 --- a/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/main.qml +++ b/native-apps/urban-heat-analyzer/UrbanHeatAnalyzer/qml/main.qml @@ -32,6 +32,8 @@ ApplicationWindow { width: 800 height: 600 + title: "Urban Heat Analyzer" + Material.theme: Material.Dark Material.accent: "#C9F2FF" Material.background: "#0289C3" @@ -65,13 +67,13 @@ ApplicationWindow { Layout.alignment: Qt.AlignLeft Layout.leftMargin: 10 color: Material.foreground - text: "Name:" + name + text: "Name: " + name } Text { Layout.alignment: Qt.AlignLeft Layout.leftMargin: 10 color: Material.foreground - text: "Risk:" + risk + text: "Risk: " + risk } } @@ -80,7 +82,6 @@ ApplicationWindow { onClicked: { riskView.currentIndex = index; mapForm.heatRiskListModel.select(index); - console.log(name); } } } @@ -97,6 +98,13 @@ ApplicationWindow { focus: false } } + + ToolButton { + text: qsTr("Clear") + onClicked: { + mapForm.clearOverlay(); + } + } } }