//Must be on the QueuedTaskvarurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varcreateParam=newFeatureLayerCreationParams(newUri(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=newUri(url);streamLayer=LayerFactory.Instance.CreateLayer(uri,map)asStreamLayer;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=newFeatureLayerCreationParams(newUri(url)){IsVisible=true,DefinitionFilter=newCIMDefinitionFilter(){DefinitionExpression="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=newUri(url,UriKind.Absolute);//Must be on QueuedTask!varcreateParams=newFeatureLayerCreationParams(uri){RendererDefinition=newSimpleRendererDefinition(){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=newUri(url,UriKind.Absolute);//Must be on QueuedTask!varcreateParams=newFeatureLayerCreationParams(uri){IsVisible=false};varstreamLayer=LayerFactory.Instance.CreateLayer<StreamLayer>(createParams,map);//Define the unique values by handvaruvr=newCIMUniqueValueRenderer(){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 9classes.Add(newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol=SymbolFactory.Instance.ConstructPointSymbol(ColorFactory.Instance.RedRGB,10,SimpleMarkerStyle.Hexagon).MakeSymbolReference()});classes.Add(newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){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>(){newCIMUniqueValueGroup(){Classes=classes.ToArray()}};//add the groups to the rendereruvr.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 tracktypecaseTrackType.None:caseTrackType.AttributeOnly:caseTrackType.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(newTimeSpan(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()asCIMFeatureLayer;//set the number of previous observations, def.PreviousObservationsCount=(int)streamLayer.GetExpirationMaxCount()-1;//set show previous observations and track lines to truedef.ShowPreviousObservations=true;def.ShowTracks=true;//commit the changesstreamLayer.SetDefinition(def);
Rendering
Defining a unique value renderer definition
varuvrDef=newUniqueValueRendererDefinition(){ValueFields=newstring[]{"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 receivedstreamLayer.SetRenderer(streamLayer.CreateRenderer(uvrDef));
Setting a unique value renderer for latest observations
//Define the classes by hand to avoid using CreateRenderer(...)CIMUniqueValueClassuvcB727=newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol=SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(255,0,0),8,SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueClassuvcD9=newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){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=newCIMUniqueValueGroup(){Classes=newCIMUniqueValueClass[]{uvcB727,uvcD9}};//assign the group to the renderervarUVrndr=newCIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},Groups=newCIMUniqueValueGroup[]{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 recieved, the//layer may or may not have events for each of the specified//unique value classesstreamLayer.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=newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol=SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(255,0,0),4,SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueClassuvcD9Prev=newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){FieldValues=newstring[]{"DC9"}}},Visible=true,Label="DC 9",Symbol=SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(0,255,0),4,SimpleMarkerStyle.Hexagon).MakeSymbolReference()};CIMUniqueValueGroupuvGrpPrev=newCIMUniqueValueGroup(){Classes=newCIMUniqueValueClass[]{uvcB727Prev,uvcD9Prev}};varUVrndrPrev=newCIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},Groups=newCIMUniqueValueGroup[]{uvGrpPrev},UseDefaultSymbol=true,DefaultLabel="Others",DefaultSymbol=SymbolFactory.Instance.ConstructPointSymbol(CIMColor.CreateRGBColor(185,185,185),4,SimpleMarkerStyle.Hexagon).MakeSymbolReference()};
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=newSimpleRendererDefinition(){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 effectstreamLayer.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
awaitQueuedTask.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(awaitrc.WaitForRowsAsync()){while(rc.MoveNext()){using(varrow=rc.Current){//determine the origin of the row eventswitch(row.GetRowSource()){caseRealtimeRowSource.PreExisting://pre-existing row at the time of subscribecontinue;caseRealtimeRowSource.EventInsert://row was inserted after subscribecontinue;caseRealtimeRowSource.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(awaitrc.WaitForRowsAsync()){//etc}}}});
Search And Subscribe With Cancellation
awaitQueuedTask.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=newCancellationTokenSource(newTimeSpan(0,0,20));//catch TaskCanceledExceptiontry{while(awaitrc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrow=rc.Current){//etc}}}}catch(TaskCanceledExceptiontce){//Handle cancellation as needed}cancel.Dispose();}});
Explicitly Cancel WaitForRowsAsync
//somewhere in our code we create a CancellationTokenSourcevarcancel=newCancellationTokenSource();//...//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(awaitrc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrow=rc.Current){//etc}}}}catch(TaskCanceledExceptiontce){//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";awaitQueuedTask.Run(()=>{varrealtimeServiceConProp=newRealtimeServiceConnectionProperties(newUri(url),RealtimeDatastoreType.StreamService);using(varrealtimeDatastore=newRealtimeDatastore(realtimeServiceConProp)){//A Realtime data store only contains **one** Realtime feature class (or table)varname=realtimeDatastore.GetTableNames().First();using(varrealtimeFeatureClass=realtimeDatastore.OpenTable(name)asRealtimeFeatureClass){//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 rowsawaitQueuedTask.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=newSpatialQueryFilter();//waiting for new features to be streamed//default is no cancellationwhile(awaitrc.WaitForRowsAsync()){while(rc.MoveNext()){using(varrow=rc.Current){switch(row.GetRowSource()){caseRealtimeRowSource.EventInsert://getting geometry from new events as they arrivePolygonpoly=((RealtimeFeature)row).GetShape()asPolygon;//using the geometry to select features from another feature layerspatialFilter.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 subscribeawaitSystem.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(awaitrc.WaitForRowsAsync()){//pre-existing rows will be retrieved that were searchedwhile(rc.MoveNext()){using(varrow=rc.Current){varrow_source=row.GetRowSource();switch(row_source){caseRealtimeRowSource.EventDelete://TODO - handle deletesbreak;caseRealtimeRowSource.EventInsert://TODO handle insertsbreak;caseRealtimeRowSource.PreExisting://TODO handle pre-existing rowsbreak;}}}}}//row cursor is disposed. row cursor is unsubscribed}});
Search And Subscribe With Cancellation
awaitSystem.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=newCancellationTokenSource(newTimeSpan(0,0,20));//catch TaskCanceledExceptiontry{while(awaitrc.WaitForRowsAsync(cancel.Token)){//check for row eventswhile(rc.MoveNext()){using(varrecord=rc.Current){//etc}}}}catch(TaskCanceledExceptiontce){//Handle cancellation as needed}cancel.Dispose();}}});