Using web maps in ArcGIS Runtime: Accessing groups and items

Using web maps in ArcGIS Runtime: Accessing groups and items

Example on visualising a selection of groups

In this post, I will look into the basics of using groups and items in ArcGIS Online. In the ArcGIS information model maps, scenes, layers and basically everything else are items. These can be shared with a group or groups and accessed by the people that are part of the group. Both ArcGIS Online and ArcGIS Enterprise have the same model can the concepts apply to both.

This post is part of “using web maps”-series.

NOTE:
Example application and code are available at https://github.com/anttikajanus/working-with-maps-arcgis-runtime-dotnet. It’s working in progress so things might have changed from the post.

Groups

A group is a collection of items that are related to the specific topic such as “Our company basemaps” or “Trails“. You can use groups to organise your content and sharing them with a specific group of people. For example, I created multiple groups to represent different ways of utilising the ArcGIS platform in the ArcGIS Runtime and shared only the items (resources) related to that specific case to the group.

Groups have some useful information that can be used in clients such as:

  • id (guid)
  • title (string)
  • description (HTML)
  • snippet (string)
  • tags (collection of strings)
  • thumbnail (400×400 px)
  • owner (string)
  • created (date)
  • modified (date)
  • etc…

If you are new to using groups in ArcGIS Portal, see get started with groups documentation. Here is a link to the REST specification.

Using groups in the client

When a user authenticates to the ArcGIS Portal, users information gets returned from the portal which contains a list of groups that she is part of.

IEnumerable<PortalGroup> groups = portal.User.Groups;

You can read more about how to authenticate to the portal here.

You can also filter the groups down based on the things that you are actually interested in. For example, in the following example, I have a list of group Ids stored in the configuration that the application loads to the UI.

// GroupSelectionViewModel.cs
public override void OnNavigatedTo(NavigationContext navigationContext)
{
    var portal = navigationContext.Parameters["portal"] as ArcGISPortal;
    var groupIds= ApplicationServices.ConfigurationService.GetSetting("GroupIds").Split(',');
    var groups = portal.User.Groups.Where(x => { return groupIds.Contains(x.GroupId); });
    Groups = new ObservableCollection<PortalGroup>(groups);

    base.OnNavigatedTo(navigationContext);
}

You can also search groups through ArcGISPortal.FindGroupsAsync task or use ArcGISPortal.GetFeatureGroupsAsync task to get features ones. The featured groups are defined by the portal administrators. If you have problems to figure out how to construct the query, then check this one.

Group information in the client
<ItemsControl Margin="0,1,5,-1"
	HorizontalAlignment="Stretch"
    HorizontalContentAlignment="Stretch"
    ItemsSource="{Binding Groups}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <materialDesign:Card Padding="5" Margin="4,8"
                    Height="180" materialDesign:ShadowAssist.ShadowDepth="Depth3">
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="auto"/>
                    </Grid.RowDefinitions>
					<Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="auto"/>
                    </Grid.ColumnDefinitions>
                    <!-- Returned as 400x400 -->
                    <Image Source="{Binding ThumbnailUri}" Grid.RowSpan="5" 
                        Margin="5"></Image>
                    <!-- Returned as a string -->
                    <TextBlock Text="{Binding Title}" 
                        Style="{StaticResource MaterialDesignTitleTextBlock}" Grid.Column="1" 
                        Margin="5"/>
                    <!-- Returned as a HTML string -->
                    <tiny:WpfHtmlControl 
                        Html="{Binding GroupDescription}" Grid.Row="1" 
                        Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top"/>

                    <Grid Grid.Row="4" Grid.Column="1" Margin="0,0,0,5" Grid.ColumnSpan="2">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Margin="5,5,0,0"
                                Style="{StaticResource MaterialDesignBody2TextBlock}"
                                Text="Created:"/>
                            <TextBlock  Margin="5,5,0,0"
                                Style="{StaticResource MaterialDesignBody1TextBlock}"
                                Text="{Binding Created, Mode=OneWay, StringFormat={}{0:f}}"/>
                            <TextBlock Margin="15,5,0,0"
                                Style="{StaticResource MaterialDesignBody2TextBlock}"
                                Text="Updated:"/>
                            <TextBlock  Margin="5,5,0,0"
                                Style="{StaticResource MaterialDesignBody1TextBlock}"
                                Text="{Binding Modified, Mode=OneWay, StringFormat={}{0:f}}"/>
                            <TextBlock Margin="15,5,0,0"
                                Style="{StaticResource MaterialDesignBody2TextBlock}"
                                Text="Owned:"/>
                            <TextBlock  Margin="5,5,0,0"
                                Style="{StaticResource MaterialDesignBody1TextBlock}"
                                Text="{Binding Owner, Mode=OneWay}"/>
                            <TextBlock Margin="15,5,0,0"
                                Style="{StaticResource MaterialDesignBody2TextBlock}"
                                Text="Access:"/>
                            <TextBlock  Margin="5,5,0,0"
                                Style="{StaticResource MaterialDesignBody1TextBlock}"
                                Text="{Binding Access, Mode=OneWay}"/>
                        </StackPanel>
                    </Grid>
                    <StackPanel Grid.Column="2" Grid.RowSpan="2">
                        <Button  Margin="5" Width="200" BorderBrush="Blue" 
                            BorderThickness="1" 
                            Style="{DynamicResource MaterialDesignFlatButton}"
                            Command="{Binding ElementName=root, Path=DataContext.NavigateCommand}"
                            CommandParameter="{Binding}">Open
						</Button>
                    </StackPanel>
                </Grid>
            </materialDesign:Card>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Items

Items are unit of content in the portal so, for example, a webmap is an item. I was covering this in my previous post as well. All the items in the portal share the same metadata model so you can build a generic item browser easily if required.

Items have a set of information that is shared between all content

  • id (guid)
  • title (string)
  • snippet (string)
  • description (HTML string)
  • type (portal item type)
  • tags (collection of string)
  • thumbnail (600×400 px)
  • accessInformation (HTML string)
  • owner (string)
  • created (date)
  • modified (date)
  • etc…

There are quite a few things that are obsolete now in 100.5, so be careful which properties you end up using. For example “GUID” is now “ItemId” and “AccessAndUseConstrainsHtml” is now “TermsOfUse“. If you look at the terminology that ArcGIS Pro, ArcGIS Online and other APIs use, you will see that there are slight differences so make sure that you clarify which field you should be using. 🙂 For example, in ArcGIS Online items have a field called “Credits (Attribution)” but that field is called “AccessInformation” in the ArcGIS Runtime.

Here is a link to the REST specification.

Using items in the client

Items aren’t returned as part of the user information so you have to search from them. In the example below, I have selected a group that I want to see and then loading 20 webmaps that are shared with the group. I also specify that only find maps that are stored in my organisation.

// WebMapsViewModel.cs
private async Task LoadItemsAsync(PortalGroup group)
{
    var results = await group.Portal.FindItemsAsync(new PortalQueryParameters($@"type:'web map' AND  group: {group.GroupId}")
        {
            CanSearchPublic = false, // Find only items from the used portal
            Limit = 20,
            SortField = "relevance"
        }); 
    foreach (var item in results.Results)
    {
        WebMaps.Add(item);
    }
}

Finding the items and groups behave the same so after you have figured out how it works in one you have both cases covered. There is also ArcGISPortal.GetFeaturedItemsAsync task that corresponds to the one with groups.

Item information in the client
<ItemsControl Margin="0,1,5,-1"
    HorizontalAlignment="Stretch"
    HorizontalContentAlignment="Stretch"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
    ItemsSource="{Binding WebMapModels}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <materialDesign:Card Padding="5" Margin="4,8" 
                        Height="180" materialDesign:ShadowAssist.ShadowDepth="Depth3">
                    <Grid >
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="auto"/>
                        </Grid.ColumnDefinitions>
                        <Image Source="{Binding Item.ThumbnailUri}"
                            Grid.RowSpan="5" Margin="5"></Image>
                        <TextBlock Text="{Binding Item.Title}" 
                            Style="{StaticResource MaterialDesignTitleTextBlock}" Grid.Column="1" 
                             Margin="5"/>
                        <tiny:WpfHtmlControl Html="{Binding Item.Description}" 
                            Grid.Row="1" Grid.Column="1" />
                        <Grid Grid.Row="4" Grid.Column="1" 
                             Margin="0,0,0,5" Grid.ColumnSpan="2">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Margin="5,5,0,0"
                                    Style="{StaticResource MaterialDesignBody2TextBlock}"
                                    Text="Created:"/>
                                <TextBlock  Margin="5,5,0,0"
                                  Style="{StaticResource MaterialDesignBody1TextBlock}"
                                  Text="{Binding Item.Created, Mode=OneWay, StringFormat={}{0:f}}"/>
                                <TextBlock Margin="15,5,0,0"
                                  Style="{StaticResource MaterialDesignBody2TextBlock}"
                                  Text="Updated:"/>
                                <TextBlock  Margin="5,5,0,0"
                                  Style="{StaticResource MaterialDesignBody1TextBlock}"
                                  Text="{Binding Item.Modified, Mode=OneWay, StringFormat={}{0:f}}"/>
                            </StackPanel>
                        </Grid>
                        <StackPanel Grid.Column="2" Grid.RowSpan="4">
                            <Button  Margin="5" Width="200" BorderBrush="Blue" 
                                BorderThickness="1" 
                                Style="{DynamicResource MaterialDesignFlatButton}"
                                Command="{Binding ElementName=root, Path=DataContext.NavigateToOnlineMapCommand}"
                                CommandParameter="{Binding}">Online</Button>
                            <Button  Margin="5" Width="200" BorderBrush="Blue" 
                                BorderThickness="1" 
                                Style="{DynamicResource MaterialDesignFlatButton}"
                                Command="{Binding ElementName=root, Path=DataContext.NavigateToOfflineSelectionCommand}"
                                CommandParameter="{Binding}">Offline</Button>
                            <Button Grid.Column="2" Margin="5" Width="200" BorderBrush="Blue" 
                                BorderThickness="1" 
                                Style="{DynamicResource MaterialDesignFlatButton}"
                                Command="{Binding ElementName=root, Path=DataContext.NavigateToDetailsCommand}"
                                CommandParameter="{Binding}">Details</Button>
                        </StackPanel>
                    </Grid>
                </materialDesign:Card>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
</ItemsControl>

Item comments

Items can have comments. I haven’t seen these being utilized in large scale but you can do some handy things with them. Since there is no way to provide any kind of changelog with your maps, you can use comments to provide an idea of how the map has changed in the passage of time.

Using comments to provide change log

Weirdly the PortalItemComment.Comment returns the text escaped which doesn’t happen with any other fields that I have used with other types. I’m not sure if this is a bug or a feature but you can fix it by running it through Uri.UnescapeDataString method.

// WebMapDetailsDialogViewModel.cs
public async override void OnDialogOpened(IDialogParameters parameters)
{
    var model = parameters.GetValue<WebMapModel>("model");
    Item = model.Item;

    var comments = await Item.GetCommentsAsync();
    foreach (var comment in comments)
    {
        Comments.Add(new CommentModel(comment));
    }
}

// CommentModel.cs
public class CommentModel : BindableBase
{
    private readonly PortalItemComment _comment;

    public CommentModel(PortalItemComment comment)
    {
        _comment = comment ?? throw new ArgumentNullException(nameof(comment));
        CommentText = Uri.UnescapeDataString(_comment.Comment);
    }

    public string CommentText { get; }

    public DateTimeOffset Created => _comment.Created;

    public string Owner => _comment.Owner;
}
<ListView Margin="0,0,0,10" Padding="10"
    Grid.Row="1"
    HorizontalAlignment="Stretch"
    HorizontalContentAlignment="Stretch"
    ItemsSource="{Binding Comments}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <materialDesign:Card Padding="5" Margin="5,1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}">
                        <Run Text="{Binding Owner, Mode=OneWay}"/>
                        <Run Text="-"/>
                        <Run Text="{Binding Created, Mode=OneWay, StringFormat={}{0:F}}"/>
                    </TextBlock>
                    <TextBlock Text="{Binding CommentText}" Grid.Row="1" MaxWidth="800" Margin="5"/>
                </Grid>
            </materialDesign:Card>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Summary

If you end up working with ArcGIS Online or ArcGIS Enterprise in any larger setup, you most likely will end up leveraging Groups and especially Items in your application. Using groups to manage content and item scopes give many interesting patterns that you can use to deliver content to the clients. There are still a lot of companies that have the platform in use but they don’t use it in full.

The usage isn’t that complicated in general and you will pick the API very fast. I think that the biggest annoyance is the different naming between ArcGIS Products and the difference in how the fields return stuff (text, escaped text, HTML). Thankfully this is minor since it doesn’t limit functionality in any way.

Leave a Reply