/** * @RDE_HEADER@ * ViewshedGeoProcessing * $Id$ * $HeadURL$ * $Author$ */ // Own #include "viewshed_geo_processing.h" #include "map_impl.h" #include #include #include #include // ESRI #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include namespace SituationMap { //----------------------------------------------------------------------------- // class ViewshedBase //----------------------------------------------------------------------------- ViewshedGeoProcessing::ViewshedGeoProcessing(const QString &url, Esri::ArcGISRuntime::GraphicsOverlay *graphicsLayer, const Esri::ArcGISRuntime::SpatialReference &spatialReference, QObject *parent) : ViewshedBase(parent) , m_url(url) , m_resultsOverlay(graphicsLayer) , m_srMap(spatialReference) , m_logger(getLogger("ViewshedGeoProcessing")) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } ViewshedGeoProcessing::~ViewshedGeoProcessing() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; clearGraphics(); delete m_viewshedTask; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } bool ViewshedGeoProcessing::init(SituationMap::MapImpl *mapImpl) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // check viewshedContraint (V100.2.1: disable usage of GPU to avoid failure: /* 2018-03-21 13:06:15.911 ERROR ViewshedGeoProcessing |#| void SituationMap::ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &) GeoprocessingJob -> EsriError occured: sender= 'GeoprocessingJob', code=400, domain=0, extendedErrorType=0, message='Unable to complete operation.', additionalMessage='Error executing tool. Viewshed2Feature : ERROR 010151: No features found in C:\users\ctc\Temp\arcgisruntime_8\LegatusBremen201803151510.gpk\jobs\_\j5a4e467cb51047c28a463a703c2f2bcd\scratch\scratch.gdb\Viewshed_Polygon. Possible empty feature class. Failed to execute (Raster to Polygon). Failed to execute (Viewshed2Feature). Failed to execute (Viewshed2Feature).' (pl/viewshed_geo_processing.cpp:529) 2018-03-21 13:06:15.911 ERROR Qt |#| Geoprocessing Task failed (ication/ngl_qt_msg_handler.inc:26) 2018-03-21 13:06:15.911 ERROR Qt |#| Geoprocessing Task failed message: code = 400 domain = 0 message = "Unable to complete operation." additionalMessage = "Error executing tool. Viewshed2Feature : ERROR 010151: No features found in C:\\users\\ctc\\Temp\\arcgisruntime_8\\LegatusBremen201803151510.gpk\\jobs\\_\\j5a4e467cb51047c28a463a703c2f2bcd\\scratch\\scratch.gdb\\Viewshed_Polygon. Possible empty feature class.\nFailed to execute (Raster to Polygon).\nFailed to execute (Viewshed2Feature).\nFailed to execute (Viewshed2Feature)." extendedErrorType = 0 (ication/ngl_qt_msg_handler.inc:26) 2018-03-21 13:06:15.911 INFO ViewshedGeoProcessing |#| void SituationMap::ViewshedGeoProcessing::onJobStatusChanged() jobStatusChanged is now (m_jobStatus): Job failed (pl/viewshed_geo_processing.cpp:256) */ { const char cuda[] = "CUDA_VISIBLE_DEVICES"; bool ok = qEnvironmentVariableIsSet(cuda) && (qEnvironmentVariableIsEmpty(cuda) || qEnvironmentVariableIntValue(cuda) == -1); if (ok) DEBUG_LOG(m_logger) << LOGFUNCTION << " VIEWSHED_CONTRAINT fulfilled: " << cuda << " = '" << qgetenv(cuda) << "'"; else { FATAL_LOG(m_logger) << LOGFUNCTION << " VIEWSHED_CONTRAINT NOT fulfilled: " << cuda << " = '" << qgetenv(cuda) << "'"; QMessageBox::critical(nullptr, tr("Configuration Error"), tr("Environmentvariable '%1' is not set to deactivate GPU. Operations can fail").arg(QString::fromLatin1(cuda)), QMessageBox::Ok); } } if (m_url.isEmpty()) { ERROR_LOG(m_logger) << LOGFUNCTION << " url is empty"; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; return false; } m_mapImpl = mapImpl; // mapImpl is required only if processRasterResults is used delete m_viewshedTask; m_viewshedTask = new Esri::ArcGISRuntime::GeoprocessingTask(QUrl(m_url), this); m_viewshedTask->setObjectName(QLatin1String("GeoprocessingTask")); // Handle errors of GeoprocessingTask: connect(m_viewshedTask, &Esri::ArcGISRuntime::GeoprocessingTask::errorOccurred, this, &ViewshedGeoProcessing::onErrorOccuredGeoprocessingTask); m_initialized = true; TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_initialized=" << m_initialized; return m_initialized; } bool ViewshedGeoProcessing::execute(const QPointF &viewshedPoint_map, double outerRadius, double observerOffset) { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " viewshedPoint_map=" << viewshedPoint_map << " m_srMap.wkid=" << m_srMap.wkid() << " outerRadius=" << outerRadius << " observerOffset=" << observerOffset << " m_viewshedInProgress=" << m_viewshedInProgress << " url='" << m_url << "'"; // check point is the viewshed point Esri::ArcGISRuntime::Point esriViewshedPoint(viewshedPoint_map.x(), viewshedPoint_map.y(), m_srMap); // Esri::ArcGISRuntime::SpatialReference::webMercator()); DEBUG_LOG(m_logger) << LOGFUNCTION << " esriViewshedPoint.toJson: " << esriViewshedPoint.toJson(); m_viewshedInProgress = true; createInputGraphic(esriViewshedPoint); createExtentGraphic(esriViewshedPoint, outerRadius); clearResultGraphics(); // Create a new feature collection table based upon point geometries using the current map view spatial reference Esri::ArcGISRuntime::FeatureCollectionTable *inputFeatures = new Esri::ArcGISRuntime::FeatureCollectionTable(QList() , Esri::ArcGISRuntime::GeometryType::Point , m_srMap , this); // Create a new feature from the feature collection table. It will not have a coordinate location (x,y) yet Esri::ArcGISRuntime::Feature *inputFeature = inputFeatures->createFeature(this); // Assign a physical location to the new point feature based upon where the user clicked on the map view inputFeature->setGeometry(esriViewshedPoint); // connect to addFeature status changed signal connect(inputFeatures, &Esri::ArcGISRuntime::FeatureCollectionTable::addFeatureCompleted, this, [this, inputFeatures, outerRadius, observerOffset](QUuid, bool success) { INFO_LOG(m_logger) << LOGFUNCTION << " addFeatureCompleted..."; if (!success) { ERROR_LOG(m_logger) << LOGFUNCTION << " no success"; return; } // Create the parameters that are passed to the used geoprocessing task Esri::ArcGISRuntime::GeoprocessingParameters viewshedParameters = Esri::ArcGISRuntime::GeoprocessingParameters(Esri::ArcGISRuntime::GeoprocessingExecutionType::SynchronousExecute); // AsynchronousSubmit | SynchronousExecute // GeoprocessingExecutionType should match Esri::ArcGISRuntime::GeoprocessingServiceType of LocalGeoprocessingService // Request the output features to use the same SpatialReference as the map view viewshedParameters.setOutputSpatialReference(m_srMap); // Add an input location to the geoprocessing parameters QMap inputs; inputs[QLatin1String("Input_point_or_polyline_observer_features")] = new Esri::ArcGISRuntime::GeoprocessingFeatures(inputFeatures, this); // Radius inputs[QLatin1String("Inner_radius")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(55.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); inputs[QLatin1String("Outer_radius")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(outerRadius, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); // Observer // //!!! DO NOT DEFINE, keep elevation of ellipsoid in mind // inputs[QLatin1String("Observer_elevation")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(0.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); inputs[QLatin1String("Observer_offset")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(observerOffset, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); // Target inputs[QLatin1String("Surface_offset")] = new Esri::ArcGISRuntime::GeoprocessingLinearUnit(0.0, Esri::ArcGISRuntime::GeoprocessingLinearUnits::Meter, this); // setInputs viewshedParameters.setInputs(inputs); // Print geoprocessingParameters for debugging purpose: QMapIterator inputsIt(inputs); while (inputsIt.hasNext()) { inputsIt.next(); QString value(QLatin1String("-")); Esri::ArcGISRuntime::GeoprocessingParameter *gpPara = inputsIt.value(); Esri::ArcGISRuntime::GeoprocessingLinearUnit *gpParaLU = dynamic_cast(gpPara); if (gpParaLU) value = QStringLiteral("%1 %2").arg(gpParaLU->distance()).arg(gpParaLU->linearUnit().name()); DEBUG_LOG(m_logger) << LOGFUNCTION << " key= '" << inputsIt.key() << "'" << " value= '" << value << "'"; } // while // Create the job that handles the communication between the application and the geoprocessing task m_viewshedJob = m_viewshedTask->createJob(viewshedParameters); m_viewshedJob->setObjectName(QLatin1String("GeoprocessingJob")); // Handle errors of GeoprocessingJob: connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::errorOccurred, this, &ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob); // Create signal handler for the job #if ACRGIS_VERSION >= 0x640E00 // 100.14.0 // since V100.14 connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::statusChanged, this, &ViewshedGeoProcessing::onJobStatusChangedWithJsParameter); #else // until V100.14: connect(m_viewshedJob, &Esri::ArcGISRuntime::GeoprocessingJob::jobStatusChanged, this, &ViewshedGeoProcessing::onJobStatusChanged); #endif // ACRGIS_VERSION // start the job if (m_viewshedJob->start()) LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); else ERROR_LOG(m_logger) << LOGFUNCTION << " viewshedJob->start() failed"; }); // Add the new feature with (x,y) location to the feature collection table inputFeatures->addFeature(inputFeature); TRACE_LOG(m_logger) << LOGFUNCTION << " END" << " m_viewshedInProgress=" << m_viewshedInProgress; return true; } void ViewshedGeoProcessing::onJobStatusChanged() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // qCritical() << "jobStatusChanged... viewshedJob->jobStatus()=" << (int)viewshedJob->jobStatus(); bool viewshedInProgressOld = m_viewshedInProgress; switch (m_viewshedJob->jobStatus()) { case Esri::ArcGISRuntime::JobStatus::NotStarted: ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed"; m_viewshedInProgress = false; m_jobStatus = QLatin1String("Job NotStarted"); break; case Esri::ArcGISRuntime::JobStatus::Failed: { ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed"; const Esri::ArcGISRuntime::Error &err = m_viewshedJob->error(); ERROR_LOG(m_logger) << LOGFUNCTION << "Geoprocessing Task failed message: " << err; m_viewshedInProgress = false; m_jobStatus = QLatin1String("Job failed"); } break; case Esri::ArcGISRuntime::JobStatus::Started: m_viewshedInProgress = true; m_jobStatus = QLatin1String("Job in progress..."); break; case Esri::ArcGISRuntime::JobStatus::Paused: m_viewshedInProgress = false; m_jobStatus = QLatin1String("Job paused..."); break; case Esri::ArcGISRuntime::JobStatus::Succeeded: m_viewshedInProgress = false; m_jobStatus = QLatin1String("Job succeeded"); // handle the results processResults(m_viewshedJob->result()); break; default: break; } DEBUG_LOG(m_logger) << LOGFUNCTION << " jobStatusChanged is now (m_jobStatus): " << m_jobStatus; if ((m_viewshedInProgress == false) && (viewshedInProgressOld != m_viewshedInProgress)) LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); // emit signals // emit viewshedInProgressChanged(); // emit jobStatusChanged(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } #if ACRGIS_VERSION >= 0x640E00 // 100.14.0 void ViewshedGeoProcessing::onJobStatusChangedWithJsParameter(const Esri::ArcGISRuntime::JobStatus &/*newStatus*/) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; onJobStatusChanged(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } #endif // ACRGIS_VERSION bool ViewshedGeoProcessing::processResults(Esri::ArcGISRuntime::GeoprocessingResult *results) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; if (!results) { ERROR_LOG(m_logger) << LOGFUNCTION << " no results"; return false; } const QMap &outputsMap = results->outputs(); if (outputsMap.isEmpty()) { ERROR_LOG(m_logger) << LOGFUNCTION << " no keys, outputsMap is empty "; return false; } // clear graphics: clearResultGraphics(); QString key; key = QStringLiteral("Output_polygon_features"); if (outputsMap.contains(key)) { // Get the results from the outputs as GeoprocessingFeatures Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures = static_cast(outputsMap[key]); return processFeatureResults(viewshedResultFeatures); } key = QStringLiteral("Output_raster"); if (outputsMap.contains(key)) { Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster = static_cast(outputsMap[key]); return processRasterResults(viewshedResultRaster); } ERROR_LOG(m_logger) << LOGFUNCTION << " failure, no known keyword found in outputsMap" << ", outputsMap.size() " << outputsMap.size(); return false; } bool ViewshedGeoProcessing::processFeatureResults(Esri::ArcGISRuntime::GeoprocessingFeatures *viewshedResultFeatures) { DEBUG_LOG(m_logger) << LOGFUNCTION << " processFeatureResults"; if (!viewshedResultFeatures) { ERROR_LOG(m_logger) << LOGFUNCTION << " no viewshedResultFeatures"; return false; } Esri::ArcGISRuntime::SimpleFillSymbol sfsVisi (Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, QColor(0, 226, 0, 255), this); Esri::ArcGISRuntime::SimpleFillSymbol sfsInVisi(Esri::ArcGISRuntime::SimpleFillSymbolStyle::Solid, QColor(226, 0, 0, 255), this); // Add all the features from the result feature set as a graphics to the map Esri::ArcGISRuntime::FeatureIterator features = viewshedResultFeatures->features()->iterator(); while (features.hasNext()) { Esri::ArcGISRuntime::Feature *feat = features.next(this); const QString &attKey(QLatin1String("gridcode")); if (feat->attributes()->containsAttribute(attKey)) { DEBUG_LOG(m_logger) << LOGFUNCTION << " attributes()->attributeValue=== " << feat->attributes()->attributeValue(attKey).toString(); if (feat->attributes()->attributeValue(attKey).toInt()) m_resultGraphics.append(new Esri::ArcGISRuntime::Graphic(feat->geometry(), &sfsVisi)); else m_resultGraphics.append(new Esri::ArcGISRuntime::Graphic(feat->geometry(), &sfsInVisi)); // ERROR_LOG(m_logger) << LOGFUNCTION << " attributeValue " << feat->attributes()->attributeValue(attKey).toInt(); } else ERROR_LOG(m_logger) << LOGFUNCTION << " no key attributeValue "; } // transfer from own container to resultsOverlay: m_resultsOverlay->graphics()->append(m_resultGraphics); return true; } bool ViewshedGeoProcessing::processRasterResults(Esri::ArcGISRuntime::GeoprocessingRaster *viewshedResultRaster) { DEBUG_LOG(m_logger) << LOGFUNCTION << " viewshedResultRaster -> url = " << viewshedResultRaster->url().toString(); connect(viewshedResultRaster, &Esri::ArcGISRuntime::GeoprocessingRaster::fetchFileCompleted, this, [this] (QUuid, const QString& filePath) { DEBUG_LOG(m_logger) << LOGFUNCTION << " filePath=" << filePath; Esri::ArcGISRuntime::Raster *raster = new Esri::ArcGISRuntime::Raster(filePath, this); if (!raster) { ERROR_LOG(m_logger) << LOGFUNCTION << " raster failed"; return; } connect(raster, &Esri::ArcGISRuntime::Raster::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) { ERROR_LOG(m_logger) << LOGFUNCTION << " raster EsriError occured: " << error; }); connect(raster, &Esri::ArcGISRuntime::Raster::loadStatusChanged, this, [this, raster] (Esri::ArcGISRuntime::LoadStatus loadStatus) { ERROR_LOG(m_logger) << LOGFUNCTION << " raster loadStatus=" << loadStatus; if (loadStatus == Esri::ArcGISRuntime::LoadStatus::Loaded) { Esri::ArcGISRuntime::RasterLayer *rl = new Esri::ArcGISRuntime::RasterLayer(raster, this); if (!rl) { ERROR_LOG(m_logger) << LOGFUNCTION << " rl failed"; return; } connect(rl, &Esri::ArcGISRuntime::RasterLayer::errorOccurred, this, [this] (const Esri::ArcGISRuntime::Error &error) { ERROR_LOG(m_logger) << LOGFUNCTION << " RasterLayer EsriError occured: " << error; }); connect(rl, &Esri::ArcGISRuntime::RasterLayer::loadStatusChanged, this, [this] (Esri::ArcGISRuntime::LoadStatus loadStatus) { ERROR_LOG(m_logger) << LOGFUNCTION << " RasterLayer loadStatus=" << loadStatus; }); DEBUG_LOG(m_logger) << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << m_mapImpl->getEsriMap()->operationalLayers()->size(); m_mapImpl->getEsriMap()->operationalLayers()->append(rl); DEBUG_LOG(m_logger) << LOGFUNCTION << " getEsriMap()->operationalLayers()->size" << m_mapImpl->getEsriMap()->operationalLayers()->size(); DEBUG_LOG(m_logger) << LOGFUNCTION << " rasterLayer loading ..."; rl->load(); } }); DEBUG_LOG(m_logger) << LOGFUNCTION << " raster loading ..."; raster->load(); }); Esri::ArcGISRuntime::TaskWatcher tw = viewshedResultRaster->fetchFile(QStringLiteral("/tmp/viewshedFetched.tif")); if (!tw.isValid()) { ERROR_LOG(m_logger) << LOGFUNCTION << " m_mapImpl failed"; return false; } return true; } void ViewshedGeoProcessing::clearGraphics() { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; clearInputGraphics(); clearExtentGraphics(); clearResultGraphics(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } void ViewshedGeoProcessing::createInputGraphic(const Esri::ArcGISRuntime::Point &origin) { if (!m_originGraphic) { m_originGraphic = new Esri::ArcGISRuntime::Graphic(this); m_resultsOverlay->graphics()->append(m_originGraphic); } m_originGraphic->setGeometry(origin); Esri::ArcGISRuntime::SimpleMarkerSymbol sms(Esri::ArcGISRuntime::SimpleMarkerSymbolStyle::Circle, QColor("red"), 12.0); m_originGraphic->setSymbol(&sms); m_originGraphic->setZIndex(std::numeric_limits::max()); } void ViewshedGeoProcessing::createExtentGraphic(const Esri::ArcGISRuntime::Point &origin, double radius) { if (!m_extentGraphic) { m_extentGraphic = new Esri::ArcGISRuntime::Graphic(this); m_resultsOverlay->graphics()->append(m_extentGraphic); } const Esri::ArcGISRuntime::Polygon &circlePoly = Esri::ArcGISRuntime::GeometryEngine::buffer(origin, radius); // circlePoly.parts().part(0).pointCount(); -> 96 m_extentGraphic->setGeometry(circlePoly); Esri::ArcGISRuntime::SimpleLineSymbol sls(Esri::ArcGISRuntime::SimpleLineSymbolStyle::Solid, QColor("red"), 3.0); m_extentGraphic->setSymbol(&sls); m_extentGraphic->setZIndex(std::numeric_limits::max()); } void ViewshedGeoProcessing::clearInputGraphics() { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; if (m_originGraphic) { m_resultsOverlay->graphics()->removeOne(m_originGraphic); delete m_originGraphic; m_originGraphic = nullptr; } TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } void ViewshedGeoProcessing::clearExtentGraphics() { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; if (m_extentGraphic) { m_resultsOverlay->graphics()->removeOne(m_extentGraphic); delete m_extentGraphic; m_extentGraphic = nullptr; } TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } void ViewshedGeoProcessing::clearResultGraphics() { TRACE_LOG(m_logger) << LOGFUNCTION << " START" << this; for (auto graphic : qAsConst(m_resultGraphics)) { m_resultsOverlay->graphics()->removeOne(graphic); delete graphic; } m_resultGraphics.clear(); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } void ViewshedGeoProcessing::onErrorOccuredGeoprocessingTask(const Esri::ArcGISRuntime::Error &error) { QString senderName; Esri::ArcGISRuntime::GeoprocessingTask *senderObj = qobject_cast(sender()); if (senderObj) senderName = senderObj->objectName(); ERROR_LOG(m_logger) << LOGFUNCTION << " GeoprocessingTask -> EsriError occured: " << error << ", sender= '" << senderName << "'"; } void ViewshedGeoProcessing::onErrorOccuredGeoprocessingJob(const Esri::ArcGISRuntime::Error &error) { QString senderName; Esri::ArcGISRuntime::GeoprocessingJob *senderObj = qobject_cast(sender()); if (senderObj) senderName = senderObj->objectName(); ERROR_LOG(m_logger) << LOGFUNCTION << " GeoprocessingJob -> EsriError occured: " << error << ", sender= '" << senderName << "'"; } } // namespace SituationMap