//Must be on the QueuedTaskvarurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varcreateParam=new FeatureLayerCreationParams(new Uri(url)){IsVisible=false//turned off by default};varstreamLayer= LayerFactory.Instance.CreateLayer<StreamLayer>(createParam, map);//or use "original" create layer (will be visible by default)Uriuri=new Uri(url);streamLayer= LayerFactory.Instance.CreateLayer(uri, map)as StreamLayer;
streamLayer.SetVisibility(false);//turn off
Create a stream layer with a definition query
//Must be on the QueuedTaskvarurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varlyrCreateParam=new FeatureLayerCreationParams(new Uri(url)){IsVisible=true,//At 2.x - DefinitionFilter = new CIMDefinitionFilter()//{// DefinitionExpression = "RWY = '29L'",// Name = "Runway"//}DefinitionQuery=new DefinitionQuery(whereClause:"RWY = '29L'", name:"Runway")};varstreamLayer= LayerFactory.Instance.CreateLayer<StreamLayer>(lyrCreateParam, map);
Create a stream layer with a simple renderer
varurl=@"https://geoeventsample1.esri.com:6443/arcgis/rest/services/LABus/StreamServer";varuri=new Uri(url, UriKind.Absolute);//Must be on QueuedTask!varcreateParams=new FeatureLayerCreationParams(uri){RendererDefinition=new SimpleRendererDefinition(){SymbolTemplate= SymbolFactory.Instance.ConstructPointSymbol(
ColorFactory.Instance.BlueRGB,12,
SimpleMarkerStyle.Pushpin).MakeSymbolReference()}};varstreamLayer= LayerFactory.Instance.CreateLayer<StreamLayer>(
createParams, map);
Setting a unique value renderer for latest observations
varurl=@"https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varuri=new Uri(url, UriKind.Absolute);//Must be on QueuedTask!varcreateParams=new FeatureLayerCreationParams(uri){IsVisible=false};varstreamLayer= LayerFactory.Instance.CreateLayer<StreamLayer>(
createParams, map);//Define the unique values by handvaruvr=new CIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},UseDefaultSymbol=true,DefaultLabel="Others",DefaultSymbol= SymbolFactory.Instance.ConstructPointSymbol(
CIMColor.CreateRGBColor(185,185,185),8, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};varclasses=newList<CIMUniqueValueClass>();//add in classes - one for ACTYPE of 727, one for DC 9
classes.Add(new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol= SymbolFactory.Instance.ConstructPointSymbol(
ColorFactory.Instance.RedRGB,10, SimpleMarkerStyle.Hexagon).MakeSymbolReference()});
classes.Add(new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"DC9"}}},Visible=true,Label="DC 9",Symbol= SymbolFactory.Instance.ConstructPointSymbol(
ColorFactory.Instance.GreenRGB,10, SimpleMarkerStyle.Hexagon).MakeSymbolReference()});//add the classes to a groupvargroups=newList<CIMUniqueValueGroup>(){new CIMUniqueValueGroup(){Classes= classes.ToArray()}};//add the groups to the renderer
uvr.Groups = groups.ToArray();//Apply the renderer (for current observations)
streamLayer.SetRenderer(uvr);
streamLayer.SetVisibility(true);//turn on the layer
//spatial or non-spatial?if(streamLayer.TrackType == TrackType.AttributeOnly){//this is a non-spatial stream layer}else{//this must be a spatial stream layer}
Check the Stream Layer connection state
if(!streamLayer.IsStreamingConnectionOpen)//Must be on QueuedTask!
streamLayer.StartStreaming();
Start and stop streaming
//Must be on QueuedTask!//Start...
streamLayer.StartStreaming();//Stop...
streamLayer.StopStreaming();
Delete all current and previous observations
//Must be on QueuedTask!//Must be called on the feature classusing(varrfc= streamLayer.GetFeatureClass())
rfc.Truncate();
Get the Track Id Field
if(streamLayer.IsTrackAware){vartrackField= streamLayer.TrackIdFieldName;//TODO use the field name}
Get The Track Type
vartrackType= streamLayer.TrackType;switch(trackType){//TODO deal with tracktypecase TrackType.None:case TrackType.AttributeOnly:case TrackType.Spatial:break;}
Set the Maximum Count of Previous Observations to be Stored in Memory
//Must be on QueuedTask//Set Expiration Method and Max Expiration Countif(streamLayer.GetExpirationMethod()!= FeatureExpirationMethod.MaximumFeatureCount)
streamLayer.SetExpirationMethod(FeatureExpirationMethod.MaximumFeatureCount);
streamLayer.SetExpirationMaxCount(15);//FYIif(streamLayer.IsTrackAware){//MaxCount is per track! otherwise for the entire layer}
Set the Maximum Age of Previous Observations to be Stored in Memory
//Must be on QueuedTask//Set Expiration Method and Max Expiration Ageif(streamLayer.GetExpirationMethod()!= FeatureExpirationMethod.MaximumFeatureAge)
streamLayer.SetExpirationMethod(FeatureExpirationMethod.MaximumFeatureAge);//set to 12 hours (max is 24 hours)
streamLayer.SetExpirationMaxAge(new TimeSpan(12,0,0));//FYIif(streamLayer.IsTrackAware){//MaxAge is per track! otherwise for the entire layer}
Set Various Stream Layer properties via the CIM
//The layer must be track aware and spatialif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask//get the CIM Definitionvardef= streamLayer.GetDefinition()as CIMFeatureLayer;//set the number of previous observations,
def.PreviousObservationsCount =(int)streamLayer.GetExpirationMaxCount()-1;//set show previous observations and track lines to true
def.ShowPreviousObservations =true;
def.ShowTracks =true;//commit the changes
streamLayer.SetDefinition(def);
Rendering
Defining a unique value renderer definition
varuvrDef=new UniqueValueRendererDefinition(){ValueFields=newList<string>{"ACTYPE"},SymbolTemplate= SymbolFactory.Instance.ConstructPointSymbol(
ColorFactory.Instance.RedRGB,10, SimpleMarkerStyle.Hexagon).MakeSymbolReference(),ValuesLimit=5};//Note: CreateRenderer can only create value classes based on//the current events it has received
streamLayer.SetRenderer(streamLayer.CreateRenderer(uvrDef));
Setting a unique value renderer for latest observations
//Define the classes by hand to avoid using CreateRenderer(...)CIMUniqueValueClassuvcB727=new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol= SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(255,0,0),8, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueClassuvcD9=new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"DC9"}}},Visible=true,Label="DC 9",Symbol= SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(0,255,0),8, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};//Assign the classes to a groupCIMUniqueValueGroupuvGrp=new CIMUniqueValueGroup(){Classes=new CIMUniqueValueClass[]{ uvcB727, uvcD9 }};//assign the group to the renderervarUVrndr=new CIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},Groups=new CIMUniqueValueGroup[]{ uvGrp },UseDefaultSymbol=true,DefaultLabel="Others",DefaultSymbol= SymbolFactory.Instance.ConstructPointSymbol(
CIMColor.CreateRGBColor(185,185,185),8, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};//set the renderer. Depending on the current events received, the//layer may or may not have events for each of the specified//unique value classes
streamLayer.SetRenderer(UVrndr);
Setting a unique value renderer for previous observations
//The layer must be track aware and spatialif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask!//Define unique value classes same as we do for current observations//or use "CreateRenderer(...)" to assign them automaticallyCIMUniqueValueClassuvcB727Prev=new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol= SymbolFactory.Instance.ConstructPointSymbol(
CIMColor.CreateRGBColor(255,0,0),4, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueClassuvcD9Prev=new CIMUniqueValueClass(){Values=new CIMUniqueValue[]{new CIMUniqueValue(){FieldValues=newstring[]{"DC9"}}},Visible=true,Label="DC 9",Symbol= SymbolFactory.Instance.ConstructPointSymbol(
CIMColor.CreateRGBColor(0,255,0),4, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueGroupuvGrpPrev=new CIMUniqueValueGroup(){Classes=new CIMUniqueValueClass[]{ uvcB727Prev, uvcD9Prev }};varUVrndrPrev=new CIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},Groups=new CIMUniqueValueGroup[]{ uvGrpPrev },UseDefaultSymbol=true,DefaultLabel="Others",DefaultSymbol= SymbolFactory.Instance.ConstructPointSymbol(
CIMColor.CreateRGBColor(185,185,185),4, SimpleMarkerStyle.Hexagon).MakeSymbolReference()};
streamLayer.SetRenderer(UVrndr, FeatureRendererTarget.PreviousObservations);
Setting a simple renderer to draw track lines
//The layer must be track aware and spatialif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask!//Note: only a simple renderer with solid line symbol is supported for track //line renderervartrackRenderer=new SimpleRendererDefinition(){SymbolTemplate= SymbolFactory.Instance.ConstructLineSymbol(
ColorFactory.Instance.BlueRGB,2, SimpleLineStyle.Solid).MakeSymbolReference()};
streamLayer.SetRenderer(
streamLayer.CreateRenderer(trackRenderer),
FeatureRendererTarget.TrackLines);
Check Previous Observation and Track Line Visibility
//The layer must be track aware and spatial for these settings//to have an effectif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTaskif(!streamLayer.AreTrackLinesVisible)
streamLayer.SetTrackLinesVisibility(true);if(!streamLayer.ArePreviousObservationsVisible)
streamLayer.SetPreviousObservationsVisibility(true);
Make Track Lines and Previous Observations Visible
//The layer must be track aware and spatial for these settings//to have an effectif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask//Note: Setting PreviousObservationsCount larger than the //"SetExpirationMaxCount()" has no effect
streamLayer.SetPreviousObservationsCount(6);if(!streamLayer.AreTrackLinesVisible)
streamLayer.SetTrackLinesVisibility(true);if(!streamLayer.ArePreviousObservationsVisible)
streamLayer.SetPreviousObservationsVisibility(true);
Retrieve the current observation renderer
//Must be on QueuedTask!varrenderer= streamLayer.GetRenderer();
Retrieve the previous observation renderer
//The layer must be track aware and spatialif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask!varprev_renderer= streamLayer.GetRenderer(
FeatureRendererTarget.PreviousObservations);
Retrieve the track lines renderer
//The layer must be track aware and spatialif(streamLayer.TrackType != TrackType.Spatial)return;//Must be on QueuedTask!vartrack_renderer= streamLayer.GetRenderer(
FeatureRendererTarget.TrackLines);
Subscribe and SearchAndSubscribe
Search And Subscribe for Streaming Data
await QueuedTask.Run(async()=>{//query filter can be null to search and retrieve all rows//true means recycling cursorusing(varrc= streamLayer.SearchAndSubscribe(qfilter,true)){//waiting for new features to be streamed//default is no cancellationwhile(await rc.WaitForRowsAsync()){while(rc.MoveNext()){using(varrow= rc.Current){//determine the origin of the row eventswitch(row.GetRowSource()){case RealtimeRowSource.PreExisting://pre-existing row at the time of subscribecontinue;case RealtimeRowSource.EventInsert://row was inserted after subscribecontinue;case RealtimeRowSource.EventDelete://row was deleted after subscribecontinue;}}}}}//row cursor is disposed. row cursor is unsubscribed//....or....//Use the feature class instead of the layerusing(varrfc= streamLayer.GetFeatureClass()){//non-recycling cursor - 2nd param "false"using(varrc= rfc.SearchAndSubscribe(qfilter,false)){//waiting for new features to be streamed//default is no cancellationwhile(await rc.WaitForRowsAsync()){//etc}}}});
Search And Subscribe With Cancellation
await QueuedTask.Run(async()=>{//Recycling cursor - 2nd param "true"//or streamLayer.Subscribe(qfilter, true) to just subscribeusing(varrc= streamLayer.SearchAndSubscribe(qfilter,true)){//auto-cancel after 20 secondsvarcancel=new CancellationTokenSource(new TimeSpan(0,0,20));//catch TaskCanceledExceptiontry{while(await rc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrow= rc.Current){//etc}}}}catch(TaskCanceledException){//Handle cancellation as needed} cancel.Dispose();}});
Explicitly Cancel WaitForRowsAsync
//somewhere in our code we create a CancellationTokenSourcevarcancel=new CancellationTokenSource();//...//call cancel on the CancellationTokenSource anywhere in//the add-in, assuming the CancellationTokenSource is in scopeif(SomeConditionForCancel)
cancel.Cancel();//<-- will cancel the token//Within QueuedTask we are subscribed! streamLayer.Subscribe() or SearchAndSubscribe()try{//TaskCanceledException will be thrown when the token is cancelledwhile(await rc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrow= rc.Current){//etc}}}}catch(TaskCanceledException){//Handle cancellation as needed}
cancel.Dispose();
Realtime FeatureClass
Connect to a real-time feature class from a real-time datastore
varurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";await QueuedTask.Run(()=>{varrealtimeServiceConProp=new RealtimeServiceConnectionProperties(new Uri(url), RealtimeDatastoreType.StreamService);using(varrealtimeDatastore=new RealtimeDatastore(realtimeServiceConProp)){//A Realtime data store only contains **one** Realtime feature class (or table)varname= realtimeDatastore.GetTableNames().First();using(varrealtimeFeatureClass= realtimeDatastore.OpenTable(name)as RealtimeFeatureClass){//feature class, by default, is not streaming (opposite of the stream layer) realtimeFeatureClass.StartStreaming();//TODO use the feature class//...}}});
Get the Track Id Field from the Realtime Feature class
//Must be on QueuedTaskusing(varrfc= streamLayer.GetFeatureClass())using(varrfc_def= rfc.GetDefinition()){if(rfc_def.HasTrackIDField()){varfld_name= rfc_def.GetTrackIDField();}}
Subscribe to Streaming Data
//Note: with feature class we can also use a System Task to subscribe and//process rowsawait QueuedTask.Run(async()=>{// or var rfc = realtimeDatastore.OpenTable(name) as RealtimeFeatureClassusing(varrfc= streamLayer.GetFeatureClass()){//non-recycling cursor - 2nd param "false"//subscribe, pre-existing rows are not searchedusing(varrc= rfc.Subscribe(qfilter,false)){SpatialQueryFilterspatialFilter=new SpatialQueryFilter();//waiting for new features to be streamed//default is no cancellationwhile(await rc.WaitForRowsAsync()){while(rc.MoveNext()){using(varrow= rc.Current){switch(row.GetRowSource()){case RealtimeRowSource.EventInsert://getting geometry from new events as they arrivePolygonpoly=((RealtimeFeature)row).GetShape()as Polygon;//using the geometry to select features from another feature layer spatialFilter.FilterGeometry =poly;//project poly if needed... countyFeatureLayer.Select(spatialFilter);continue;default:continue;}}}}}//row cursor is disposed. row cursor is unsubscribed}});
Search Existing Data and Subscribe for Streaming Data
//Note we can use System Task with the Realtime feature class//for subscribeawait System.Threading.Tasks.Task.Run(async()=>// or use ... QueuedTask.Run(){using(varrfc= streamLayer.GetFeatureClass()){//non-recycling cursor - 2nd param "false"using(varrc= rfc.SearchAndSubscribe(qfilter,false)){//waiting for new features to be streamed//default is no cancellationwhile(await rc.WaitForRowsAsync()){//pre-existing rows will be retrieved that were searchedwhile(rc.MoveNext()){using(varrow= rc.Current){varrow_source= row.GetRowSource();switch(row_source){case RealtimeRowSource.EventDelete://TODO - handle deletesbreak;case RealtimeRowSource.EventInsert://TODO handle insertsbreak;case RealtimeRowSource.PreExisting://TODO handle pre-existing rowsbreak;}}}}}//row cursor is disposed. row cursor is unsubscribed}});
Search And Subscribe With Cancellation
await System.Threading.Tasks.Task.Run(async()=>// or use ... QueuedTask.Run(){using(varrfc= streamLayer.GetFeatureClass()){//Recycling cursor - 2nd param "true"using(varrc= rfc.SearchAndSubscribe(qfilter,true)){//auto-cancel after 20 secondsvarcancel=new CancellationTokenSource(new TimeSpan(0,0,20));//catch TaskCanceledExceptiontry{while(await rc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrecord= rc.Current){//etc}}}}catch(TaskCanceledException){//Handle cancellation as needed} cancel.Dispose();}}});