Skip to content

ProConcepts Map Authoring

UmaHarano edited this page Nov 6, 2023 · 26 revisions

Mapping functionality in ArcGIS Pro is delivered through the ArcGIS.Desktop.Mapping assembly. The ArcGIS.Desktop.Mapping namespace provides classes and members to author maps. This includes creating maps, opening web maps in ArcGIS Online, adding content—such as layers—to maps, creating and editing symbols, assigning renderers to layers, and supporting map annotation and dynamic labeling. The ArcGIS.Desktop.Mapping namespace also provides the ability to manage styles and style items in an ArcGIS Pro project.

Language:      C#
Subject:       Map Authoring
Contributor:   ArcGIS Pro SDK Team <arcgisprosdk@esri.com>
Organization:  Esri, http://www.esri.com
Date:          10/02/2023
ArcGIS Pro:    3.2
Visual Studio: 2022

In this topic

Map Class

The Map class is the primary object used for the organization of geographic data in ArcGIS Pro. It is a container of layers and stand-alone tables. The Map object has properties that operate on all layers within the map, such as spatial reference, map scale, and so on, along with methods that manipulate the map's layers and stand-alone tables. Typical tasks with the Map object include adding a new layer, setting a basemap, changing the spatial reference, obtaining the currently selected features and elements and managing spatio-temporal bookmarks.

Within the application, existing maps appear in the Catalog pane as individual project items, or as open views. Map views are associated with a single map. You can have multiple views for a single map. For a project, only one view can be active at a time. The active view may, or may not be a map view.

Creating and displaying maps

Because of the asynchronous nature of the application, a Map object cannot be created using its constructor; in fact, the Map class does not have a public constructor. Use the MapFactory.Instance.CreateMap method to create and add the newly created map into the current project. .

// create a new map with an ArcGIS Online basemap
QueuedTask.Run(( )=> 
{
  Map map = MapFactory.Instance.CreateMap(
      "World Map", ArcGIS.Core.CIM.MapType.Map, ArcGIS.Core.CIM.MapViewingMode.Map, Basemap.Streets);
  ...
}

At the conclusion of the MapFactory.Instance.CreateMap(), a map has been created and added to the Maps folder collection in the Catalog window, however the map has not been opened. To open a map, create a MapView to host the map using FrameworkApplication.Panes.CreateMapPaneAsync(map). The map view pane will open and display its associated map. The TOC will be populated with the map content.

// create and open a new map
Map world_map = await QueuedTask.Run(() =>
{
  // create a new map with an ArcGIS Online basemap
  Map map = MapFactory.Instance.CreateMap(
        "World Map", ArcGIS.Core.CIM.MapType.Map, ArcGIS.Core.CIM.MapViewingMode.Map, Basemap.Streets);

  return map;
});
//should be called on the UI
await ProApp.Panes.CreateMapPaneAsync(world_map);

*CreateMapPaneAsync should be called on the UI thread.

Retrieving and displaying existing maps

To retrieve a map stored in a project (and open it) use Project.Current.GetItems<T>(), where "T" is of type MapProjectItem. Use LINQ to filter the returned collection to just the map item you are looking for. Next, call mapProjectItem.GetMap() to retrieve the map and load it. The map, once retrieved, can be passed to FrameworkApplication.Panes.CreateMapPaneAsync to open the map.

// open an existing map 
var mapProjectItems = Project.Current.GetItems<MapProjectItem>();
var mapProjectItem = mapProjectItems.FirstOrDefault(mpi => mpi.Name.Equals("World Map"));
await QueuedTask.Run(async () =>
{
  Map map = mapProjectItem.GetMap();
  await ProApp.Panes.CreateMapPaneAsync(map);
});

Once a map is open and a map view is the active view, addins can use the application context via the MapView.Active static property to get a handle to the current active map view (or null if there is no active map view) and MapView.Active.Map to access the map:

// access the currently active map
Map map = MapView.Active.Map;
// set or change the basemap layer of the active map
QueuedTask.Run(() => 
{
  Map map = MapView.Active.Map;
  map.SetBasemapLayers(Basemap.Gray);
});

Creating basemaps

A basemap is a non-editable layer that provides background, or reference information, in your map. It is typically designed to provide a visual reference for other layers to help orient the user of the map. Aerial imagery, terrain, and streets from ArcGIS Online are examples of basemap layers you can add to your map. In addition, you can create custom basemap layers for your project by creating a map of basemap type and authoring it as if it were a regular map. The map appears in the basemap layer gallery on the ribbon as well as in the Catalog pane with a different icon.

// create custom basemap in your current project
Map map = await QueuedTask.Run(() =>
{
  Map basemap = MapFactory.Instance.CreateLocalBasemap("My basemap");
  // create and add layer to the basemap using LayerFactory.Instance
  string url = @"c:\temp\roads.shp"; 
  Uri uri = new Uri(url);
  Layer lyr = LayerFactory.Instance.CreateLayer(uri, basemap);

  return basemap;
});

Working with web maps

ArcGIS Pro supports seamless integration with ArcGIS Online or your organization portal. You can add and open an existing web map in your project, add an existing portal layer item to a map, or publish a map from your project as a web map to share within your organization or with a wider audience.

Map Metadata

Map implements the ArcGIS.Desktop.Core.IMetadataInfo to provide read and write access to its metadata*. Map metadata is saved with the map in the .aprx. Metadata retrieved from the map will be in xml format and will be styled according to whatever is the current metadata style in use. It is the add-in's responsibility to ensure that the rules associated with the current metadata style are met when editing metadata.

 //Supports access and update of metadata
 public interface IMetadataInfo 
 {
   // Returns true if the metadata can be edited otherwise false
   bool GetCanEditMetadata();
   // Returns the xml formatted metadata
   string GetMetadata();
   // Set the metadata xml. The requirements of the current style
   //should be met
   void SetMetadata(string metadataXml);
 }

In this example, the map metadata is retrieved and "set" without any changes if the map metadata is editable. For map metadata to be editable, at a minimum the project access permissions must be read/write.

 var map = MapView.Active.Map; //Assume null check...

 QueuedTask.Run(() => {
   //read the map metadata and set it back if the metadata
   //is editable
   var map_xml = map.GetMetadata();
   if (map.GetCanEditMetadata())
      map.SetMetadata(map_xml);
 });

* Metadata access is also available off the ArcGIS.Desktop.Mapping.MapProjectItem

Generating Offline Maps

Maps in Pro containing sync-enabled feature service content can be taken "offline". Offline content refers to content that can be accessed in the map even in the absence of a network connection. Taking a map offline requires generating a local copy, (or replica), of the feature service content and, optionally, exporting any relevant raster and/or vector tile cache content to the local machine as well.

Once on the local machine, offline feature service content can be edited and sync'd back with the service. Synchronization via the API is currently limited to bi-directional synchronization meaning that any pending changes (saved edits since the last sync) on the client are pushed to the service and any pending changes on the service (any edits and/or changes sync'd with the parent from other child replicas) are pulled to the client within a single synchronization call in the API. Underlying replica(s) generated by the API are two-way replicas. When editing is finished or the client replicas are to be discarded, the replicas can be unregistered on the service. The relevant operations in the API to create, sync, and remove replicas as well as to export tile cache content are exposed via the GenerateOfflineMap and associated classes in the ArcGIS.Desktop.Mapping.Offline namespace.

Documentation on equivalent Pro UI functionality can be found in Take a map offline. The additional Work with offline maps and branch versioned data in the ArcGIS Server documentation may also be helpfull. Properties of the local replicas, created when a map is taken offline, can also be reviewed via the Pro UI replica management capabilities.

Taking Map Content Offline

Taking map content offline includes:

  • Generating a two-way replica for any sync-enabled feature service content* in the map. One replica per sync-enabled feature service is created and copied to a local SQLite geodatabase.
  • Exporting raster tile cache content to a local tile package, one per exported service. Raster tile cache content that can be exported includes tiled map services and image services.
  • Exporting vector tile cache content to a local tile package, one per exported service.

*More information on what constitutes "sync-enabled" content can be found in Prepare data for offline use.

The API provides three methods for determining if a map has content which can be taken offline:

  • GenerateOfflineMap.Instance.GetCanGenerateReplicas(map) - true if the map contains any sync-enabled feature service content.
  • GenerateOfflineMap.Instance.GetCanExportRasterTileCache(map) - true if the map contains any raster tile cache content.
  • GenerateOfflineMap.Instance.GetCanExportVectorTileCache(map) - true if the map contains any vector tile cache content.

If any of these three methods returns true, then the map contains content that can be taken offline. To take the relevant content offline, addins can call the corresponding method(s) to generate replica(s), export raster, or export vector tile cache content.

  • GenerateOfflineMap.Instance.GenerateReplicas(map, replica_params) - generates two-way replicas for the sync-enabled feature service content.
  • GenerateOfflineMap.Instance.ExportRasterTileCache(map, export_params) - exports raster tile cache content.
  • GenerateOfflineMap.Instance.ExportVectorTileCache(map, export_params) - exports vector tile cache content.

At the conclusion of a GenerateReplicas call, feature layers sourced on the sync-enabled feature service content are re-sourced to point to the local two-way replica content. In the case of exported tile cache content, a new layer, pointing at the exported tile package, is added to the map. The original raster or vector tile layer has its visibility set to false. In all cases, the amount of data to be taken offline is constrained by an extent property set on the relevant "params" class passed in as an argument to the above methods. The provided extent must be in the same spatial reference as the map or an exception will be thrown.

The quality/detail of the exported tile cache is controlled via a MaximumUserDefinedScale property set on the ExportTileCacheParams passed into "Export". The MaximumUserDefinedScale is compared against the respective Level Of Details, LODs, for the given raster or vector tile service, to determine which LOD (within the specified extent) will be exported. The chosen LOD will be the maximum (coarsest) LOD possible that contains the specified scale. To get a list of available scales, relevant to the available LODs of the raster and/or vector tile cache content, addins can call GetExportRasterTileCacheScales(map, extent) and GetExportVectorTileCacheScales(map, extent) respectively. MaximumUserDefinedScale should be selected from one of the scales in the list. The first scale in the given list represents the most detailed LOD available, the last scale in the list represents the coarsest, or least detailed LOD available.

LOD increases with decreasing MaximumUserDefinedScale values, however, so too does the number of tiles (covering the area to be exported). If the combined size of the exported tiles exceeds 1 or 2 GBs (or less if network speed is poor), then export can take minutes. MaximumUserDefinedScale and the export extent parameters should be set accordingly. Note: The extent used to determine the list of scales should be the same extent that is used for the export.

Both the GenerateReplicaParams and ExportTileCacheParams classes contain a DestinationFolder property that is used to determine the output location to which the replica and/or exported tile package(s) will be copied. If the property is left blank, the destination folder defaults to whatever is the current Offline Maps location specified for the current project. Typically, this is the current project folder. Specifying a folder that does not exist will throw an ArgumentException.

The following code example illustrates how to take map content offline:

  var extent = MapView.Active.Extent;
  var map = MapView.Active.Map;

  await QueuedTask.Run(() => 
  {
    
    //Check map for sync-enabled content
    var canGenReplicas = GenerateOfflineMap.Instance.GetCanGenerateReplicas(map);
    if (canGenReplicas) 
    {

      //Calling GenerateReplicas on a map with no sync-enabled content throws
      //an InvalidOperationException.
      GenerateOfflineMap.Instance.GenerateReplicas(map, new GenerateReplicaParams() 
      {
        //The area to be exported
        Extent = extent,//clip extent - must be in the same SR as the map

        //Note: DestinationFolder is _not_ set in this case. Therefore it will default 
        //to the current offline maps location set in the project properties UI. If 
        //none is set, it defaults to the current project folder
      });
    }

    //Check for raster tile cache content
    var canExport = GenerateOfflineMap.Instance.GetCanExportRasterTileCache(map);
    if (canExport) 
    {

      //Get a list of available scales. Calling this method on a map with no raster
      //tile cache content returns an empty list
      var scales = GenerateOfflineMap.Instance.GetExportRasterTileCacheScales(map, extent);
      var max_scale = scales[scales.Count() - 1];//pick a scale - in this case the coarsest
                                                    //so the fastest but least detail

      //Export the raster tile caches in the map. Calling ExportRasterTileCache on a map
      //with no raster tile cache content will throw an InvalidOperationException
      GenerateOfflineMap.Instance.ExportRasterTileCache(map, new ExportTileCacheParams() 
      {

        Extent = extent,//clip extent - must be in the same SR as the map
        MaximumUserDefinedScale = max_scale, //Controls the exported LOD 
                                             //(and, hence, download performance)
        DestinationFolder = @"C:/Data/Exports" //target folder - must exist
       });
    }

    //Check for vector tile cache content
    canExport = GenerateOfflineMap.Instance.GetCanExportVectorTileCache(map);
    if (canExport) 
    {

      //Get a list of available scales. Calling this method on a map with no vector
      //tile cache content returns an empty list
      var scales = GenerateOfflineMap.Instance.GetExportVectorTileCacheScales(map, extent);
      var max_scale = scales[scales.Count() - 1];//pick a scale - in this case the coarsest

      //Export the vector tile caches in the map. Calling ExportVectorTileCache on a map
      //with no vector tile cache content will throw an InvalidOperationException
      GenerateOfflineMap.Instance.ExportVectorTileCache(map, new ExportTileCacheParams() 
      {

        Extent = extent,//clip extent - must be in the same SR as the map
        MaximumUserDefinedScale = max_scale,
        DestinationFolder = @"C:/Data/Exports"
       });
    }
  });

Synchronizing and Removing Replicas

Once map content has been taken offline, the local replica content can be synchronized back with its parent replica on the feature service. As the local replicas, created via GenerateReplicas, are two-way replicas, data changes between the child (local) and parent (service) replicas can be synchronized multiple times. Only changes made since the last synchronzation are applied during a sync call. Note: any unsaved edits on the client are not synchronized.

Synchronization pushes all (saved) pending changes from the child to the parent and pulls all pending changes from the parent to the child. Changes pulled from the parent do not participate in an edit session. They are written directly to the child replica and are not un-doable. Addins, therefore, should save or discard all pending edits before performing synchronization.

Addins can check if there is any local syncable content in the map via GenerateOfflineMap.Instance.GetCanSynchronizeReplicas(map)* and perform the bi-directional synchronization with GenerateOfflineMap.Instance.SynchronizeReplicas(map).

  var map = MapView.Active.Map;

  //Save or discard any edits first!
  await Project.Current.DiscardEditsAsync();
  //await Project.Current.SaveEditsAsync();

  await QueuedTask.Run(() => {
     var canSync = GenerateOfflineMap.Instance.GetCanSynchronizeReplicas(map);
     if (canSync) {
       //Do a bi-directional sync. If the local replicas were unregistered, this is
       //a no-op
       GenerateOfflineMap.Instance.SynchronizeReplicas(map);
     }
  });

*GetCanSynchronizeReplicas only detects whether any map content is sourced on a local replica. It does not detect pending changes. It does not determine if the local replicas have, for some reason, been unregistered on the server. Calling SynchronizeReplicas on previously unregistered replica content results in a no-op.
 

To remove the local replicas from the map (as well as the corresponding database item(s) in the project), addins call GenerateOfflineMap.Instance.RemoveReplicas(map). Remove replicas unregisters any local replicas on the service for content sourced in the map and removes the corresponding database items from the project. Layers that were targetting the local replica content are re-sourced to point back to the relevant feature service content. Addins can call GenerateOfflineMap.Instance.GetCanRemoveReplicas(map) to determine if there is syncable content present in the map that can be removed*.

Any pending changes saved in the local replica will not be synchronized with the parent when performng the RemoveReplicas. To synchronize any pending changes, addins should save (or discard) any unsaved edits and call SynchronizeReplicas first.

  var map = MapView.Active.Map;

  //Save any outstanding edits and call sync to
  //sync pending changes _before_ calling RemoveReplicas
  //if addins want local changes sync'd with the parent _first_.
  await Project.Current.SaveEditsAsync();

  await QueuedTask.Run(() => {
     var canRemove = GenerateOfflineMap.Instance.GetCanRemoveReplicas(map);
     if (canRemove) {
       //Sync first as needed...at the discretion of the addin
       //GenerateOfflineMap.Instance.SynchronizeReplicas(map);

       //unregister on the service and remove associated database items from
       //the project. Re-source relevant local feature layers to point back
       //to the service content.
       GenerateOfflineMap.Instance.RemoveReplicas(map);
     }
  });

*GetCanRemoveReplicas does not detect if local sync replicas present in the map have already been unregistered. Calling RemoveReplicas on previously unregistered replica content results in a no-op.

Working with MapMembers

MapMember is an abstract class and represents items contained within a map. It contains common methods and properties that are shared by layers and stand-alone tables. The Layer and StandaloneTable classes inherit from the MapMember class. To get a list of available layers and stand-alone tables in a map, use the Map.Layers and Map.StandaloneTables properties. The results from these properties maintains any group layer structure; use the Map.GetLayersAsFlattenedList() or Map.GetStandaloneTablesAsFlattenedList() methods to get a list without group layer hierarchy.

//Generic MapMember class hierarchy

                                                            MapMember
                                          |--------------------|-----------------|
                                          |                                      |
                                        Layer                               StandaloneTable
                                          |
                |-------------------------|-----------------------------------------|
                |                         |                                         |
         BasicFeatureLayer          BasicRasterLayer                          CompositeLayer
                |                         |                                         |
      |---------|---------|          |----|----------|                 |------------|---------|
      |                   |          |               |                 |            |         |
AnnotationLayer    FeatureLayer  RasterLayer  ImageServiceLayer    GroupLayer  MosaicLayer  (etc.)

To find an existing MapMember such as a layer or a stand-alone table, use the FindLayer or FindStandaloneTable method respectively from a map. These methods allow you to search by unique layer or table URI. There is also a 'FindLayers' and 'FindStandaloneTables' method to find layers or standalone tables by name. Because the map supports nested grouping of layers and standalone tables use the GetLayersAsFlattenedList and GetStandaloneTablesAsFlattenedList methods to recursively obtain the layers and standalone tables from the map. The GroupLayer and CompositeLayer classes provide similar methods to find layers and standalone tables within that context.

These functions do not support partial searches, but you can achieve that by using the .NET lambda expression. You can also use the OfType<T> construct to restrict the return list to layers of a specified type. A number of examples are below.

//find all layers from the active map that contain 'world' in their name
Map map = MapView.Active.Map;
IEnumerable<Layer> layers = map.GetLayersAsFlattenedList().Where(l => l.Name.Contains("world"));

// find the layer with the specified Uri from the map
Layer layer = map.FindLayer(layerURI);

// find the standalone table with the specified name from the map
StandaloneTable table = map.FindStandaloneTables(tableName);

// find the first group layer in the map
GroupLayer groupLayer = map.GetLayersAsFlattenedList().OfType<GroupLayer>().FirstOrDefault();

// find the layers and standalone tables from the group Layer
var layers = groupLayer.Layers;
var tables = groupLayer.StandaloneTables;

// find all layers standalone tables from a group layer (recursively)
var layers = groupLayers.GetLayersAsFlattenedList();
var tables = groupLayer.GetStandaloneTablesAsFlattenedList();

To get the layers or tables highlighted (selected) in the TOC, you need to use the MapView class, not Map.

MapView mapView = MapView.Active;
IReadOnlyList<Layer> selectedLayers = mapView.GetSelectedLayers();
IReadOnlyList<StandaloneTable> selectedTables = mapView.GetSelectedStandaloneTables();

MapMember metadata

Layers and standalone tables implement the ArcGIS.Desktop.Core.IMetadataInfo to provide read and write access to their metadata*. By default, new map layers and tables created by adding a data source to the map reference will utilize the metadata associated with the underlying data source. For example, when a new feature layer is added to a map, the feature layer will use the feature class metadata by default. Layer and table metadata can be switched between their own (independent) full metadata and (dependent) data source metadata via the IMetadataSource interface. MapMember metadata being accessed off the source is read-only and mapMember.GetCanEditMetadata() will return false.

MapMember metadata is saved with the map member in the .aprx. Metadata will be in xml format and will be styled according to whatever is the current metadata style in use. It is the add-in's responsibility to ensure that the rules associated with the current metadata style are met when editing metadata.

 //Supports access and update of metadata
 public interface IMetadataInfo {
   // Returns true if the metadata can be edited otherwise false
   bool GetCanEditMetadata();
   // Returns the xml formatted metadata
   string GetMetadata();
   //Set the metadata xml. The requirements of the current style
   //should be met
   void SetMetadata(string metadataXml);
 }

 //Indicates if metadata can be retrieved from a source other than itself
 public interface IMetadataSource {
   //Gets whether an underlying source is being used for metadata
   bool GetUseSourceMetadata();
   // Set to true to use the underlying source object's metadata
   void SetUseSourceMetadata(bool useSource);
 }

In this example, layer metadata is set to be independent of its source.

  //var layer = ...;
  //Must be on the QueuedTask.Run()

  //Is this layer using source metadata?
  if (layer.GetUseSourceMetadata())
    //Set the metadata to be independent of the underlying layer source
    layer.SetUseSourceMetadata(false);
  

In this example, a layer's metadata is retrieved and "set" without any changes if the layer metadata is editable. As a minimum, for layer (and table) metadata to be editable, mapMember.GetUseSourceMetadata() must be false and project access permissions must be read/write.

 //var layer = ...;

 QueuedTask.Run(() => {
   //read the layer metadata and set it back if the metadata
   //is editable
   var layer_xml = layer.GetMetadata();
   if (layer.GetCanEditMetadata())
      layer.SetMetadata(layer_xml);
 });

* _Sub_layers do not support metadata. Check the layer.SupportsMetadata property for true|false depending on whether metadata is supported or not. Sublayers include: AnnotationSubLayer, ServiceCompositeSubLayer, ServiceSubLayer, and WMSSubLayer. Attempting to access metadata off a sublayer (where SupportsMetadata is false) will throw a NotSupportedException.

Working with Layers

Layers display geographic information on a map. A layer does not store the actual geographic data; rather, it references the data contained in shapefiles, geodatabases, images, and so on, then defines how to display this geographic data. Most often, a layer references a single shapefile, table, or a view in the database. Sometimes the source of a layer is a joined table made of two or more tables. Some layers do not refer to geographic data. For example, a GroupLayer refers to other layers, and an AnnotationLayer stores text or graphic elements.

Each type of layer object represents different types of data. Examples of layer objects include the following:

  • FeatureLayer
  • RasterLayer
  • GroupLayer
  • FeatureSceneLayer
  • StreamLayer
  • Graphicslayer

Layer create methods allow you to use the following:

  • Local or shared paths to shapefiles, FeatureClasses, and so on
  • URLs to map or feature services
  • Item objects

All layers inherit from an abstract class called Layer, which inherits from MapMember. Layers do not have a public constructor; you cannot create them with a new keyword. Instead, you need to use one of the methods from LayerFactory.Instance to create one. The LayerFactory.Instance provides a few CreateLayer methods; the first allows you to simply specify the path to the data.

//create a layer from a shapefile
string uriShp = @"\\Machine\SharedFolder\Census.shp";
Layer lyr = LayerFactory.Instance.CreateLayer(new Uri(uriShp), map);

//create a layer from a feature class off an sde
string uriSde = @"c:\MyDataConnections\MySDE.sde\Census";
Layer lyr = LayerFactory.Instance.CreateLayer(new Uri(uriSde), map);

//create a layer using a URL
string url = @"http://sampleserver6.arcgisonline.com/arcgis/rest/services/NapervilleShelters/FeatureServer/0";
Layer lyr = LayerFactory.Instance.CreateLayer(new Uri(url), map);

// create a layer and add it to a groupLayer
string urlLyrx = @"\\Machine\SharedFolder\Census.lyrx";
Layer lyr = LayerFactory.Instance.CreateLayer(new Uri(urlLyrx), grpLayer);

However, perhaps you need more control when creating the layer in order to set certain prroperties prior to adding the layer to the map. For example you may wish to add a layer to a map but have it invisible by default. The LayerCreationParams class was created with this purpose and can be used in conjunction with the LayerFactory.Instance.CreateLayer<T>(LayerCreationParams, ILayerContainerEdit) method. There are many types of LayerCreationParams each with properties specific to the matching layer type. For example the FeatureLayerCreationParams allow you to also set the DefinitionQuery and Renderer prior to adding a FeatureLayer.

Below are some examples of this particular CreateLayer call. Note the use of the layer type Template in the calls which matches the usage of a specific descendent of the LayerCreationParams class.

// create a layer from a uri
//    set the name and visibility
var uri = new Uri(@"c:\MyDataConnections\MySDE.sde\LANDUSE_polygon");
var createParams = new LayerCreationParams(uri)
{
   Name = "Landuse",
   IsVisible = false,
};
Layer layer = LayerFactory.Instance.CreateLayer<Layer>(createParams, MapView.Active.Map);


// create a layer from a feature class
//    use the FeatureLayerCreationParams since I want to 
//    set name, visibility and definition query
var featureCreateParams = new FeatureLayerCreationParams(featureClass)
{
  Name = "Cities",
  IsVisible = false,
  DefinitionQuery = new DefinitionQuery("My Query", "ObjectID > 3"), 
};
// use the FeatureLayer template to match the FeatureLayerCreationParams
FeatureLayer featurelayer = 
         LayerFactory.Instance.CreateLayer<FeatureLayer>(featureCreateParams, MapView.Active.Map);


// create a group layer - use GroupLayerCreationParams
//     set the name and scale
var groupCreateParams = new GroupLayerCreationParams()
{
  Name="GroupLayer", 
  MinimumScale = 1000, 
  MaximumScale = 1000000,
};
// use GroupLayer to match GroupLayerCreationParams
GroupLayer grouplayer = 
       LayerFactory.Instance.CreateLayer<GroupLayer>(groupCreateParams, MapView.Active.Map);


// create a graphics layer
//    set the name
var graphicscreateParams = new GraphicsLayerCreationParams()
{
  Name = "My Graphics Layer",
};
GraphicsLayer graphicsLayer = 
    LayerFactory.Instance.CreateLayer<GraphicsLayer>(graphicscreateParams, MapView.Active.Map);

Note, you can also use the Layerfactory.Instance.CreateGroupLayer to create a GroupLayer.

Map Notes can be added to the map. Map Notes are available as a collection of layer template packages. Each of these Map Notes can be added to the map as an "item" object using the LayerCreationParams.

//Gets the collection of layer template packages installed with Pro for use with maps
var items = MapView.Active.Map.LayerTemplatePackages;     
//Iterate through the collection of items to add each Map Note to the active map
foreach (var item in items)
{
  //Create a parameter item for the map note
  var layer_params = new LayerCreationParams(item);
  layer_params.IsVisible = false;
  await QueuedTask.Run(() => {
    //Create a feature layer for the map note
    var layer = LayerFactory.Instance.CreateLayer<Layer>(layer_params, MapView.Active.Map);
  });
}

Working with Feature Layers

A FeatureLayer is a layer that is based on vector geographic data, which is typically a geodatabase, shapefile feature class, or sublayer off a map or feature service. It allows you to draw and/or edit the underlying features. A DefinitionQuery can be used to draw a subset of all features. You can also define a RendererDefinition to display the data with the desired symbology instead of with the default symbology.

As shown above, use the LayerFactory.Instance.CreateLayer methods to create feature layers. Here are some code snippets.

//create a feature layer to ONLY cities in California
FeatureLayer flyr = LayerFactory.Instance.CreateLayer(new Uri(strUri), map) as FeatureLayer;
flyr.SetDefinitionQuery("state_name = 'California'");


// or use the FeatureLayerCreationParams
var featureCreateParams = new FeatureLayerCreationParams(new Uri(strUri))
{
  Name = "States",
  DefinitionQuery = new DefinitionQuery("California State", "state_name = 'California'"), 
};
// use the FeatureLayer template to match the FeatureLayerCreationParams
FeatureLayer featurelayer = 
        LayerFactory.Instance.CreateLayer<FeatureLayer>(featureCreateParams, MapView.Active.Map);
//create a feature layer with a graduated color renderer on a numeric field
var featureCreateParams = new FeatureLayerCreationParams(new Uri(strUri))
{
  Name = "Population Density",
  RendererDefinition = new GraduatedColorsRendererDefinition("POP10_SQMI"),  
};
// use the FeatureLayer template to match the FeatureLayerCreationParams
FeatureLayer featurelayer = 
     LayerFactory.Instance.CreateLayer<FeatureLayer>(featureCreateParams, MapView.Active.Map);
//create a feature layer with a graduated color renderer and a definition query
RendererDefinition rd = new GraduatedColorsRendererDefinition("POP10_SQMI");
var createParams = new FeatureLayerCreationParams(uri)
{
   Name = "Population Density",
   RendererDefinition = rd,
   DefinitionQuery = new DefinitionQuery("Population greater than 1000", "POP > 1000"), 
};
FeatureLayer flyr = LayerFactory.Instance.CreateLayer<FeatureLayer>(createParams, myMap);

Renderers

Renderers are objects that store symbolization for feature layers and draw this data based on the stored symbolization rules. To create a renderer in ArcGIS Pro, it is recommended that you create a RendererDefinition, call the CreateRenderer method which returns a CIMRenderer object. If you need to, you can modify that before assigning it to the feature layer by calling the SetRenderer method. You can get hold of the current renderer assigned to a feature layer—for example, if you want to modify a symbol used in the renderer—by calling the GetRenderer method.

//assign a graduated color renderer with 6 breaks and exclusion clause
GraduatedColorsRendererDefinition gcDef = new GraduatedColorsRendererDefinition()
{
  ClassificationField = "CROP_ACR07",
  ClassificationMethod = ArcGIS.Core.CIM.ClassificationMethod.NaturalBreaks,
  BreakCount = 6,
  ExclusionClause = "CROP_ACR07 = -99",
  ExclusionSymbol = aExclusionSymbol,
  ColorRamp = aColorRamp,
  SymbolTemplate = aSymbolTemplate,
};

CIMRenderer gcRenderer = aFeatureLayer.CreateRenderer(gcDef);
aFeatureLayer.SetRenderer(gcRenderer);

There are more examples of Renderers in the Map Authoring snippets

Labeling

The labeling properties of a layer are stored in the LabelClasses collection available on FeatureLayer. To update or read properties on an existing LabelClass, get the LabelClass you want from the collection. The LabelClass provides access to commonly updated label class properties. Full label placement properties can be accessed for the label engine the map is currently using by calling GetMaplexLabelPlacementProperties() or GetStandardLabelPlacementProperties(), which return CIMMaplexLabelPlacementProperties and CIMStandardLabelPlacementProperties, respectively. To update these properties, call SetMaplexLabelPlacementProperties() or SetStandardLabelPlacementProperties() depending on the label engine being used. Call GetLabelEngine on the Map to determine which label engine is currently being used. Map level labeling properties can be accessed via the Map method GetGeneralPlacementProperties() and updated via SetGeneralPlacementProperties().

Working with Raster Layers

RasterLayer represents an image or pixel data on disk or in a geodatabase.

// Create a RasterLayer from an image on disk.
string url = @"C:\Images\Italy.tif";
RasterLayer rasterLayer =  LayerFactory.Instance.CreateLayer(new Uri(url), map) as RasterLayer;

Use the RasterLayerCreationParams class and define the ColorizerDefinition to create a raster or image service layer with the desired colorizer instead of with the default colorizer.

// Create a new stretch colorizer definition using default constructor.
StretchColorizerDefinition stretchColorizerDef = new StretchColorizerDefinition();
await QueuedTask.Run(() =>
{
  var rasterCreationParams = new RasterLayerCreationParams(new Uri(url));
  rasterCreationParams.Name = layerName;
  rasterCreationParams.MapMemberIndex = 0;
  rasterCreationParams.ColorizerDefinition = stretchColorizerDef;
  // Create a raster layer using the colorizer definition created above.
  // Note: You can create a raster layer from a url, project item, or data connection.
  RasterLayer rasterLayerfromURL = LayerFactory.Instance.CreateLayer<RasterLayer>(rasterCreationParams, map); 
});

Working with Mosaic Layers

MosaicLayer is a group layer that represents a mosaic dataset. A mosaic dataset is a collection of images that have been seamlessly mosaicked together so that they look like one image. The mosaic layer group layer can contain up to four different sublayers:

  • Boundary—Feature layer that represents the boundary of the mosaic.
  • Footprint—Feature layer that represents the footprints of the images comprising the mosaic.
  • Seamline—Feature layer that represents the seamlines between the images of the mosaic. This layer only shows if seamlines have been generated.
  • Image—Image service layer that represents the mosaic. Users can control the way images are displayed, mosaicked, and transmitted through this layer.

Feature layers that are part of a mosaic layer can be distinguished from other feature layers by using the FeatureMosaicSubLayer class. Similarly, an image service layer that is part of a mosaic layer can be distinguished from other image service layers by using the ImageMosaicSubLayer class.

// Get the Image sublayer of the mosaic layer.
ImageMosaicSubLayer mosaicImageSublayer = mosaiclayer.GetImageLayer();
// Get the Footprint sublayer of the mosaic layer.
FeatureMosaicSubLayer mosaicFootprintSubLayer = mosaiclayer.GetFootprintLayer();
// Get the Boundary sublayer of the mosaic layer.
FeatureMosaicSubLayer mosaicBoundarySubLayer = mosaiclayer.GetBoundaryLayer();
// Get the Seamline sublayer of the mosaic layer.
FeatureMosaicSubLayer mosaicSeamlineSubLayer = mosaiclayer.GetSeamlineLayer();

LayerFactory.Instance.CreateLayer() allows you to pass in a RasterColorizerDefinition to create a mosaic layer with the desired colorizer instead of with the default colorizer.

// Create a new colorizer definition using default constructor.
StretchColorizerDefinition stretchColorizerDef = new StretchColorizerDefinition();
await QueuedTask.Run(() =>
{
  // Note: You can create a mosaic layer from a url, project item, or data connection.
  var mosaicCreationParams = new MosaicLayerCreationParams(new Uri(url));
  mosaicCreationParams.Name = layerName;
  mosaicCreationParams.MapMemberIndex = 0;
  mosaicCreationParams.ColorizerDefinition = stretchColorizerDef;

  // Create a mosaic layer using the colorizer definition created above.  
  MosaicLayer newMosaicLayer = LayerFactory.Instance.CreateLayer<MosaicLayer>(mosaicCreationParams, map);
});

Working with Image Service Layers

ImageServiceLayer represents pixel data coming from an image service or a mosaic dataset. Image service layers allow users to change the order in which images are mosaicked together (called the mosaic method) and how the overlapping portions of the images are displayed (called the mosaic operator). The image service layer also allows users to control the compression of the image being transmitted.

// Get the mosaic rule of the image service.
CIMMosaicRule mosaicRule = isLayer.GetMosaicRule();
// Set the mosaic method to be Center.
mosaicRule.MosaicMethod = RasterMosaicMethod.Center;
// Update the image service with the changed mosaic rule.
isLayer.SetMosaicRule(mosaicRule);

Use the LayerFactory.Instance.CreateLayer() method with a RasterLayerCreationParams to specify a ColorizerDefinition to create a raster or image service layer with the desired colorizer instead of with the default colorizer.

// Create a new colorizer definition using default constructor.
StretchColorizerDefinition stretchColorizerDef = new StretchColorizerDefinition();
await QueuedTask.Run(() =>
{
  var rasterCreationParams = new RasterLayerCreationParams(new Uri(url));
  rasterCreationParams.Name = "my image layer";
  rasterCreationParams.MapMemberIndex = 0;
  rasterCreationParams.ColorizerDefinition = stretchColorizerDef;

  // Create an image service layer using the colorizer definition created above.
  ImageServiceLayer imageServiceLayer =
            LayerFactory.Instance.CreateLayer<BasicRasterLayer>(rasterCreationParams, map) as ImageServiceLayer;
});

Colorizers

Raster, mosaic, and image service layers are drawn based on properties stored in colorizers. To modify a colorizer, use the GetColorizer method on the raster and image service layers to get a CIMRasterColorizer object, make modifications accordingly, then use the SetColorizer method to apply the changes.

To create colorizers in ArcGIS Pro, it is recommended that you create a RasterColorizerDefinition, call the CreateColorizer method to return the CIMRasterColorizer then assign it to the layer using the SetColorizer method.

// Get the colorizer from the raster layer.
CIMRasterColorizer rasterColorizer = rasterLayer.GetColorizer();
// Update raster colorizer properties.
rasterColorizer.Brightness = 10;
rasterColorizer.Contrast = -5;
rasterColorizer.ResamplingType = RasterResamplingType.NearestNeighbor;
// Update the raster layer with the changed colorizer.
rasterLayer.SetColorizer(rasterColorizer);

Working with Elevation Surface Layers

Starting at 3.0, Elevation surfaces and sources are represented by the ElevationSurfaceLayer class. An ElevationSurfaceLayer is derived from CompositeLayer and can contain zero, one or more child elevation source layers (eg a raster, TIN, etc.). For example, the following TOC shows 3 elevation surface layers. The first is the ground elevation surface layer ("Ground") and it contains 2 elevation source layers. The second elevation surface layer ("Surface1") contains a single elevation source layer and the third elevation surface layer ("Surface2") contains 0 elevation source layers.

ElevationSurfaceLayers

You can retrieve the elevation surface layers in the map using Map.GetElevationSurfaceLayers. Note that the Map.Layers or Map.GetLayersAsFlattenedList methods will not retrieve elevation surface layers; you must use Map.GetElevationSurfaceLayers. There is an additional method Map.GetGroundElevationSurfaceLayer to retrieve the elevation surface layer specified as ground. There is always only one ground elevation surface layer in the map. The individual elevation source layers are retrieved by accessing the Layers property of the ElevationSurfaceLayer class.

 // retrieve the elevation surface layers in the map including the Ground
 var surfaceLayers = map.GetElevationSurfaceLayers();

 // retrieve the single ground elevation surface layer in the map
 var groundSurfaceLayer = map.GetGroundElevationSurfaceLayer();
 // determine the number of elevation sources in the ground elevation surface layer
 int numberGroundSources = groundSurfaceLayer.Layers.Count;
 // get the first elevation source layer from the ground elevation surface layer
 var groundSourceLayer = groundSurfaceLayer.Layers.FirstOrDefault();

You can find a particular elevation surface layer using Map.FindElevationSurfaceLayer(LayerUri) or Map.GetElevationSurfaceLayers with a LINQ query

  var surfaceLayers = map.GetElevationSurfaceLayers();
  var surfaceLayer = surfaceLayers.FirstOrDefault(l => l.Name == "Surface2");

  var surfaceLayer = map.FindElevationSurfaceLayer(layerUri);

As with all the other layer types, there is an ElevationLayerCreationParams class to use with the LayerFactory.Instance.CreateLayer method to add an elevation surface layer to a map. Specify the ElevationSurfaceLayer type as the Template parameter. The surface layer will be created with an ElevationMode of ElevationMode.CustomSurface.

  var map = MapView.Active.Map;

  await QueuedTask.Run(() =>
  {
    string uri = "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer";
    var createParams = new ElevationLayerCreationParams(new Uri(uri));
    createParams.Name = "My custom elevation surface";
    // adds a new elevation surface layer to the map with the specified elevation source
    var eleSurfaceLayer = LayerFactory.Instance.CreateLayer<ElevationSurfaceLayer>(createParams, map);
  });

To add an elevation source to an existing elevation surface layer use the same CreateLayer method, but specify the Layer type as the Template, and the elevation surface layer as the container rather than the map.

  var surfaceLayers = map.GetElevationSurfaceLayers();
  var surfaceLayer = surfaceLayers.FirstOrDefault(l => l.Name == "Surface2");

  // surfaceLayer could also be the ground layer
  // var surfaceLayer = map.GetGroundElevationSurfaceLayer();

  await QueuedTask.Run(() =>
  {
    string uri = "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer";
    var createParams = new ElevationLayerCreationParams(new Uri(uri));
    createParams.Name = "Terrain 3D";
    var eleSourceLayer = LayerFactory.Instance.CreateLayer<Layer>(createParams, surfaceLayer);
  });

To add a new ground elevation surface layer to a map, use the MapFactory.Instance.CreateScene method.

 //Or create a new scene from scratch with a ground elevation source
 var scene = MapFactory.Instance.CreateScene("My scene", groundSourceUri, MapViewingMode.SceneGlobal);

To remove elevation surface layers from the map using the RemoveLayer, RemoveLayers or ClearElevationSurfaceLayers methods. Noe that you cannot remove the ground elevation surface layer using these calls.

 map.RemoveLayer(surfaceLayer);//Cannot remove ground
 map.RemoveLayers(map.GetElevationSurfaceLayers()); //Ground will not be removed

 map.ClearElevationSurfaceLayers();   //Ground will not be removed

Remove the individual elevation source layers from an elevation surface layer by using the RemoveLayer and RemoveLayers methods from the ElevationSurfaceLayer object.

  await QueuedTask.Run(() =>
  {
    eleSurfaceLayer.RemoveLayer(eleSurfaceLayer.Layers.FirstOrDefault());
  });

Working with Catalog Layers

CatalogLayer is a group or "composite" layer that manages references to multiple different data types and portal items within the single "catalog" layer. The references themselves are stored within a catalog dataset - which is the container for the various item references. Each individual item, within the dataset, is called a catalog data item. Catalog dataset items store a polygon outline/foot print geometry for the referenced item as well as the data path.

As currently, catalog datasets can be created via the catalog pane UI or via Geoprocessing. In the catalog pane, the "New->Catalog Dataset" menu item can be used to add a catalog dataset to a geodatabase. Otherwise, via Geoprocessing, use the Create Catalog Dataset GP tool. Once a catalog dataset has been created, catalog dataset items can be added to it. Unrelated data items can be added to a catalog dataset. Catalog layers currently support time filtering and definition queries.

When navigating around a map or scene in ArcGIS Pro the catalog layer only draws dataset items that fall within the extent and scale of the current map view.

To create a catalog layer, use a CatalogLayerCreationParams and the URI to the relevant catalog dataset with the templated version of CreateLayer on the LayerFactory. Set the templated type to be CatalogLayer:

 var map = MapView.Active.Map;

 //Note: Call within the lambda of QueuedTask.Run(() => {

 var createParams = new CatalogLayerCreationParams(new Uri(
                         @"C:\CatalogLayer\CatalogDS.gdb\HurricaneCatalogDS"));
 //note: can also use a reference to a catalog dataset...
 //var createParams = new CatalogLayerCreationParams(catalogDataset);

 //Set a definition query (optional)
 createParams.DefinitionQuery = new DefinitionQuery("Query1", "cd_itemname = 'PuertoRico'");
 //Set name of the new Catalog Layer (optional)
 createParams.Name = "PuertoRico";

 //Create Layer adding it to the map on the current active view
 var catalogLayer = LayerFactory.Instance.CreateLayer<CatalogLayer>(
                                                           createParams, map);

Retrieve catalog layers from the map:

  var catalogLayer = MapView.Active.Map.GetLayersAsFlattenedList()
                         .OfType<CatalogLayer>().FirstOrDefault();
  if (catalogLayer != null) {
    //get the catalog dataset from the layer
    using(var cat_ds = catalogLayer.GetCatalogDataset()) {
    //TODO - use the catalog dataset
    ...
   

Working with 3D Analyst Layers

See the 3D Analyst Data and 3D Analyst Layers documents for information regarding 3D Analyst data such as TIN, Terrain and LAS dataset and their associated layers.

Working with Tables

Tables, or "standalone" tables, are used to display attribute information in a tabular format. In the simplest terms, tables are composed of rows and columns, and all rows have the same columns. Additionally, tables do not contain a shape column. Table data is visualized using a table view (this includes the attributes associated with a feature layer). Multiple tables can be open simultaneously, whether sourced from a standalone table or a layer. The table view can be customized via the UI to modify columns, filter fields, sort records, select records, and edit data.

Via StandaloneTableFactory, a stand-alone table can be added to a map or scene in a similar manner to a layer (using LayerFactory):

  • Using a local or shared path to a table
  • URLs to feature services that expose a table (as one of the layer endpoints)
  • Item objects

Tables can be added to either the map, in the Standalonetables collection or, starting at 2.9, to a group layer.

For example:

 //container can be a map or group layer
 var container = MapView.Active.Map;
 //var container =  MapView.Active.Map.GetLayersAsFlattenedList()
 //                                  .OfType<GroupLayer>().First();

 QueuedTask.Run(() => 
 {
   // use a local path
   var table = StandaloneTableFactory.Instance.CreateStandaloneTable(
            new Uri(@"C:\Temp\Data\SDK.gdb\EarthquakeDamage_Table", UriKind.Absolute),container);

   //use a URI to a feature service table endpoint
   var table2 = StandaloneTableFactory.Instance.CreateStandaloneTable(
          new Uri(@"https://bexdog.esri.com/server/rest/services/FeatureServer" + "/2", UriKind.Absolute),
          container);

   //Use an item and StandaloneTableCreationParams
   //   StandaloneTableCreationParams allows you to set name, position, DefinitionQuery
   var item = ItemFactory.Instance.Create(@"C:\Temp\Data\SDK.gdb\ParcelOwners");
   var tableCreationParams = new StandaloneTableCreationParams(item);
   tableCreationParams.Name = "Parcel Owners";
   tableCreationParams.MapMemberPosition = MapMemberPosition.AddToTop;
   var table3 = StandaloneTableFactory.Instance.CreateStandaloneTable(tableCreationParams, container);
 });

Once added to the map, tables can be retrieved from their respective container. A table view can be opened with FrameworkApplication.Panes.OpenTablePane(...). Note that OpenTablePane must be called from the UI thread:

 var container = MapView.Active.Map;

 //the map standalone table collection
 var table = container.GetStandaloneTablesAsFlattenedList()
                   .FirstOrDefault(tbl => tbl.Name == "EarthquakeDamage");

 //or from a group layer
 var grp_layer = MapView.Active.Map.FindLayers("GroupLayer1").First() as GroupLayer;
 var table2 = grp_layer.FindStandaloneTables("EarthquakeDamage").First();
 //or         grp_layer.GetStandaloneTablesAsFlattenedList()

 //show the table in a table view 
 //use FrameworkApplication.Current.Dispatcher.BeginInvoke if not on the UI thread
 FrameworkApplication.Panes.OpenTablePane(table2);

Tables can be moved within their container, or to another container, using the MoveStandaloneTable method on the respective container (map or group layer) where the "position" is specified as a zero-based index (within the target container) to which the table should be moved. Position 0 is the top of the container, position n-1 or any invalid index such as -1 would be the bottom. To move a table between group layer containers, or from the map to a group layer, use the MoveStandaloneTable(StandaloneTable table, CompositeLayerWithTables targetLayer, int position) overload specifying the target container as the targetLayer parameter.

To move a table from a group layer to the map is a special case. The map cannot be specified as the target container in the MoveStandaloneTable() overload, only a group layer can. Instead, simply use map.MoveStandaloneTable(table, index) method specifying the table to be moved and the index to move it to (in the map table container). Because, in this case, the specified table is contained in a group layer but MoveStandaloneTable is being invoked on the map, the map is assumed to be the target container.

//get the current map
var map = MapView.Active.Map;//assumes non-null
//get the first group layer that has at least one table
var grp_layer = MapView.Active.Map.GetLayersAsFlattenedList()
   .OfType<GroupLayer>().First(g => g.StandaloneTables.Count > 0);//we assume there is one
var grp_layer2 = ....;//get another group layer from the map

QueuedTask.Run(() =>
{
  //move the first table in the group layer to the bottom of the container
  grp_layer.MoveStandaloneTable(grp_layer.StandaloneTables.First(), -1);


  //move the last table in the map standalone tables to a group
  //layer and place it at position 3. If 3 is invalid, the table
  //will be placed at the bottom of the target container

  //assumes the map has at least one standalone table...
  var table = map.StandaloneTables.Last();
  map.MoveStandaloneTable(table, grp_layer, 3);//move it to the group layer
  
  //move a table from one group layer to another
  grp_layer.MoveStandaloneTable(table, grp_layer2, 0);//move it to group layer "2"


  //move a table from a group layer to the map standalone tables
  //collection - assumes a table called 'Earthquakes' exists - 
  //special case - call MoveStandaloneTable() on the map...
  var table2 = grp_layer.FindStandaloneTables("Earthquakes").First();
  //move to the map container
  map.MoveStandaloneTable(table2, 0);//will be placed at the top
});

Tables can be removed from their respective container via RemoveStandaloneTable(...) or, to remove multiple, via RemoveStandaloneTables(...).

  QueuedTask.Run(()=> 
  {
    //get the tables from the map container
    var tables = map.GetStandaloneTablesAsFlattenedList();
    //delete the first...
    if (tables.Count() > 0) 
    {
      map.RemoveStandaloneTable(tables.First());
      //or delete all of them
      map.RemoveStandaloneTables(tables);
    }

    //delete a table from a group layer
    //assumes it has at least one table...
    grp_layer.RemoveStandaloneTable(grp_layer.StandaloneTables.First());
    ...

Layer Files and Layer Packages

A layer can exist outside of your map or project as a layer file (.lyrx). A layer file is a file that stores the path to a source dataset and other layer properties including symbology. This makes it easy for others to access the layers you've built. When you add a layer file to a map, it draws exactly as it was saved provided the data referenced by the layer file is accessible.

A layer can also be saved with its data as a layer package (.lpkx). A layer package includes both the layer properties and the dataset referenced by the layer. With a layer package, you can save and share everything about the layer — its symbolization, labeling, table properties, AND the data. Other users will be able to add layer packages directly into their maps without having to know how to access the database or classify the data.

Any type of layer can be saved as a layer file or layer package, including group layers. Remember, a layer file contains only a reference to the actual data. It does NOT store the data attributes or geometry.

Here are some examples of adding layer files or packages to a map

// create a layer and add it to a groupLayer
string urlLyrx = @"\\Machine\SharedFolder\Census.lyrx";
Layer lyr = LayerFactory.Instance.CreateLayer(new Uri(urlLyrx), grpLayer)

// user the LayerCreationParams to customize the layer name
// KNOWN LIMIT  – no other properties of the LayerCreationParams (or 
//    FeaturelayerCreationParams etc) will be honored when used 
//    with lyrx or lpkx files.  
//    This is a known limit at ArcGIS Pro 2.4
string urlLpkx = @"\\Machine\SharedFolder\Census.lpkx";
var createParams = new LayerCreationParams(new Uri(urlLpkx))
{
  Name = "Population Density",
};

FeatureLayer flyr = LayerFactory.Instance.CreateLayer<FeatureLayer>(createParams,  myMap);

The LayerDocument class provides functionality to access and modify the contents of a layer file. Use this pattern to alter the layer file properties prior to adding it to the map. The following example alters the visibility and symbology of a layer file before adding it to a map.

string urlLyrx = @"\\Machine\SharedFolder\Census.lyrx";
var layerDoc = new ArcGIS.Desktop.Mapping.LayerDocument(urlLyrx);
// get the CIMLayerDocument
var cimLayerDoc = layerDoc.GetCIMLayerDocument();

// manipulate the layer definitions
//    change the visibility and renderer
var layerDefinitions = cimLayerDoc.LayerDefinitions;
var layerDef = layerDefinitions[0] as CIMFeatureLayer;
if (layerDef != null)
{
  layerDef.Visibility = false;
  layerDef.Renderer = new CIMSimpleRenderer()
  {
    Symbol = SymbolFactory.Instance.ConstructPolygonSymbol(
               CIMColor.CreateRGBColor(255, 0, 0)).MakeSymbolReference()
  };
}

// add it to the map
var createParams = new LayerCreationParams(cimLayerDoc);
LayerFactory.Instance.CreateLayer<Layer>(createParams, MapView.Active.Map);

You can also use the LayerDocument class to apply the symbology in the layer file to another layer in the map without requiring the layer file to be added to the map.

// default symbology 
CIMRenderer renderer = new CIMSimpleRenderer()
{
  Symbol = SymbolFactory.Instance.ConstructPolygonSymbol(
             CIMColor.CreateRGBColor(255, 0, 0)).MakeSymbolReference()
};

// load the layer file into a LayerDocument
var layerDoc = new ArcGIS.Desktop.Mapping.LayerDocument(@"D:\Pro_SDK\LayerDocument\LANDUSE_polygon_Edit.lyrx");
var cimLayerDoc = layerDoc.GetCIMLayerDocument();

// retrieve the renderer from the LayerDocument
var layerDefinitions = cimLayerDoc.LayerDefinitions;
var layerDef = layerDefinitions[0] as CIMFeatureLayer;
if (layerDef != null)
{
  renderer = layerDef.Renderer;
}

// apply the renderer to a layer in the map
// NOTE - the assumption is that the Landuse layer has the same fields
//   as the layer file in order for the renderer to display correctly
var fLayer = MapView.Active.Map.GetLayersAsFlattenedList().
            FirstOrDefault(l => l.Name == "Landuse") as FeatureLayer;
if (fLayer != null)
  fLayer.SetRenderer(renderer);

Querying and Selecting features or rows

FeatureLayers and StandaloneTables are often entry points to accessing and selecting data. Both of these classes implement the IDisplayTable interface to support accessing, querying, and selecting features or rows. IDisplayTable also allows you to get the field list, get the definition query, and so on. You can also get and set the selectability and editability of the layer or table using IsSelectable, SetSelectable, IsEditable and SetEditable methods. Note that (1) all IDisplayTable members are available directly from the FeatureLayer and StandaloneTable classes, you do not need to cast them to the IDiplayTable interface, and (2) these methods work properly when dealing with joined data.

To access the base table including joined tables, you need to use the GetTable method.

There is also a Search method for performing a search on a layer or a stand-alone table. This method returns a cursor of features or rows from the layer or the stand-alone table that meet a given search criteria. If there is a definition query set, Search will work on the subset that meets the definition criteria. Note that the Search method will not work on joined fields and returned values from joined fields; you do not need to anything special.

//searching and returning number of features where value in 'Class' field in 'city'
QueryFilter qf = new QueryFilter()
{
  WhereClause = "Class = 'city'"
};

using (RowCursor rows = aFeatureLayer.Search(qf))
{
  int i = 0;
  while (rows.MoveNext()) i++;
}

You can use the Select method to highlight features or rows on the map view or in the attribute table window.

//selecting features in two layers where value in 'Class' field in 'city'
QueryFilter qf = new QueryFilter()
{
  WhereClause = "Class = 'city'"
};

Selection selection = aFeatureLayer.Select(qf, SelectionCombinationMethod.New);
Selection selection2 = aFeatureLayer2.Select(qf, SelectionCombinationMethod.Add);

SelectionSet

At 2.x, operations such as MapView.GetFeatures,MapView.GetFeaturesEx, MapView.SelectFeatures, and MapView.SelectFeaturesEx on the MapView returned a set of MapMembers along with a corresponding list of objectIDs of the form Dictionary<BasicFeatureLayer, List> and Dictionary<Layer, List> respectively.

Various methods in the public API at 2.x consumed the returned set - for example MapView.ZoomTo(IReadOnlyDictionary<BasicFeatureLayer,List>,...), MapView.PanTo(IReadOnlyDictionary<BasicFeatureLayer,List>,...), and many of the EditOperation methods such as EditOperation.Move(IEnumerable<KeyValuePair<MapMember,List>>,...), EditOperation.Reshape(IEnumerable<KeyValuePair<MapMember,List>>,...), EditOperation.Rotate(IEnumerable<KeyValuePair<MapMember,List>>,...) etc.

At 3.0, this collection of MapMembers and objectIDs has been replaced with a SelectionSet class. The corresponding methods in the public API that consumed the 2.x set, have been changed to consume the new SelectionSet class.

For those addins that were using LINQ to filter the returned set Dictionary at 2.x, the SelectionSet class provides a ToDictionary conversion method. ToDictionary() converts the SelectionSet into a LINQ Dictionary collection which can then be manipulated via LINQ the same as was possible at 2.x. There is also a FromDictionary method to create a SelectionSet from a set collection. Some examples follow:

 QueuedTask.Run(() => 
 {
   var sel_poly = .... ;//Polygon to use for selection

   // get the set of features intersecting sel_poly
   //  2x returned Dictionary<BasicFeatureLayer,List<long>>
   var selSet = MapView.Active.GetFeatures(sel_poly);

   // convert to the dictionary
   var selSetDict = selSet.ToDictionary();

   // convert to the dictionary and only include those that are of type FeatureLayer
   var selSetDict = selSet.ToDictionary<FeatureLayer>();



   //zoom to the extent of the retrieved set of features - No change
   MapView.Active.ZoomTo(MapView.Active.GetFeatures(sel_poly));


   //move the selected set of features - No change
   var editOp = new EditOperation() { ..... };
   editOp.Move(MapView.Active.SelectFeatures(sel_poly), 500.0, 500.0);
   editOp.Execute();


   //rotate - no change
   var editOp = new EditOperation() { ..... };
   editOp.Rotate(MapView.Active.SelectFeatures(sel_poly), origin, 35.0);
   editOp.Execute();
   

   //get the geometry of the first selected feature. 
   // Use ToDictionary() to apply LINQ
   var selSet = MapView.Active.GetFeatures(sel_poly);
   var selSetDict = selSet.ToDictionary();
   var insp = new Inspector();
   insp.Load(selSetDict.Keys.First(), selSetDict.Values.First());
   var selGeom = insp.Shape;


   //Get the list of object ids from SelectFeaturesEx for a particular layer. 
   // Use ToDictionary() to apply LINQ
   var sname = "Points of Interest";
   var selSet = MapView.Active.SelectFeaturesEx(env);
   var oids1 = selSet.ToDictionary().Where(kvp => kvp.Key.Name == sname).First().Value;
   //TODO - use the object ids


    //Create a selection set from a list of object ids
    //using FromDictionary
    var addToSelection = new Dictionary<MapMember, List<long>>(); 
    addToSelection.Add(us_zips, new List<long> { 1506, 2696, 2246, 1647, 948 }); 
    var sset = SelectionSet.FromDictionary(addToSelection); 
    //TODO - use sset


    // get the objectIDs from mapMember in the features intersected by sel_poly
    var selSet = MapView.Active.GetFeatures(sel_poly);
    if (selSet.Contains(mapMember))
    {
      var oids = selSet[mapMember;
    }


    //etc
 });

Display Filters

Display filters are scale-dependent queries that specify which features of a layer are drawn at which scale ranges on a map. Display filters are ideal for use with dense feature datasets that have a large number of features being drawn at small scales. This can make it difficult to visually interpret the data and makes the layer slow to draw. Display filters differ from definition queries in the following two main ways:

  • Display filters affect the display of features only. Non-visible features that are excluded from the display by a display filter are still accessible by the layer for queries and selection. Filtered records will also still show up in the attribute table view. Definition queries, on the other hand, filter features entirely from the layer.
  • Display filters are scale dependent and are mutually exclusive. Scale ranges for display filters cannot overlap. Definition queries, meanwhile, can include or exclude the same sets of overlapping features depending on the nature of the underlying definition query filter.

Note: Some general information about Display Filters can be found here: ArcGIS Pro Use Display Filters

Modifying Display Filters

You can create and apply Display Filters to a feature layer using CIMDisplayFilter, part of the feature layer CIM (Cartographic Information Model) definition. Create a CIMDisplayFilter for each scale range that will be filtered. Set theMinScale and MaxScale properties of the CIMDisplayFilter to specify the scale range of the filter and provide a WhereClause to do the actual filtering (of features). Display filter scale ranges should not overlap.

In the following code snippet, an array of CIMDisplayFilters are defined, specifying flow rates to show at different consecutive scale ranges for a hydrology dataset. The display filters are controlling the feature density at different scales without impacting the symbology or the underlying features:

     //Create a list of Display Filters
     var arrayDisplayFilters = new List<CIMDisplayFilter>()
     {
       new CIMDisplayFilter{ Name = "StreamOrder > 6", 
                  WhereClause = "StreamOrder > 6", MinScale= 0, MaxScale=50000000},
       new CIMDisplayFilter{ Name = "StreamOrder > 5", 
                  WhereClause = "StreamOrder > 5", MinScale= 50000000, MaxScale=20000000},
       new CIMDisplayFilter{ Name = "StreamOrder > 4", 
                  WhereClause = "StreamOrder > 4", MinScale= 20000000, MaxScale=5000000},
       new CIMDisplayFilter{ Name = "StreamOrder > 3", 
                  WhereClause = "StreamOrder > 3", MinScale= 5000000, MaxScale=1000000},
       new CIMDisplayFilter{ Name = "StreamOrder > 2", 
                  WhereClause = "StreamOrder > 2", MinScale= 1000000, MaxScale=100000},
       new CIMDisplayFilter{ Name = "StreamOrder > 1", 
                  WhereClause = "StreamOrder > 1", MinScale= 100000, MaxScale=24000},
     };

The display filters are applied via the layer CIM Definition CIMFeatureLayer.DisplayFilters* collection property. To use the display filter, the layer's CIMFeatureLayer.EnableDisplayFilters property, also on the CIM definition, must be set to true. Changes to the CIM are applied with a featureLayer.SetDefinition(...); call. A complete example is shown below:

  //Get the Hydrology layer from the TOC
  var hydrologyLyr = MapView.Active.Map.FindLayers("Hydrology").First() as FeatureLayer;
  await QueuedTask.Run(() => 
  {

    //Get the CIM Definition
    var cimDefn = hydrologyLyr.GetDefinition() as CIMFeatureLayer;

    //Create a list of Display Filters
    var arrayDisplayFilters = new List<CIMDisplayFilter>()
    {
      new CIMDisplayFilter{ Name = "StreamOrder > 6", 
                  WhereClause = "StreamOrder > 6", MinScale= 0, MaxScale=50000000},
      new CIMDisplayFilter{ Name = "StreamOrder > 5", 
                  WhereClause = "StreamOrder > 5", MinScale= 50000000, MaxScale=20000000},
      new CIMDisplayFilter{ Name = "StreamOrder > 4", 
                  WhereClause = "StreamOrder > 4", MinScale= 20000000, MaxScale=5000000},
      new CIMDisplayFilter{ Name = "StreamOrder > 3", 
                  WhereClause = "StreamOrder > 3", MinScale= 5000000, MaxScale=1000000},
      new CIMDisplayFilter{ Name = "StreamOrder > 2", 
                  WhereClause = "StreamOrder > 2", MinScale= 1000000, MaxScale=100000},
      new CIMDisplayFilter{ Name = "StreamOrder > 1", 
                  WhereClause = "StreamOrder > 1", MinScale= 100000, MaxScale=24000},
    };

    //apply the display filter collection to the CIM definition
    cimDefn.DisplayFilters = arrayDisplayFilters.ToArray();
    //make sure display filters are enabled
    cimDefn.EnableDisplayFilters = true;

    //apply the change to the CIM - note "filtered" features still show up in the attribute
    //table view and are available for query and selection...
    hydrologyLyr.SetDefinition(cimDefn);
  });

* CIMFeatureLayer.DisplayFilters can be null when EnableDisplayFilters is false.

To remove display filters, delete the relevant CIMDisplayFilter(s) from the layer DisplayFilters collection. To turn display filters "off", set the EnableDisplayFilters property to false. Any changes to the CIM must always be applied via a SetDefinition(...) call.

The below screenshots show the same USA hydrology dataset, from the snippet above, being drawn at different scale ranges and symbolized by flow volume with no display filter. Notice that the feature density obscures any meaningful information on the map at small scales:

DetailedHydrology

With the display filters applied, more and more features are progressively displayed as the map scale changes (and is "zoomed in"). At the smallest scales, only the rivers with the largest flow rates are displayed ("StreamOrder > 6"):

MajorRivers

At larger scales, the query of the relevant display filter (for "that" scale range) allows smaller flow-rate rivers to be draw, progressively adding to the map detail as the map is "zoomed in" ("StreamOrder > 4"):

SmallerRivers

Finally, at the largest scales, the display filters allow all features to draw, regardless of their flow rate attribution:

allRivers

A complete "Display Filter" sample is also available: DisplayFilters sample

Time filters

A layer or stand-alone table can be configured to work with time by setting its temporal properties that indicate where the data exists in time. This configuration can be either based on attribute fields in the data, such as the date and time of earthquakes, or setting a fixed extent for the entire layer, such as an aerial image with an effective lifespan of three months. Layers and tables that have temporal properties are often referred to as time-aware.

Note: General information about working with time aware data can be found here: Get started with time

Set time properties on data

A MapMember has to be time aware in order to be configured to work with time. To check if a map member is "time aware", use the IsTimeSupported method. This method returns True if the MapMember supports time.

var lyr = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
if (!lyr.IsTimeSupported()) return; //Note: run within the context of QueuedTask.

The TimeParameters class represents a set of properties and methods that can be used to create time filters to visualize map members. As mentioned previously, you can create time filters based on attribute fields in the data or by setting a fixed extent for the entire layer. The following sections illustrate these two scenarios.

Set time properties using attribute fields

For data that contains temporal values, the time properties available for the layer vary depending on the dataset. For feature classes, stand-alone tables, catalog datasets, mosaic datasets, and netCDF feature layers, you can specify one field (time field) or two fields (a start time field and, optionally, an end time field). If one time field is used to visualize the data, it means each feature in the layer exists at that instant in time. Specifying the start and end time field to visualize a layer means that each feature in the layer exists for a certain duration in time.

To visualize the data using one time field, set the TimeParameters class' StartTimeFieldName property to the single field name. In order to visualize the data using two time fields (start and end time field), set the StartTimeFieldName and the EndTimeFieldName properties of the TimeParameters class.

var tParams = new TimeParameters();
tParams.StartTimeFieldName = "StartTimeFieldName";
tParams.EndTimeFieldName = "EndimeFieldName"; //Optional. Needed only to visualize data within a duration in time.

In order to create time filters, you will also need to set the time extent during which you want to visualize your data. In general this time extent represents the minimum and maximum dates of your data. GetDataTimeExtent method available on the MapMember can be used to get the time extent (minimum and maximum dates) of the data for the specified field. This method returns a TimeExtent object that has a StartTime property that stores the minimum date value of the specified field and a EndTime property that stores the maximum date value of the specified field.

TimeExtent timeExtent = lyr.GetDataTimeExtent("DateField"); //Note: run within the context of QueuedTask.
DateTime startTime = timeExtent.StartTime;
DateTime endTime = timeExtent.EndTime;

These StartTime and EndTime property values of the TimeExtent class can now be used to configure the TimeParameters class in order to visualize the map member within the specified time frame. The TimeRange property of the TimeParameters class gets and sets the time extent. Set the Start and End properties of the TimeParameters' TimeRange property with these values. The code snippet below shows how to set the TimeRange property

tParams.TimeRange = new TimeRange();
tParams.TimeRange.Start = lyr.GetDataTimeExtent("DateField").StartTime;
tParams.TimeRange.End = lyr.GetDataTimeExtent("DateField").EndTime;

The configured TimeParameters object with the time filter settings can now be applied to the map member for visualization. Test the validity of the time filter settings by calling the CanSetTime method on the map member. Pass in the configured TimeParameter object to the CanSetTime method. If true, apply the time parameter settings by calling the SetTime method. This method sets the specified time parameters for the MapMember.

if (lyr.CanSetTime(tParams)){
       lyr.SetTime(tParams); //apply the filter
}

Below is the complete code snippet for applying a time filter to a feature layer using time stamps stored in two fields.

 var lyr = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
if (lyr == null) return;
QueuedTask.Run(() =>{
   if (!lyr.IsTimeSupported()) return;
   //Setting the TimeParameter to use for the filter.
   var tParams = new TimeParameters();
   tParams.StartTimeFieldName = "ReportDate";
   tParams.EndTimeFieldName = "EndDate";
   //Caluclating the time extent for visualizing
   TimeExtent startTimeExtent = lyr.GetDataTimeExtent("ReportDate");
   TimeExtent endTimeExtent = lyr.GetDataTimeExtent("EndDate");
   tParams.TimeRange = new TimeRange();
   tParams.TimeRange.Start = startTimeExtent.StartTime;
   tParams.TimeRange.End = endTimeExtent.EndTime;
   //Testing the validity of the time filter
   if (lyr.CanSetTime(tParams)){
       lyr.SetTime(tParams); //apply the filter
    }
});

Set time properties using a fixed time extent

Time properties for an entire layer can be set to a fixed time extent. In order to display an entire layer within a fixed time extent, the Start and End time extent properties of TimeParameter's TimeRange property have to be set. The code snippet below illustrates how to set a feature layer to display only during a specific time extent.

var lyr = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
if (lyr == null) return;
QueuedTask.Run(() => {
     if (!lyr.IsTimeSupported()) return;
     //Setting an extent time with start and end
     var tParams = new TimeParameters();
     tParams.TimeRange = new TimeRange();
     tParams.TimeRange.Start = new DateTime(2013, 2, 1);
     tParams.TimeRange.End = new DateTime(2013, 3, 1);
     if (lyr.CanSetTime(tParams)){
        lyr.SetTime(tParams);
     }
 });

Additional Temporal properties

Time Interval settings

The time interval used to capture the data can be set using the TimeParameters class. To set the time interval, use the TimeIntervalType property on the TimeParameters class. Use the TimeIntervalType enum as possible values for the TimeIntervalType property.

TimeIntervalType enum Definition
TimeIntervalType.None Indicates there is no time interval
TimeIntervalType.Regular Indicates the data was captured at regular intervals, such as hour temperature readings.
TimeIntervalType.Irregular ** Indicates the data was captured at irregular intervals, such as earthquakes or power outages.

** Note: If TimeIntervalType is set to TimeIntervalType.Irregular and there are more than 1000 values present in the data, the TimeIntervalType reverts to TimeIntervalType.None.

Use the StepInterval property on the TimeParameters class to get or set the interval value and unit when TimeIntervalType is Regular.

 tParams.TimeIntervalType = TimeIntervalType.Regular;
 tParams.StepInterval = new TimeDelta(49, TimeUnit.Days);

Time format

The TimeFormat property on the TimeParameters class is used to get and set the time format for the data. Set this when specifying StartTimeFieldName or EndTimeFieldName properties for the TimeParameters class and the field type is string or numeric. Determine the best Time Format to use, based on the time stamps in the field. Use GetSupportedTimeFormats to verify your choice is valid for the field type.

Clear time

You can programmatically clear time filters applied to a layer by calling the ClearTime method on the MapMember. Clearing the time filter ensures that data is always shown. This method must be called on the MCT. Use QueuedTask.Run.

Note: You should always call the CanClearTime method on the MapMember method before calling the ClearTime method.

if (lyr.CanClearTime()){
    lyr.ClearTime();
}

Replacing data sources

There are numerous reasons why data sources need to be repaired or redirected to different locations. The idea of making these changes manually in every affected map document can be overwhelming. Methods are available with Map, Layer, and StandaloneTable classes to make these changes programmatically. You have control of updating data sources for individual map members, or you can update those that have a common workspace all at once.

FindAndReplaceWorkspacePath, available on Map, Layer, and StandaloneTable classes, allows you to substitute an entire or partial string for a layer or table's workspace path. It can't be used if the workspace type or dataset name has changed. It is ideal for scenarios where drive letters change, switch to UNC paths, update SDE connection file information, and so on.

ReplaceDatasource (also on the Map, Layer and StandaloneTable classes) allows you to change the entire workspace path for all layers and tables that share the workspace path.

The Map class also provides the ChangeVersion method to switch and view data from a different geodatabase version.

Display and Feature Cache

ArcGIS Pro uses a cache to optimize performance when working in previously visited extents. This cache is refreshed when changes are made to a map, layer properties, or the data therein, such as when the features in a map are edited. This is known as the display cache. In most cases, caching options can be set to invalidate the cache frequently, or to not use the cache at all. The DisplayCacheType property controls this setting for each layer.

For web feature layers, a feature cache is used to improve performance and common tasks by storing features in a temporary folder. The cache is automatically managed and as features are cached, the number of queries needed to retrieve data is reduced and drawing time is improved. This can be useful when working with large or complex datasets, in a specific map extent, or when numerous users are using the same service. This also reduces strain on the server as it reduces the number of service requests. The FeatureCacheType property controls the setting for whether a layer will participate in the feature cache. Only certain types of layers (BasicFeatureLayer, SubtypeGroupLayer) support the FeatureCacheType property.

You can update the cache options for a layer by using SetCacheOptions. If the layer's feature class supports feature caching, then both the display cache and feature cache are set otherwise only the display cache is set. The Following table shows the mapping between the LayerCacheType and DisplayCacheType and FeatureCacheType enumerations.

LayerCacheType DisplayCacheType FeatureCacheType Notes
None None None Don't cache the layer locally. If the layer supports feature caching, then any existing cache is also cleared.
Session InSession Session Cache will be cleared when the session ends.
MaxAge MaxAge N/A

Cache will be kept between sessions and cleared every Layer.MaxDisplayCacheAge minutes. Use Layer.SetDisplayCacheMaxAge to set the cache age. Not supported for layers that support feature caching (i.e. feature service layers).

Permanent Permanent N/A Cache will be kept between sessions and invalidated when the data is updated. Not supported for layers that support feature caching (i.e. feature service layers).

Here's a code snippet showing the SetCacheOptions methods.

  DisplayCacheType currentDCT = featureLayer.DisplayCacheType;
  FeatureCacheType currentFCT = featureLayer.FeatureCacheType;

  await QueuedTask.Run(() =>
  {
    featureLayer.SetCacheOptions(LayerCacheType.None);
  });

By default, the feature cache is filled automatically by the application. However you can manage the cache manually to take advantage of the performance benefits provided by the cache. Use the AutoFillFeatureCache and SetAutoFillFeatureCache routines to control this setting.

The feature cache must be filled before it can be used. You can choose to fill the cache for the entire map (only applies to the web feature layers in the map) or for a specified set of layers. The rules for when a cache is filled for a layer are outlined here. Note that the layer's FeatureCacheType must be set to FeatureCacheType.Session (along with meeting the other rules) for the feature cache to be generated for that layer.

Use the CanFillFeatureCache method to determine whether the application is currently fulfilling an existing fill cache request before calling one of the FillFeatureCacheAsync methods. The FillFeatureCacheAsync methods will throw an InvalidOperationException if the application is fulfilling an existing fill cache request when called. For this reason it is recommended to wrap the cache methods in a try/catch. Use the showToast parameter to signify whether a toast notification is to be displayed in the application during execution of the operation.

FeatureCacheToast

  var map = MapView.Active.Map;
  if (map.CanFillFeatureCache())
  {
    var msg = await QueuedTask.Run(() =>
    {
      try
      {
        map.FillFeatureCacheAsync(showToast);
        // or specify a set of web feature layers
        // map.FillFeatureCacheAsync(layers, showToast);

        return "";
      }
      catch (Exception ex)
      {
        return ex.Message;
      }
    });
  }

Clear the feature cache with the ClearFeatureCacheAsync methods. As with the fill methods, you can clear the cache for the entire set of web feature layers (feature services) in the map or for a subset of layers.

  var msg = await QueuedTask.Run(async () =>
  {
    try
    {
      await MapView.Active.Map.ClearFeatureCacheAsync(showToast);
      //or a set of layers
      // await MapView.Active.Map.ClearFeatureCacheAsync(layers, showToast);
      return "";
    }
    catch (Exception ex)
    {
      return ex.Message;
    }
  });

You can cancel any existing cache requests via the CancelFeatureCacheRequestsAsync method.

  var msg = await QueuedTask.Run(() =>
  {
    try
    {
      MapView.Active.Map.CancelFeatureCacheRequestsAsync(showToast);
      return "";
    }
    catch (Exception ex)
    {
      return ex.Message;
    }
  });

The FeatureCacheEvent provides information when feature cache operations are being executed. Subscribe to this event if you wish to be informed. For example, the following snippet will allow you to show your own toast notification message when cache operations are started, completed, canceled etc. Use the FeatureCacheEventArgs.EventHint to determine the event hint.

FeatureCacheEvent.Subscribe(OnFeatureCacheHint);
...

internal void OnFeatureCacheHint(FeatureCacheEventArgs args)
{
  _ = ArcGIS.Desktop.Internal.Mapping.Utilities.StartOnUIThread(() =>
  {
    string msg = "";
    switch (args.EventHint)
    {
      case FeatureCacheEventHint.NoLayers:
        msg = "There are no layers to cache";
        break;
      case FeatureCacheEventHint.Started:
        msg = "Cache Fill operation started";
        break;
      case FeatureCacheEventHint.Completed:
        msg = "Cache fill operation completed";
        break;
      case FeatureCacheEventHint.Clear_Started:
        msg = "Cache clear operation started";
        break;
      case FeatureCacheEventHint.Clear_Completed:
        msg = "Cache clear operation completed";
        break;
      case FeatureCacheEventHint.Canceled:
        msg = "Cache Fill operation canceled";
        break;
      case FeatureCacheEventHint.Error:
        msg = "An error in the cache operation.";
        break;
    }

    Notification toast = new Notification
    {
      Title = "My notification",
      Message = msg,
    };

    toast.ImageSource = BitmapUtil.GetImageSource(
          "pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/Success_Toast48.png");

    FrameworkApplication.AddNotification(toast);
  });
}

Styles

Styles are project items that contain reusable things such as symbols, colors, color schemes, and layout elements. They are represented by the StyleProjectItem class. The styles in a project can be retrieved as follows:

//Get all styles in the project
var styles = Project.Current.GetItems<StyleProjectItem>();

//Get a specific style in the project
StyleProjectItem style = styles.First(x => x.Name == "NameOfTheStyle");

ArcGIS Pro includes system styles that are installed and available to add to a project. These are read-only; you cannot modify their contents. When a new project is created, four system styles (ArcGIS 2D, ArcGIS 3D, ArcGIS Colors, and ColorBrewer Schemes RGB) are added to the project to ensure galleries of versatile symbols, colors, color schemes, and layout elements.

In addition to the system styles, you can create new custom styles and add these to your project as follows:

 //Full path for the new style file (.stylx) to be created and added to the project
 string styleToCreate = @"C:\Temp\NewStyle.stylx";
 Project.Current.CreateStyle(styleToCreate);

Or if the style file already exists, use the following to add new styles (system or custom) to your project.

//For ArcGIS Pro system styles, just pass in the name of the style to add to the project
Project.Current.AddStyle("3D Vehicles");

//For custom styles, pass in the full path to the style file on disk
string customStyleToAdd = @"C:\Temp\CustomStyle.stylx";
Project.Current.AddStyle(customStyleToAdd);

Removing a style from a project does not delete or otherwise change the style or its contents. Styles can be removed from a project as follows:

//For core ArcGIS Pro styles, just pass in the name of the style to remove from the project
Project.Current.RemoveStyle("3D Vehicles");

//For custom styles, pass in the full path to the style file on disk
string customStyleToRemove = @"C:\Temp\CustomStyle.stylx";
Project.Current.RemoveStyle(customStyleToRemove);

Favorite Style

In addition to the 4 system styles, a "Favorites" style is also added to each new project. By default, this style is empty. However style items can be added to "Favorites" via either the UI or the public API using AddItem(...). Accessing the favorites style is a little different than accessing one of the system styles as it is not stored in the project items collection. Instead, it is stored in the Style container's items collection and is provisioned when the project is opened (or is being created if this is a new project). To access the Favorites style, use the following snippet:

//note: we must be on the MCT to call "GetItems" on the style container
var fav_style_item = await QueuedTask.Run(() => 
{
  var style_container = Project.Current.GetProjectItemContainer("Style");
  return style_container.GetItems().OfType<StyleProjectItem>().First(item => item.TypeID == "personal_style");
});
if (fav_style_item != null)
{
  ...
}

Upgrading styles

The underlying structure of symbols and styles may change with successive releases of ArcGIS Pro. That means that styles created in earlier versions may not be current. Styles that are not current can still be used in a project but will be read-only. You can check whether a style is current as follows:

//For custom styles, pass in the full path to the style file on disk
string customStyleToAdd = @"C:\Temp\CustomStyle.stylx";
Project.Current.AddStyle(customStyleToAdd);
StyleProjectItem style = Project.Current.GetItems<StyleProjectItem>().First(x => x.Path == customStyleToAdd);

//returns true if style matches the current Pro version
bool isCurrent = style.IsCurrent;  

If a style is not current, it can be upgraded. Upgrading will allow you to add, delete, and modify the contents of the style.

//For custom styles, pass in the full path to the style file on disk
string customStyleToAdd = @"C:\Temp\CustomStyle.stylx";
Project.Current.AddStyle(customStyleToAdd);
StyleProjectItem style = Project.Current.GetItems<StyleProjectItem>().First(x => x.Path == customStyleToAdd);

//Upgrade style
if (style.CanUpgrade)
{
 StyleHelper.UpgradeStyle(style);
}

Style Items

StyleItem represents an item contained in a style. A style can contain the following types of style items:

  • SymbolStyleItem—Can be of the following types:

    • Point symbol—Symbol to draw point features.
    • Line symbol—Symbol to draw line features.
    • Polygon symbol—Symbol to draw polygon features.
    • Text symbol—Symbol to draw labels or annotation features.
  • ColorStyleItem—Single color defined by values in a supported color model.

  • ColorRampStyleItem—Array of colors in a specified pattern or scheme.

  • NorthArrowStyleItem—Directional layout element used to indicate north orientation.

  • ScaleBarStyleItem—Layout element that graphically indicates map scale.

  • MapSurroundStyleItem —Layout element that graphically indicates a map surround.

  • LabelPlacementStyleItem—Can be of the following types:

    • Standard label placement—Set of rules based on the standard label engine determining how labels will be placed in relation to features.
    • Maplex label placement—Set of rules based on the Maplex label engine determining how labels will be placed in relation to features.

You can search for a specific type of style items in a style. For example, this example shows how to search for point symbols in a style:

//Get all point symbols in a style - pass in an empty string for the searchString parameter
IList<SymbolStyleItem> allPointSymbols = style.SearchSymbols(StyleItemType.PointSymbol, "");

//Get point symbols in a style that satisfy the search criteria
IList<SymbolStyleItem> pointSymbols = style.SearchSymbols(StyleItemType.PointSymbol, "red");

Similarly, you can also search for other types of style items. For example, here is how to search for colors in a style:

//Get colors in a style that satisfy the search criteria
IList<ColorStyleItem> colors = style.SearchColors("blue");

Each style item in a style has a unique key, and this key can be used to retrieve the specific style item from a style as follows:

//Get a specific style item from a style based on the KEY of the style item
//In this example, "Cone_Volume_3" is the KEY of the Cone symbol in the "ArcGIS 3D" system style
SymbolStyleItem redCone = await QueuedTask.Run<SymbolStyleItem>(() =>
{
  return style.LookupItem(StyleItemType.PointSymbol, "Cone_Volume_3") as SymbolStyleItem;
});

Style items can be added or removed from a style as follows:

//Add a style item to a style
await QueuedTask.Run(() =>
{
  style.AddItem(styleItemToAdd);
});

//Remove a style item from a style
await QueuedTask.Run(() =>
{
  style.RemoveItem(styleItemToRemove);
});

Once you have the StyleItem, it is easy to retrieve it's content. For example retrieve a symbol from a SymbolStyleItem as follows:

//Get symbol from SymbolStyleItem
CIMSymbol symbol = await QueuedTask.Run<CIMSymbol>(() =>
{
  return symbolStyleItem.Symbol;
});

Similarly, you can retrieve color, color ramp, north arrow, and so on, from their respective style items. For example, this is how you retrieve the north arrow from a NorthArrowStyleItem:

//Get north arrow from NorthArrowStyleItem
CIMNorthArrow northArrow = await QueuedTask.Run<CIMNorthArrow>(() =>
{
  return northArrowItem.NorthArrow;
});

The StyleItem class is designed to support data binding, which helps in creation of custom symbol galleries. Style items can be retrieved from a style based on search criteria, and the results can be displayed along with a preview image for each style item in a gallery. The CustomSymbolPicker SDK sample shows how this can be accomplished.

Creating and editing symbols

SymbolFactory.Instance provides many static methods and properties to create new symbols. For example, new symbols can be created as follows:

//Construct a point symbol of a specific color, size and shape
CIMPointSymbol pointSymbol = await QueuedTask.Run<CIMPointSymbol>(() =>
{
  return SymbolFactory.Instance.ConstructPointSymbol(
          ColorFactory.Instance.RedRGB, 10.0, SimpleMarkerStyle.Star);
});

//Construct a polygon symbol of specific color and fill style
CIMPolygonSymbol polygonSymbol = 
   SymbolFactory.Instance.ConstructPolygonSymbol(ColorFactory.Instance.RedRGB, SimpleFillStyle.Solid);

//Construct a line symbol of specific color, size and line style
CIMLineSymbol lineSymbol = 
   SymbolFactory.Instance.ConstructLineSymbol(ColorFactory.Instance.BlueRGB, 4.0, SimpleLineStyle.Solid);

Methods are also provided for editing some properties of symbols. For example:

//Set rotation of point symbol
pointSymbol.SetAngle(30.0);

//Set width of line symbol
lineSymbol.SetSize(4.0);

In 3D scenes, point, line and polygon symbols can be displayed either in real-world units or in page units. If the symbol is displayed in real-world units, the symbol will scale with distance, which means that the symbol will display larger when you zoom in closer and smaller as you zoom away from the symbol. When creating new symbols, it is important to set the real-world unit setting of the symbol to be the same as that of the feature layer so that you do not see unexpected display results because of a mismatch. Here is how this can be done:

//Set real world setting of symbol to be the same as that of the feature layer
pointSymbol.SetRealWorldUnits(featureLayer.UsesRealWorldSymbolSizes);

3D specific symbols can also be created using the SymbolFactory.

//Construct a 3D point symbol of a specific color, size and shape
CIMPointSymbol pointSymbol = await QueuedTask.Run<CIMPointSymbol>(() =>
{
  return SymbolFactory.Instance.ConstructPointSymbol(
          ColorFactory.Instance.RedRGB, 10.0, Simple3DMarkerStyle.Diamond);
});

//Construct a line symbol of specific color, size and line style
CIMLineSymbol lineSymbol = 
   SymbolFactory.Instance.ConstructLineSymbol(ColorFactory.Instance.BlueRGB, 4.0, 
                           SimpleLineStyle.Solid, Simple3DLineStyle.Square);

//Construct a polygon symbol with a 3D marker fill
var marker = SymbolFactory.Instance.ConstructMarker(ColorFactory.Instance.CreateRGBColor(249, 159, 0), 
                                 Simple3DMarkerStyle.Diamond);
marker.MarkerPlacement = new CIMMarkerPlacementInsidePolygon() { StepX = 2, StepY = 2 };
var fill = SymbolFactory.Instance.ConstructFill();
var stroke = SymbolFactory.Instance.ConstructStroke();
var polygonMarkerFill = SymbolFactory.Instance.ConstructPolygonSymbol(fill, stroke, marker);

Using symbols with renderers

To update the symbol of a feature layer, you need to first get the feature layer's current renderer by calling the GetRenderer method, set the renderer's symbol reference to the symbol reference of the new symbol, and update the feature layer renderer by calling the SetRenderer method. For example, here is how the symbol can be updated for a feature layer symbolized with a simple renderer:

//Get simple renderer from the feature layer
CIMSimpleRenderer currentRenderer = featureLayer.GetRenderer() as CIMSimpleRenderer;

//Set the symbol's real world setting to be the same as that of the feature layer
symbolToApply.SetRealWorldUnits(featureLayer.UsesRealWorldSymbolSizes);

//Update the symbol reference of the current simple renderer
currentRenderer.Symbol = symbolToApply.MakeSymbolReference();

//Update the feature layer renderer
featureLayer.SetRenderer(currentRenderer);

Device Location API, GNSS/GPS Data

You can connect a global navigation device, such as a GNSS receiver, to ArcGIS Pro, to view your current location in a map or scene. GNSS, or Global Navigation Satellite System, is the standard generic term used for satellite navigation systems that provide autonomous geo-spatial positioning with global coverage. The Device Location API allows you to connect to, visualize, and access data from a connected GNSS device.

Connect to a GNSS Device

To connect a GNSS device to Pro, it must support output in the form of National Marine Electronics Association, NMEA, sentences. Add-ins will use the ArcGIS.Desktop.Core.DeviceLocation.DeviceLocationService.Instance singleton to open, close, and update connections to GNSS devices. Multiple GNSS devices can be connected to a machine, however, only one device can be set as the currently active, or "open", connection. To open a connection, add-ins must specify a DeviceLocationSource and, optionally, any associated DeviceLocationProperties. Currently, only one device location source is supported: a SerialPortDeviceLocationSource source object. As a minimum, the ComPort the device location source is connected to must be specified. If a spatial reference is not specified then WGS84 is assumed. If a baud rate is not specified, 4800 is assumed. The DeviceLocationProperties parameter can be used to set the AccuracyThreshold if necessary.

  var newSrc = new SerialPortDeviceLocationSource();
  newSrc.ComPort = "Com3";//required
  newSrc.BaudRate = 4800; //optional
  newSrc.AntennaHeight = 3;  // meters
                             //Sr WGS84 will be assumed

  var props = new DeviceLocationProperties();
  props.AccuracyThreshold = 10;   // meters

  try
  {
    // jump to the background thread
    await QueuedTask.Run(() =>
    {
      // open the source
      DeviceLocationService.Instance.Open(newSrc, props);
    });
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message);
  }

Add-ins can call DeviceLocationService.Instance.IsDeviceConnected() to determine if a source is currently connected and DeviceLocationService.Instance.GetSource() to retrieve its connection parameters.

  if (DeviceLocationService.Instance.IsDeviceConnected()) 
  {
    var src = DeviceLocationService.Instance.GetSource();
    //Get the connection source properties
    if (src is SerialPortDeviceLocationSource serialPortSrc) 
    {
      var port = serialPortSrc.ComPort;
      ... //etc

Add-ins can disconnect from the current GNSS device by calling the DeviceLocationService.Instance.Close() method.

  await QueuedTask.Run(() =>
  {
    //If no device is connected, close is a no-op
    DeviceLocationService.Instance.Close();
  });

Accessing GNSS data

The current snapshot data can be retrieved via the DeviceLocationSource.Instance.GetCurrentSnapshot() method. Snapshots are of type NMEASnapshot; a data structure used to represent a feed from a device that conforms to the NMEA specifications.

In the following snippet, the latest snapshot is being retrieved and added to a graphics layer:

  // ensure there is a device
  var source = DeviceLocationService.Instance.GetSource();
  if (source == null)
     return;

  bool validPoint = await QueuedTask.Run(() =>
  {
    // get the last location
    var snapshot = DeviceLocationService.Instance.GetCurrentSnapshot();

    var pt = snapshot?.GetPositionAsMapPoint();
    if ((pt == null) || (pt.IsEmpty))
      return false;

    // create symbol
    var ptSymbol = SymbolFactory.Instance.ConstructPointSymbol(
                       CIMColor.CreateRGBColor(125, 125, 0), 10, SimpleMarkerStyle.Triangle);

    // Add it to the graphics layer
    graphicsLayer.AddElement(pt, ptSymbol);
    graphicsLayer.ClearSelection();
    return true;
  });

In addition to the current snapshot, add-ins can subscribe to the SnapshotChangedEvent (in ArcGIS.Desktop.Core.DeviceLocation.Events namespace). This event will be raised each time a new snapshot is received (from the open device).

  SnapshotChangedEvent.Subscribe(OnSnapshotChanged)

  private void OnSnapshotChanged(SnapshotChangedEventArgs args)
  {
    if (args == null)
      return;

    var snapshot = args.Snapshot as NMEASnapshot;
    if (snapshot == null)
      return;

    QueuedTask.Run(() =>
    {
      var pt = snapshot.GetPositionAsMapPoint();        

      // examine properties of MMEASnapshot
      // snapShot.DateTime, snapshot.Altitiude
      // snapshot.Latitude, snapshot.Longitude
      // snapshot.VDOP  etc

      //TODO - use returned point
    });
  }

Note: Use the editing API EditOperation.Create to create features with the current device location coordinates.

Interacting with the map

To start tracking the location updates, or "snapshots", on the active view, the open device location source must be enabled (on the current map). Add-ins enable (and disable) the open device location source via the MapDeviceLocationService.Instance singleton object calling MapDeviceLocationService.Instance.SetDeviceLocationEnabled(true | false). Before enabling the device location source, add-ins should check that there is an active view and map and there is an open device location source or an InvalidOperationException will be thrown.

  //Enable the current device location source...

  //There must be an active map or an InvalidOperationException will be thrown
  if (MapView.Active?.Map == null)
     return;
  //There must be an open device location source or an InvalidOperationException will be thrown
  if (!DeviceLocationService.Instance.IsDeviceConnected())
     return;
  //Enable the device
  QueuedTask.Run(() => MapDeviceLocationService.Instance.SetDeviceLocationEnabled(true));

Once the device location source has been enabled on the current map, various options associated with how the snapshots can be tracked can be set on the map via MapDeviceLocationService.Instance.SetDeviceLocationOptions(options). The map will automatically track the incoming location snapshots (once enabled) depending on how the MapDeviceLocationOptions have been configured. The map can also be explicitly panned or zoomed to the most recent snapshot via MapDeviceLocationService.Instance.ZoomOrPanToCurrentLocation(true | false) to zoom or pan respectively. Calling either SetDeviceLocationOptions or ZoomOrPanToCurrentLocation with no device location source enabled will throw an InvalidOperationException.

Configuring the display of the current feed and then zooming to the current snapshot location is shown in the following example:

  //There must be an active map or an InvalidOperationException will be thrown
  if (MapView.Active?.Map == null)
     return;
  //There must be an open device location source or an InvalidOperationException will be thrown
  if (!DeviceLocationService.Instance.IsDeviceConnected())
     return;

  //Get the current options
  var options = MapDeviceLocationService.Instance.GetDeviceLocationOptions();

  //Configure...
  options.DeviceLocationVisibility = true; // show the device location
  options.NavigationMode = MappingDeviceLocationNavigationMode.KeepAtCenter;
  options.TrackUpNavigation = true;;

  // update the options
  QueuedTask.Run(() => 
  {
    MapDeviceLocationService.Instance.SetDeviceLocationOptions(options);
    //zoom to the most recent snapshot location
    MapDeviceLocationService.Instance.ZoomOrPanToCurrentLocation(true);

  });

Map Tray Buttons

Tray buttons can be found at the bottom of a map view or a layout view. There are a number of standard map tray buttons (Snapping, Grid, Constraints, Inference, Corrections) that are at the bottom of a map view. The number of visible map tray buttons is configurable according to view type (2D, 3D Scene etc). The following screenshot shows the tray button area and the standard map tray buttons for a 2D map.

mapTrayButtons

At 3.0, there is a Visual Studio template (ArcGIS Pro Map Tray Button) to allow you to create your own custom map tray buttons. A Map Tray Button is defined and registered in the esri_mapping_MapTrayButtons category in the config.daml file.

    <updateCategory refID="esri_mapping_MapTrayButtons">
      <insertComponent id="TrayButtons_MyTrayButton" className="MyTrayButton">
        <content L_name="My TrayButton" 
         largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed32.png" 
         smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed16.png"
         L_tooltipHeading="My TrayButton" L_tooltip="This is a sample tray button"></content>
      </insertComponent>

Each Map Tray Button class inherits from MapTrayButton and has a ButtonType property that can be set to Button, ToggleButton or PopupToggleButton. Each button type allows for different configurable behavior. Set the ClickCommand property when ButtonType is Button to execute some action when the button is clicked. Override the OnButtonChecked method to configure behavior when the button state is changed if ButtonType is ToggleButton or PopupToggleButton. Override the ConstructPopupContent method to provide your own UI to be displayed when the button is hovered over and the ButtonType is PopupToggleButton.

Custom tray buttons can be positioned using the placeWith and insert attributes in the config.daml file. You can also add a separator before your tray button by adding the separator attribute and setting it to true.

    <updateCategory refID="esri_mapping_MapTrayButtons">
      <insertComponent id="TrayButtons_MyTrayButton" className="MyTrayButton">
        <content L_name="My TrayButton" 
          largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed32.png" 
          smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed16.png"
          placeWith="esri_mapping_SnappingTrayButton" insert="before"
          L_tooltipHeading="My TrayButton" L_tooltip="This is a sample tray button"></content>
      </insertComponent>

This image shows the map tray area containing 2 custom map tray buttons, one inserted before Snapping, and the other (of type PopupToggleButton) preceded with a separator.

maptraybutton_popup

Override the IsVisibleOnView to customize the visibility of the tray button according to view type or other scenarios.

 public override bool IsVisibleOnView()
 {
   return Is2DMap;

  // another example
  //  show the tray button only when the map name is "MyMap"
  // return (this.MapView?.Map?.Name == "MyMap");
 }

Controlling AutoClose Behavior

For tray buttons of type PopupToggleButton, the default behavior of the popup is to auto-close when the mouse travels outside the border of the window. If the popup hosts other controls such as a combobox, DateTimePicker control or a ColorPicker control that launch UI not completely confined by the popup window, there can be situations where the popup closes too early. Typically this would occur when your mouse moves to interact with the child control and that mouse location is outside the confines of the popup window.

The MapTrayButton class and the Visual Studio MapTrayButton template provide code to help manage this situation.

  • TrayButton.CanAutoClose - a flag on the Tray button to control its auto-close behavior.
  • A property defined in the popup ViewModel class - CanPopupAutoClose
  • Two event handlers in the popup View code behind - PopupOpened and PopupClosed. The PopupOpened event handler sets the CanPopupAutoClose property on the ViewModel class to false. And the PopupClosed event handler resets the CanPopupAutoClose to true.
  • Additional code in the tray button class - the PropertyChanged event handler for the ViewModel sets the TrayButton.CanAutoClose when the viewModel's CanpPopupAutoClose property changes.

TrayButton developers need only ensure that the provided PopupOpened and PopupClosed event handlers are hooked to the child controls Opened and Closed events. This means that the TrayButton.CanAutoClose is set to false when the controls Opened event occurs and is reset to true when the controls Closed event occurs.

Here is an illustration of a MapTrayButton whose popup contains a combobox.

The xaml for the view has the comboboxes DropDownOpened and DropDownClosed events hooked into the provided PopupOpened and PopupClosed handlers.

 <Border BorderThickness="1" BorderBrush="{DynamicResource Esri_Blue}">
    <StackPanel
            Margin="1"
            Width="Auto" Height="Auto"
            Background="Transparent">
      <!--this is the header-->
      <CheckBox Style="{DynamicResource Esri_CheckboxTrayButtonHeader}"        
                    Foreground="{DynamicResource Esri_Blue}"
                    Background="{DynamicResource Esri_Gray105}"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}" >
        <TextBlock Style="{DynamicResource Esri_TextBlockTrayButtonHeader}"
              Text="{Binding Heading, Mode=OneWay}"/>
      </CheckBox>
      
      <!--content-->
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="Colors"/>
        <ComboBox Margin="10,10" Grid.Row="1" Width="200" DropDownOpened="PopupOpened"  
                                                          DropDownClosed="PopupClosed" >
          <ComboBoxItem>red</ComboBoxItem>
          <ComboBoxItem>green</ComboBoxItem>
          <ComboBoxItem>b;ie</ComboBoxItem>
          <ComboBoxItem>black</ComboBoxItem>
          <ComboBoxItem>white</ComboBoxItem>
        </ComboBox>
      </Grid>
    </StackPanel>
  </Border>

Here is the definition of the PopupOpened and PopupClosed handlers in the Code behind file. This code is supplied by the Visual Studio template.

  //Event handlers to control auto close behavior of tray button 
  private void PopupOpened(object sender, EventArgs e)
  {
    var vm = (DataContext as testtraybuttonPopupViewModel);
    vm.CanPopupAutoClose = false;
  }

  private void PopupClosed(object sender, EventArgs e)
  {
    var vm = (DataContext as testtraybuttonPopupViewModel);
    vm.CanPopupAutoClose = true;
  }

The CanPopupAutoclose property is defined in the viewModel source file. This code is supplied by the Visual Studio template.

  //Property to control auto close behavior of tray button 
  private bool _canPopupAutoClose;
  public bool CanPopupAutoClose
  {
    get => _canPopupAutoClose;
    set => SetProperty(ref _canPopupAutoClose, value);
  }

And finally the PropertyChanged handler in the tray button source file that ensures the TrayButton.CanAutoClose property is set appropriately. This code is also supplied by the Visual Studio template.

    private void testtraybuttonPopupViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
      if (_popupVM == null)
        return;
      // make sure MapTrayButton class has correct checked state when it changes on the VM
      if (e.PropertyName == nameof(testtraybuttonPopupViewModel.IsChecked))
      {
        // Since we are changing IsChecked in OnButtonChecked
        //We don't want property notification to trigger (another) callback to OnButtonChecked
        this.SetCheckedNoCallback(_popupVM.IsChecked);
      }
      //Control auto close behavior of tray button 
      else if (e.PropertyName == nameof(testtraybuttonPopupViewModel.CanPopupAutoClose))
      {
        this.CanAutoClose = _popupVM.CanPopupAutoClose;
      }
    }

Now when the tray button popup is displayed and the combobox is opened, the popup will not auto-close when the mouse is moved outside the popup window. The auto-close behavior of the tray button popup is reset when the combobox is closed.

A sample TrayButtons exists to illustrate the different tray buttons available. The ProGuide TrayButtons provides additional documentation and accompanies the sample.

UI controls

Coordinate System Picker

The Coordinate System Picker is a UI component similar to the core Coordinate Picker on the ArcGIS Pro Map property sheet. Use this within an Add-in to allow users to pick a spatial reference. Configure the control using a CoordinateSystemControlProperties object

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<Border BorderBrush="{DynamicResource Esri_BorderBrush}" BorderThickness="2" Margin="2">
  <mapping:CoordinateSystemsControl  x:Name="CoordinateSystemsControl"
                          ConfigureControl="{Binding Path=ControlProperties}"/>
</Border>

coordinatesystempicker

A sample using the Coordinate System Picker can be found at Coordinate Systems Dialog.

Coordinate System Details

The Coordinate System Details control displays the properties of a specified spatial reference. It can be used in conjunction with a Coordinate System Picker which provides a UI for choosing a spatial reference or used alone.

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<mapping:CoordinateSystemsDetailsControl 
         SpatialReference="{Binding ElementName=CoordinateSystemsControl, Path=SelectedSpatialReference}"
         ShowVerticalCSDetails="true"/>

coordinatesystemdetails

A sample using the Coordinate System Picker and the Coordinate System Detail control can be found at Coordinate Systems Dialog.

Transformations control

The Transformations control is a UI component similar to the core transformation picker displayed on the Transformations tab in the ArcGIS Pro Map property sheet. Use this control within an Add-in to allow users to choose a transformation between two spatial references for the purposes of projecting data.

Here is some sample xaml for a control. Configure the control using a TransformationsControlProperties object bound to the ConfigureControl dependency property.

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<mapping:TransformationsControl 
          ConfigureControl="{Binding Path=TControlProperties}" />

The TransformationsControlProperties object can be configured in many different ways. Here is a snippet showing it configured with source and target spatial references.

  // create the spatial references
  SpatialReference sr4267 = SpatialReferenceBuilder.CreateSpatialReference(4267);
  SpatialReference sr4483 = SpatialReferenceBuilder.CreateSpatialReference(4483);

  // set up the transformation info object
  var tInfo = new TransformationInfo();
  tInfo.SourceSpatialReference = sr4267;
  tInfo.TargetSpatialReference = sr4483;

  // add it to the list
  var tInfos = new List<TransformationInfo>();
  tInfos.Add(tInfo);

  var props = new TransformationsControlProperties()
  {
    CanEditCoordinateSystems = false,   // dont allow spatial references to be changed
    CanEditTransformationCollection = false,    // dont allow additional transformations to be added
    ShowColumnNames = true,   // show column names
    ShowNoTransformationsMessage = true,    // show a message if no transformations are listed
    NoTransformationsMessage = "No transformations exist",    // the message
    TransformationsInfo = tInfos,   // the transformations to be displayed
  };

transformationscontrol

A sample using the Transformations control showing other configuration options and it's use in projecting geometries can be found at TransformationsControl.

Symbol Searcher Control

The Symbol Searcher Control is used to search for StyleItems (Point/Line/Polygon symbols, colors, text symbols, etc.). The Symbol Searcher Control can be used as a stand alone control or in conjunction with the Symbol Picker Control.

Symbol Searcher Standalone Control

To add a 'Symbol Searcher Control' to an add-in DockPane and have it operate in a standalone manner, add the following snippets to your View (XAML).

xmlns:controls="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<StackPanel Orientation="Vertical">
  <controls:SymbolSearcherControl x:Name="SymbolSearch"
	       HorizontalAlignment="Stretch"
               VerticalAlignment="Top"/>
  <ListBox x:Name="SymbolPicker"
	       ItemsSource="{Binding Path=SearchResultStyleItems, ElementName=SymbolSearch, Mode=OneWay}"
               SelectedItem="{Binding SelectedPickerStyleItem}"
               DisplayMemberPath="Name"
	       HorizontalAlignment="Stretch"
               VerticalAlignment="Top" MaxHeight="450"/>
</StackPanel>

and add the corresponding properties to your ViewModel class:

using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Mapping.Controls;

private StyleItem _selectedPickerStyleItem;
public StyleItem SelectedPickerStyleItem
{
  get => _selectedPickerStyleItem;
  set
  {
    SetProperty(ref _selectedPickerStyleItem, value);
    MessageBox.Show($@"SelectedPickerStyleItem: {_selectedPickerStyleItem?.Name}");
  }
}

SymbolSearcherStandaloneControl

As you can see, the default search filter type is to search for Point symbols. To search for a different symbol type, add the SearchFilterType property in the xaml - either with a hardcoded value or binding to a property in the view model.

Here's the same example with the Symbol Searcher Control hard coded in the xaml to search for colors.

xmlns:controls="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<StackPanel Orientation="Vertical">
  <controls:SymbolSearcherControl x:Name="SymbolSearch"
               SearchFilterType="Color"
	       HorizontalAlignment="Stretch"
               VerticalAlignment="Top"/>
  <ListBox x:Name="SymbolPicker"
	       ItemsSource="{Binding Path=SearchResultStyleItems, ElementName=SymbolSearch, Mode=OneWay}"
               SelectedItem="{Binding SelectedPickerStyleItem}"
               DisplayMemberPath="Name"
	       HorizontalAlignment="Stretch"
               VerticalAlignment="Top" MaxHeight="450"/>
</StackPanel>

Additional properties on the Symbol Searcher control that can optionally be specified are

  • SearchFilterStyle - Defines the style of symbology to be searched for a style item of the given SearchFilterType. The default value is All Styles.
  • SearchOutputOptions - The default value is null (i.e. no search output options).
  • SearchPauseAutoSearchProperty - Allows automatic searching to be paused when the search filter changes. The default value is true.

Symbol Searcher/Picker Control

To use the Symbol Searcher Control with the SymbolPickerControl, add the following to a dockpane.

xmlns:controls="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<StackPanel Grid.Row="1" Orientation="Vertical">
  <controls:SymbolSearcherControl x:Name="SymbolSearch" 
    SearchFilterType="{Binding SearchFilterType}"
    SearchPauseAutoSearch="{Binding SearchPauseSearching}" />
  <controls:SymbolPickerControl x:Name="SymbolPicker" 
    PickerStyleItems="{Binding Path=SearchResultStyleItems, ElementName=SymbolSearch, Mode=OneWay}"
    SelectedPickerStyleItem="{Binding SelectedPickerStyleItem, Mode=TwoWay}"
    ViewingOption="{Binding PickerViewOption, Mode=TwoWay}"
    GroupingOption="{Binding PickerGroupOption, Mode=TwoWay}"
    HorizontalAlignment="Stretch"/>
</StackPanel>

and add the corresponding properties to your ViewModel code behind class:

using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Mapping.Controls;

protected CTOR_for_ViewModel()
{
  SearchPauseSearching = false;
  SearchFilterType = StyleItemType.PointSymbol;
}

private StyleItemType _searchFilterType;
public StyleItemType SearchFilterType
{
  get =>  _searchFilterType; 
  set => SetProperty(ref _searchFilterType, value);
}

private bool _searchPauseSearching = true; 
public bool SearchPauseSearching
{
  private get => _searchPauseSearching;
  set
  {
    SetProperty(ref _searchPauseSearching, value);
    System.Diagnostics.Debug.WriteLine($@"==== SearchPauseSearching setter: {_searchPauseSearching}");
  }
}

private StyleItem _selectedPickerStyleItem;
public StyleItem SelectedPickerStyleItem
{
  get => _selectedPickerStyleItem;
  set
  {
    SetProperty(ref _selectedPickerStyleItem, value);
    MessageBox.Show($@"SelectedPickerStyleItem: {_selectedPickerStyleItem?.Name}");
  }
}

private SymbolPickerViewOption _viewingOption = SymbolPickerViewOption.Icons;
public SymbolPickerViewOption PickerViewOption
{
  get => _viewingOption;
  set => SetProperty(ref _viewingOption, value);
}

private SymbolPickerGroupOption _groupingOption = SymbolPickerGroupOption.None;
public SymbolPickerGroupOption PickerGroupOption
{
  get => _groupingOption;
  set => SetProperty(ref _groupingOption, value);
}

SymbolSearcherPickerControl

Samples using the Symbol Searcher and Symbol Picker control can be found at Using Symbol Searcher Control.

Symbol Picker Control

The Symbol Picker Control is used to 'pick' a specific StyleItem from a given list of StyleItems. The Symbol Picker Control can be used in conjunction with the Symbol Searcher Control to prime the list of StyleItems to pick from and it can be used alone.

SymbolPickerControl

See the SymbolPickerControl used in conjunction with the SymbolSearcherControl here: Symbol Searcher/Picker Control.

Symbol Picker Standalone Control

xmlns:controls="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<controls:SymbolPickerControl x:Name="SymbolPicker"
		PickerStyleItems="{Binding Path=PickerStyleItems, Mode=OneWay}"
    SelectedPickerStyleItem="{Binding SelectedPickerStyleItem}"
		ViewingOption="{Binding ViewingOption}"
		GroupingOption="{Binding GroupingOption}"
		ShowOptionsDropDown="{Binding ShowOptionsDropDown, Mode=OneWay}"
    />

and the corresponding properties that are added to your ViewModel code behind class:

protected override Task InitializeAsync()
{
  return QueuedTask.Run(() =>
  {
    //Search for symbols in the selected style
    StyleProjectItem si = Project.Current.GetItems<StyleProjectItem>().FirstOrDefault();
    if (si != null)
    {  
      var lstStyleItems = si.SearchSymbols(StyleItemType.PointSymbol, string.Empty).Select((s) => s as StyleItem);
      RunOnUIThread(() =>
      {
        _pickerStyleItems = new ObservableCollection<StyleItem>();
        _pickerStyleItems.AddRange(lstStyleItems);
        NotifyPropertyChanged(() => PickerStyleItems);
      });
    } 
  });
}

private ObservableCollection<StyleItem> _pickerStyleItems;
public ObservableCollection<StyleItem> PickerStyleItems
{
  get => _pickerStyleItems; 
  set => SetProperty(ref _pickerStyleItems, value);
}

private StyleItem _selectedPickerStyleItem;
public StyleItem SelectedPickerStyleItem
{
  get => _selectedPickerStyleItem; 
  set
  {
    SetProperty(ref _selectedPickerStyleItem, value);
    MessageBox.Show($@"SelectedPickerStyleItem: {_selectedPickerStyleItem?.Name}");
  }
}

private SymbolPickerViewOption _viewingOption;
public SymbolPickerViewOption ViewingOption
{
  get => _viewingOption; 
  set => SetProperty(ref _viewingOption, value);
}

private SymbolPickerGroupOption _groupingOption;
public SymbolPickerGroupOption GroupingOption
{
  get => _groupingOption; 
  set => SetProperty(ref _groupingOption, value);
}

private bool _showOptionsDropDown;
public bool ShowOptionsDropDown
{ 
  get =>  _showOptionsDropDown; 
  set => SetProperty(ref _showOptionsDropDown, value);
}

#region Helpers

/// <summary>
/// Utility function to enable an action to run on the UI thread (if not already)
/// </summary>
/// <param name="action">the action to execute</param>
/// <returns></returns>
internal static Task RunOnUIThread(Action action)
{
  if (OnUIThread)
  {
    action();
    return Task.FromResult(0);
  }
  else
    return Task.Factory.StartNew(
       action, System.Threading.CancellationToken.None, TaskCreationOptions.None, QueuedTask.UIScheduler);
}

/// <summary>
/// Determines if the application is currently on the UI thread
/// </summary>
private static bool OnUIThread
{
  get
  {
    if (FrameworkApplication.TestMode)
      return QueuedTask.OnWorker;
    else
      return System.Windows.Application.Current.Dispatcher.CheckAccess();
  }
}

#endregion Helpers

SymbolPickerStandaloneControl

Samples using the Symbol Searcher and Symbol Picker control can be found at Using Symbol Searcher Control.

Color Picker Control

The Color Picker Control is a UI component similar to the core Color Picker Control on the ArcGIS Pro Symbology dockpane under the properties tab. Use this control within an Add-in to allow users to pick a color using the ArcGIS Pro color picker control. Configure the control using a CIMColor object as the SelectedColor property:

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<mapping:ColorPickerControl x:Name="ColorPicker" SelectedColor="{Binding SelectedColor, Mode=TwoWay}" />

and the corresponding SelectedColor property in your ViewModel code behind class:

/// <summary>
/// Color selection
/// </summary>
private CIMColor _SelectedColor = CIMColor.CreateRGBColor(255, 0, 0);
public CIMColor SelectedColor
{
  get => _SelectedColor;
  set => SetProperty(ref _SelectedColor, value);
}

colorpickercontrol

A sample using the Color Picker Control can be found at Color Picker Control.

Query Builder Control

The Query Builder control provides a UI for building a query expression against a layer or table. Configure the query builder with the layer or table and optional expression using a QueryBuilderControlProperties object.

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<mapping:QueryBuilderControl x:Name="QueryBuilderControl"  
                               ConfigureControl="{Binding Path=ControlProperties}"/>

querybuilder

A sample using the Query Builder control can be found at QueryBuilderControl.

Geocode Control

The Geocode Locator control provides a UI for geocoding. It looks and acts the same as the Locate dockpane. It allows you to add, remove, reorder, enable and disable locators. It also provides functionality to search and display geocode results.

xmlns:mapping="clr-namespace:ArcGIS.Desktop.Mapping.Controls;assembly=ArcGIS.Desktop.Mapping"

<mappingControls:LocatorControl x:Name="locator" />

locatorControl

A sample using the Geocode Locator control can be found at GeocodingTools.

Developing with ArcGIS Pro

    Migration


Framework

    Add-ins

    Configurations

    Customization

    Styling


Arcade


Content


CoreHost


DataReviewer


Editing


Geodatabase

    3D Analyst Data

    Plugin Datasources

    Topology

    Object Model Diagram


Geometry

    Relational Operations


Geoprocessing


Knowledge Graph


Layouts

    Reports


Map Authoring

    3D Analyst

    CIM

    Graphics

    Scene

    Stream

    Voxel


Map Exploration

    Map Tools


Networks

    Network Diagrams


Parcel Fabric


Raster


Sharing


Tasks


Workflow Manager Classic


Workflow Manager


Reference

Clone this wiki locally