How can we improve the performance of a mouse over on the Active map view when using await QueuedTask.Run(()?
protected string GetPin(MapPoint mapPoint)
{
await QueuedTask.Run(() =>
{
var layer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(l => l.Name == ArcProMapDataLink.Properties.Settings.Default.LAYER).FirstOrDefault();
if (layer != null)
{
EnvelopeBuilder envBuilder = new EnvelopeBuilder(mapPoint, mapPoint);
var selection = MapView.Active.SelectFeatures(envBuilder.ToGeometry()).FirstOrDefault(); /// This selection is slow on mouse move
}});
}
Thank you!
Curt
Hi Curt, try this: The key is that the code attempts to eliminate any unnecessary selections either by checking the amount of movement of the mouse or by comparing the current mouse position with the polygon of the current parcel. Only if the mouse has moved the pre-requisite amount that would result in a new parcel being selected does the code actually do the selection...
Note: one important assumption - that the map and parcel layer are in the _same_ projection...if they have different SRs then the "GeometryEngine.Instance.Intersects" statement will fail. The map point would have to be projected first and that could be an expensive operation. To ensure that they do have the same projection just change the Map CoordinateSystem to be the same as Parcels via the Map Properties dialog.
internal class SelectOnMove1 : MapTool
{
private Point _lastLocation;
private FeatureLayer _parcelLayer = null;
private static readonly object _resultsLock = new object();
Dictionary<string, object> _currentResults = null;
private int _deltaPixels = 0;
public SelectOnMove1()
{
IsSketchTool = true;
SketchType = SketchGeometryType.Rectangle;
SketchOutputMode = SketchOutputMode.Map;
}
protected override Task OnToolActivateAsync(bool active)
{
_parcelLayer = MapView.Active.Map.GetLayersAsFlattenedList()
.FirstOrDefault(l => l.Name == "Parcels") as FeatureLayer;
if (_deltaPixels == 0)
_deltaPixels = SelectionEnvironment.SelectionTolerance;
return base.OnToolActivateAsync(active);
}
protected override Task OnToolDeactivateAsync(bool hasMapViewChanged)
{
_parcelLayer = null;
return base.OnToolDeactivateAsync(hasMapViewChanged);
}
protected async override void OnToolMouseMove(MapViewMouseEventArgs e)
{
if (_parcelLayer == null)
return;
//use SelectionEnvironment.SelectionTolerance but change this as needed
//....default is usually 3...
if (_lastLocation.X >= e.ClientPoint.X - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels &&
_lastLocation.Y >= e.ClientPoint.Y - _deltaPixels &&
_lastLocation.X <= e.ClientPoint.X + _deltaPixels)
return;
_lastLocation = e.ClientPoint;
//Get the feature attributes or null
var pin = await QueuedTask.Run(() =>
{
//Make sure that the Map spatial reference is set to the
//_same_ SR as the parcels layer!
var mpt = MapView.Active.ClientToMap(e.ClientPoint);
var strap = "";
//check against the shape first - note: we are assuming that
//the mpt and "poly" SRs are the same!
if (_currentResults != null)
{
strap = (string)_currentResults["STRAP"];
//is the point in the current feature extent?
var extent = _currentResults["ENVELOPE"] as Envelope;
if (mpt.X <= extent.XMax &&
mpt.X >= extent.XMin &&
mpt.Y <= extent.YMax &&
mpt.Y >= extent.YMin)
{
//see if we are still selecting the same feature
var poly = _currentResults["SHAPE"] as Polygon;
if (poly != null && GeometryEngine.Instance.Intersects(poly, mpt))
return strap;
}
}
//This is for the selection + highlight
var sqf = new SpatialQueryFilter()
{
FilterGeometry = mpt,
SpatialRelationship = SpatialRelationship.Intersects,
SubFields = "OBJECTID"
};
//This is the slowest part...
var sel = _parcelLayer.Select(sqf);
if (sel.GetCount() == 0)
return strap;
var oid = sel.GetObjectIDs().First();
if (_currentResults != null)
{
var curr_oid = (long)_currentResults["OBJECTID"];
if (oid == curr_oid)
return strap;
}
var attrib = Module1.Current.GetParcelRecord(_parcelLayer, oid);
_currentResults = attrib;
strap = (string)_currentResults["STRAP"] ?? "null";
return strap;
});
//Do something with the results...
var info = $"Current: STRAP:{pin}";
//etc
}
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
return base.OnSketchCompleteAsync(geometry);
}
}
From the Module:
internal class Module1 : Module
{
private static Module1 _this = null;
private Dictionary<long, Dictionary<string, object>> _cache = new Dictionary<long, Dictionary<string, object>>();
private bool _useCache = true;
/// <summary>
/// Retrieve the singleton instance to this module here
/// </summary>
public static Module1 Current
{
get
{
return _this ?? (_this = (Module1)FrameworkApplication.FindModule("DoMouseMove_Module"));
}
}
public Dictionary<string, object> GetParcelRecord(BasicFeatureLayer parcels, long oid)
{
if (_useCache && _cache.ContainsKey(oid))
{
return _cache[oid];
}
var qf = new QueryFilter()
{
SubFields = "SHAPE,STRAP",
ObjectIDs = new List<long> { oid }
};
var rc = parcels.Search(qf);
rc.MoveNext();
var dict = new Dictionary<string, object>();
dict["OBJECTID"] = oid;
dict["SHAPE"] = rc.Current["SHAPE"] as Geometry;
dict["ENVELOPE"] = ((Geometry)rc.Current["SHAPE"]).Extent;
dict["STRAP"] = rc.Current["STRAP"] as string ?? "null";
rc.Dispose();
if (_useCache)
{
_cache[oid] = dict;
}
return dict;
}
#region Overrides
/// <summary>
/// Called by Framework when ArcGIS Pro is closing
/// </summary>
/// <returns>False to prevent Pro from closing, otherwise True</returns>
protected override bool CanUnload()
{
//TODO - add your business logic
//return false to ~cancel~ Application close
return true;
}
#endregion Overrides
}
}
Fantastic Charles! Works great!
Your generosity and the support from ESRI (Jack) is untouchable in the software industry.
Thank you.
Curt