Extending ArcGIS Pro with .NET and
Python: Interactive Analytics
Carlos A. Osorio-Murillo
Mark Janikas
Introduction
• ArcGIS Pro is highly customizable. From an application perspective, .NET can be used to
construct buttons, ribbons and tool specific user interfaces. Through the use of Python and
Geoprocessing, your desired functionality can run both in demand and interactively with
the .NET extension you created. This session introduces the key concepts necessary to
get started with extending your ArcGIS Pro Application and provides a clear case study of
interactive analytics to show you the power of the strategies outlined.
Objectives.Net & Python
• Integrate .Net and Python for improving workflows
• Powering a Density-Based Clustering method (OPTICS) through an Add-in
• Quick demo how to create an Add-in Visual Studio
Unsupervised learning methods
Density-Based
Clustering
DBSCAN OPTICS HDBSCAN
Find representavives groups
Which criteria depends of user
Partition
(Kmean)
Spatially constrain
(SKATER)
Density-based Clustering
Three clustering algorithms
Fast
Automatic Detection
Interactive
OPTICS Process
• Produce a special order of the database with a density-base clustering structure
• God for both automatic and interactive cluster analysis, including finding intrinsic
clustering structure
• Can be represented graphically (Plot)
arcpy.DensityBasedClustering_stats()
OPTICS Processarcpy.DensityBasedClustering_stats()
(Generate Reachability information)
Cluster detection
Map clustering
(Applying color
exclusion schema)
Python Tool for Detecting cluster
def execute(self, parameters, messages):import SSCluster as SSCimport SSDataObject as SSDOimport numpy as NUM
#### Get Parameters ####inputFC = parameters[0].valueAsText minPoints = int(parameters[1].valueAsText)tolerance = int(parameters[2].valueAsText)threshold = int(parameters[3].valueAsText)
#### Allow overwrite Output ###ARCPY.env.overwriteOutput = True
#### Load Data ####ssdo = SSDO.SSDataObject(inputFC)ssdo.obtainData(fields = ["REACHORDER","REACHDIST"])
#### Sort Reachability Order ###ord = ssdo.fields["REACHORDER"].data
#### Get Reachability Distances ####reachValues = ssdo.fields["REACHDIST"].data
#### Sorting by Index ####data = NUM.zeros((len(reachValues), 2), dtype= float)data[:, 0] = reachValues data[:, 1] = ordodata = data[data[:,1].argsort()]
OPTICS as an
extension of DBSCAN
Distance Threshold
Not enough minimum number
of points to be considered
Clusters
#### Function to get DBSCAN Clusters ####def getDBSCAN(distances, threshold, minPoints, orderValues):
clusterArr = NUM.ones(len(distances), dtype = int)*-1indices = distances <= thresholdini = end = 0clusterIndex = 1c = 1for i in NUM.arange(len(indices)-1):
if indices[i] and indices[i] == indices[i+1]:if c == 1:
ini = ic += 1end = i + 1
else:if (end-ini) >= minPoints-1:
clusterArr[ini:end+1] = clusterIndexclusterIndex += 1ini = end = 0c = 1
return clusterArr[orderValues]
OPTICS Tolerance
Merged clusters
Cluster shape
import SSCluster as SSC#### Initialize Detect Zone Class ####zones = SSC.DetectZones()
#### Get Cluster By Tolerance ###idClusters, idColor = zones.getClusters(reachValues,
orderValues,minPoints, tolerance)
Arcgis Pro Python Module
Interactive Clustering detection
through Add-in
and Python
Addin
DemoDetecting clusters
https://github.com/Esri/ExampleDotNetAndPythonForAnalytics
Add-in Creation using Visual Studio
Resources
https://github.com/Esri/arcgis-pro-sdk/wiki/ProGuide-
Build-Your-First-Add-in
Add-in Creation using Visual Studio
Creating an Add-in Demo
Design Control WPFComboBox
Slider
TextBox
<UserControl x:Class="InteractiveAnalytics.Dockpane1View"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:ui="clr-namespace:InteractiveAnalytics"xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"mc:Ignorable="d"d:DesignHeight="400" d:DesignWidth="300"d:DataContext="{Binding Path=ui.Dockpane1ViewModel}">
…..
Dockpanel
Add-in Events
Add-In UI App
GetLayersInMap()
GeoprocessingToolbox/Tool
UpdateClusters()
Python
Update Dataset
Update Simbology()
Async method
Time line
GetLayersInMap()
internal async void GetLayersInMap(){
if (MapView.Active == null)return;
var currentMap = MapView.Active.Map;var layers = currentMap.GetLayersAsFlattenedList();List<FeatureLayer> featureLayers = new List<FeatureLayer>();foreach (Layer lyr in layers){
if (lyr is FeatureLayer){FeatureLayer flyr = lyr as FeatureLayer;bool check = true;check = (flyr.ShapeType == esriGeometryType.esriGeometryPoint);if (check){
/// Layer should contain Reachability distance fieldint hasClusteredOpticLayer = await QueuedTask.Run<int>(() =>{return flyr.GetTable().GetDefinition().FindField("REACHDIST");
});
if (hasClusteredOpticLayer >= 0)featureLayers.Add(flyr);
}}
}_layers = featureLayers;NotifyPropertyChanged(() => Layers);
}
Obtain Point Layers with
a specific field
await QueuedTask.Run<int>(() =>{
return flyr.GetTable().GetDefinition().FindField("REACHDIST");});
check = (flyr.ShapeType == esriGeometryType.esriGeometryPoint);
UpdateClusters()
internal async void UpdateCluster(bool isTolerance){
if (MapView.Active == null)return;
// Get Layer Namestring inputFC = SelectedLayer.Name;int minPoints = 2;bool parsed = Int32.TryParse(MinPoints, out minPoints);// Set PYT path string tool_path ="C:\\PATH\\UpdateCluster.pyt\\UpdateClusterTool";IReadOnlyList<string> args= null;if (isTolerance){
/// Arguments for executing process using Toleranceargs = Geoprocessing.MakeValueArray(inputFC, minPoints, ValueSlider, -1);
}else{
/// Arguments for executing process using Thresholdargs = Geoprocessing.MakeValueArray(inputFC, minPoints, -1, ValueThreshold);
}
Task<IGPResult> task;/// Execute the Tool in the python toolboxtask = Geoprocessing.ExecuteToolAsync(tool_path, args, flags: GPExecuteToolFlags.AddToHistory);
task = Geoprocessing.ExecuteToolAsync(tool_path, args, flags: GPExecuteToolFlags.AddToHistory);
string tool_path ="C:\\PATH\\UpdateCluster.pyt\\UpdateClusterTool";
Update Simbology
await QueuedTask.Run( () =>{/// Search for a specific Symbol/// Other styles Arcgis/Resouces/Styles/Styles.stylx SQLite DBSymbolStyleItem symbolStyleItem = (SymbolStyleItem)style.LookupItem(StyleItemType.PointSymbol, "Circle 1_Shapes_3");pointSymbol = (CIMPointSymbol)symbolStyleItem.Symbol;
/// Cluster Ids based in Color Schemaint[] ids = new int[] { -1, 1, 2, 3, 4, 5, 6, 7, 8 };
/// Set Colorsstring[] colors = new string[]{ "156,156,156", "166,206,227", "31,120,180", "178,223,138",
"51,160,44", "251,154,153", "227,26,28", "253,191,111","255,127,0" };
/// Color FieldString[] fields = new string[] { "COLOR_ID" };
/// Make a reference of the point symbolCIMSymbolReference symbolPointTemplate = pointSymbol.MakeSymbolReference();
/// Get definition of type symbology unique valuesUniqueValueRendererDefinition uniqueValueRendererDef = new UniqueValueRendererDefinition(fields, symbolPointTemplate, null, symbolPointTemplate, false);
/// Get Current renderer of the Selected Layer CIMUniqueValueRenderer renderer = (CIMUniqueValueRenderer)SelectedLayer.CreateRenderer(uniqueValueRendererDef);CIMUniqueValueClass[] newClasses = new CIMUniqueValueClass[colors.Count()];
/// Get Point Symbol as string for creating other point colors string point = pointSymbol.ToXml();
/// Create Each Color for (int i = 0; i < ids.Length; i++){CIMPointSymbol npoint = CIMPointSymbol.FromXml(point);if (ids[i] == -1){npoint.SetSize(4);
}else{npoint.SetSize(6);
}CIMSymbolReference symbolPointTemplatei = npoint.MakeSymbolReference();newClasses[i] = new CIMUniqueValueClass();newClasses[i].Values = new CIMUniqueValue[1];newClasses[i].Values[0] = new CIMUniqueValue();newClasses[i].Values[0].FieldValues = new string[1];newClasses[i].Values[0].FieldValues[0] = ids[i].ToString();newClasses[i].Label = ids[i].ToString();newClasses[i].Symbol = symbolPointTemplatei;var color = colors[i].Split(',');double r = Convert.ToDouble(color[0]);double g = Convert.ToDouble(color[1]);double b = Convert.ToDouble(color[2]);newClasses[i].Symbol.Symbol.SetColor(CIMColor.CreateRGBColor(r, g, b));
}/// Add Colors into the rendererrenderer.Groups[0].Classes = newClasses;
/// Apply new renderer in the layerSelectedLayer.SetRenderer(renderer);
//SelectedLayer.RecalculateRenderer();});
UniqueValueRendererDefinition uniqueValueRendererDef = new UniqueValueRendererDefinition(fields, symbolPointTemplate, null, symbolPointTemplate, false);
CIMUniqueValueRenderer renderer = (CIMUniqueValueRenderer)SelectedLayer.CreateRenderer(uniqueValueRendererDef);
SymbolStyleItem symbolStyleItem = (SymbolStyleItem)style.LookupItem(StyleItemType.PointSymbol, "Circle 1_Shapes_3");
renderer.Groups[0].Classes = newClasses;SelectedLayer.SetRenderer(renderer);
Conclusions
- Add-in can improve Geoprocessing Tools (Density-Based Clustering)
- Easy way to join Python Scripts and .Net through an Add-in
- ArcGIS Pro SDK Extension in Visual Studio great tool to initial Add-in
Thank you
https://github.com/Esri/ExampleDotNetAndPythonForAnalytics
Top Related