// Note: call within QueuedTask.Run(){varurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varcreateParam=newFeatureLayerCreationParams(newUri(url)){IsVisible=false//turned off by default};streamLayer=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
// Note: call within QueuedTask.Run(){//Must be on the QueuedTaskvarurl="https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varlyrCreateParam=newFeatureLayerCreationParams(newUri(url)){IsVisible=true,DefinitionQuery=newDefinitionQuery(whereClause:"RWY = '29L'",name:"Runway")};streamLayer=LayerFactory.Instance.CreateLayer<StreamLayer>(lyrCreateParam,map);}
Create a stream layer with a simple renderer
// Note: call within QueuedTask.Run(){varurl=@"https://geoeventsample1.esri.com:6443/arcgis/rest/services/LABus/StreamServer";varuri=newUri(url,UriKind.Absolute);varcreateParams=newFeatureLayerCreationParams(uri){RendererDefinition=newSimpleRendererDefinition(){SymbolTemplate=SymbolFactory.Instance.ConstructPointSymbol(ColorFactory.Instance.BlueRGB,12,SimpleMarkerStyle.Pushpin).MakeSymbolReference()}};streamLayer=LayerFactory.Instance.CreateLayer<StreamLayer>(createParams,map);}
Setting a unique value renderer for latest observations
// Note: call within QueuedTask.Run(){varurl=@"https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServer";varuri=newUri(url,UriKind.Absolute);varcreateParams=newFeatureLayerCreationParams(uri){IsVisible=false};streamLayer=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 9newCIMUniqueValueClass(){Values=newCIMUniqueValue[]{newCIMUniqueValue(){FieldValues=newstring[]{"B727"}}},Visible=true,Label="Boeing 727",Symbol=SymbolFactory.Instance.ConstructPointSymbol(ColorFactory.Instance.RedRGB,10,SimpleMarkerStyle.Hexagon).MakeSymbolReference()},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)// Note: call within QueuedTask.Run(){streamLayer.StartStreaming();}
Start and stop streaming
// Note: call within QueuedTask.Run(){//Start...streamLayer.StartStreaming();//Stop...streamLayer.StopStreaming();}
Delete all current and previous observations
// Note: call within QueuedTask.Run(){//Must be called on the feature classusingvarrfc=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
// Note: call within QueuedTask.Run(){//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
// Note: call within QueuedTask.Run(){//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 Stream Layer properties via the CIM
//The layer must be track aware and spatialif(streamLayer.TrackType!=TrackType.Spatial){// not track aware and spatial}// Note: call within QueuedTask.Run(){//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
// Note: call within QueuedTask.Run(){streamLayer=null;//https://geoeventsample1.esri.com:6443/arcgis/rest/services/AirportTraffics/StreamServervaruvrDef=newUniqueValueRendererDefinition(){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 receivedstreamLayer.SetRenderer(streamLayer.CreateRenderer(uvrDef));}
Setting a unique value renderer for latest observations 2
// Note: call within QueuedTask.Run(){//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 received, 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){// not track aware and spatial}// Note: call within QueuedTask.Run(){//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}};varUVrndr=newCIMUniqueValueRenderer(){Fields=newstring[]{"ACTYPE"},Groups=newCIMUniqueValueGroup[]{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
// Note: call within QueuedTask.Run(){//The layer must be track aware and spatialif(streamLayer.TrackType!=TrackType.Spatial){// not track aware and spatial}//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){// not track aware and spatial}// Note: call within QueuedTask.Run(){if(!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){// not track aware and spatial}// Note: call within QueuedTask.Run(){//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
// Note: call within QueuedTask.Run(){varrenderer=streamLayer.GetRenderer();}
Retrieve the previous observation renderer
//The layer must be track aware and spatialif(streamLayer.TrackType!=TrackType.Spatial){// not track aware and spatial}// Note: call within QueuedTask.Run(){varprev_renderer=streamLayer.GetRenderer(FeatureRendererTarget.PreviousObservations);}
Retrieve the track lines renderer
//The layer must be track aware and spatialif(streamLayer.TrackType!=TrackType.Spatial){}// Note: call within QueuedTask.Run(){vartrack_renderer=streamLayer.GetRenderer(FeatureRendererTarget.TrackLines);}
Subscribe and SearchAndSubscribe
Search And Subscribe for Streaming Data
// Note: call within QueuedTask.Run(){//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(rc.WaitForRowsAsync().Result){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 layerusingvarrfc=streamLayer.GetFeatureClass();//non-recycling cursor - 2nd param "false"using(RealtimeCursorrc=rfc.SearchAndSubscribe(qfilter,false)){//waiting for new features to be streamed//default is no cancellationwhile(rc.WaitForRowsAsync().Result){//etc}}}
Search And Subscribe With Cancellation
// Note: call within QueuedTask.Run(){//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(rc.WaitForRowsAsync(cancel.Token).Result){//check for row eventswhile(rc.MoveNext()){usingvarrow=rc.Current;//etc}}}catch(TaskCanceledException){//Handle cancellation as needed}cancel.Dispose();}}
Explicitly Cancel WaitForRowsAsync
RealtimeCursorrc=null;boolSomeConditionForCancel=false;//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(rc.WaitForRowsAsync(cancel.Token).Result){//check for row eventswhile(rc.MoveNext()){usingvarrow=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";// Note: call within QueuedTask.Run(){varrealtimeServiceConProp=newRealtimeServiceConnectionProperties(newUri(url),RealtimeDatastoreType.StreamService);usingvarrealtimeDatastore=newRealtimeDatastore(realtimeServiceConProp);//A Realtime data store only contains **one** Realtime feature class (or table)varname=realtimeDatastore.GetTableNames().First();using(RealtimeFeatureClassrealtimeFeatureClass=realtimeDatastore.OpenTable(name)asRealtimeFeatureClass){//feature class, by default, is not streaming (opposite of the stream layer)realtimeFeatureClass.StartStreaming();//TODO use the feature class//...}}
Check the Realtime Feature Class is Track Aware
// Note: call within QueuedTask.Run(){usingvarrfc=streamLayer.GetFeatureClass();usingvarrfc_def=rfc.GetDefinition();if(rfc_def.HasTrackIDField()){//Track aware}}
Get the Track Id Field from the Realtime Feature class
// Note: call within QueuedTask.Run(){using(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 rows// Note: call within QueuedTask.Run(){// or var rfc = realtimeDatastore.OpenTable(name) as RealtimeFeatureClassusingRealtimeFeatureClassrfc=streamLayer.GetFeatureClass();//non-recycling cursor - 2nd param "false"//subscribe, pre-existing rows are not searchedusingRealtimeCursorrc=rfc.Subscribe(qfilter,false);SpatialQueryFilterspatialFilter=newSpatialQueryFilter();//waiting for new features to be streamed//default is no cancellationwhile(rc.WaitForRowsAsync().Result){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...featureLayer.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 subscribe// Note: call within QueuedTask.Run(){usingRealtimeFeatureClassrfc=streamLayer.GetFeatureClass();//non-recycling cursor - 2nd param "false"usingRealtimeCursorrc=rfc.SearchAndSubscribe(qfilter,false);//waiting for new features to be streamed//default is no cancellation - use await in async methodwhile(rc.WaitForRowsAsync().Result){//pre-existing rows will be retrieved that were searchedwhile(rc.MoveNext()){usingRealtimeRowrow=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 2
// Note: call within QueuedTask.Run(){usingRealtimeFeatureClassrfc=streamLayer.GetFeatureClass();//Recycling cursor - 2nd param "true"usingRealtimeCursorrc=rfc.SearchAndSubscribe(qfilter,true);//auto-cancel after 20 secondsvarcancel=newCancellationTokenSource(newTimeSpan(0,0,20));//catch TaskCanceledExceptiontry{// Use await in async methodwhile(rc.WaitForRowsAsync(cancel.Token).Result){//check for row eventswhile(rc.MoveNext()){usingRealtimeRowrecord=rc.Current;// Process the record}}}catch(TaskCanceledException){//Handle cancellation as needed}cancel.Dispose();}