Extendendo a ESRI ArcGIS API For Microsoft Silverlight/WPF – Parte 2

Na segunda parte desse post irei demonstrar como utilizar o método de extensão.

How can I use it?

Para utilizar o método de extensão vamos criar uma aplicação Silverlight simples que irá mostrar a informação de um layer oriundo de um serviço da ESRI.

Criamos então uma nova aplicação Silverlight 4 no Visual Studio 2010.

Adicione uma referência ao projeto que contém o método de extensão e também ao assembly ESRI.ArcGIS.Client.

Coloque o seguinte código no arquivo MainPage.xaml.

<UserControl x:Class="ArcGISServerAPIExtensionSample.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"
             mc:Ignorable="d">

    <Grid x:Name="LayoutRoot">

        <esri:Map x:Name="MainMap">
            <esri:ArcGISDynamicMapServiceLayer ID="DynamicLayer"
                                               Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer"
                                               Initialized="ArcGISDynamicMapServiceLayer_Initialized" />
        </esri:Map>

        <Grid Background="Black"
              Margin="10"
              Width="450"
              VerticalAlignment="Top"
              HorizontalAlignment="Left">

            <Grid.RowDefinitions>
                <RowDefinition Height="30" />
                <RowDefinition Height="30" />
                <RowDefinition Height="250" />
            </Grid.RowDefinitions>

            <ComboBox x:Name="cmbLayers"
                      HorizontalAlignment="Left"
                      Width="180"
                      Margin="2"
                      Grid.Row="0" />

            <Button x:Name="btnTeste"
                    Width="180"
                    Margin="2"
                    HorizontalAlignment="Left"
                    Content="Testar Método de Extensão"
                    Grid.Row="1"
                    Click="btnTeste_Click" />

            <TextBox x:Name="txtResultado"
                     Margin="2"
                     Grid.Row="2"
                     TextWrapping="Wrap" />

        </Grid>

    </Grid>
    
</UserControl>

O código XAML é bem simples e o objetivo é o usuário selecionar um layer no ComboBox e então clicar no Button, que realiza a chamada ao método de extensão.

No code-behind da página realize a inclusão dos namespaces abaixo:

using System.Text;
using ArcGISServerAPIExtension;
using ESRI.ArcGIS.Client;

Ainda no code-behind da página realize o data binding dos layers existentes no serviço DynamicLayer do mapa após o mesmo ser inicializado (evento Initialized).

private void ArcGISDynamicMapServiceLayer_Initialized(object sender, EventArgs e)
{
    // recupera layer
    var dynamicLayer = sender as ArcGISDynamicMapServiceLayer;

    // data binding
    this.cmbLayers.SelectedValuePath = "ID";
    this.cmbLayers.DisplayMemberPath = "Name";
    this.cmbLayers.ItemsSource = dynamicLayer.Layers;
}

Também faça a implementação do evento Click do Button para mostrar as informações do layer selecionado.

private void btnTeste_Click(object sender, RoutedEventArgs e)
{
    // verifica se algum layer foi selecionado
    if (this.cmbLayers.SelectedValue == null)
        return;

    // referência ao layer dinâmico que está no mapa
    var dynamicLayer = this.MainMap.Layers["DynamicLayer"] as ArcGISDynamicMapServiceLayer;

    dynamicLayer.GetLayerDetails((int)this.cmbLayers.SelectedValue, 
        (r) =>
        {
            // verifica erro
            if (r.Error != null)
            {
                this.txtResultado.Text = r.Error.Message;
                return;
            }

            // recupera resultado do método
            var layerDetails = r.Result;

            // cria string que contém as informações do layer
            var stringBuilder = new StringBuilder();

            // adiciona informações
            stringBuilder.AppendFormat("ID: {0}\n", layerDetails.ID);
            stringBuilder.AppendFormat("Name: {0}\n", layerDetails.Name);
            stringBuilder.AppendFormat("Description: {0}\n", layerDetails.Description);
            stringBuilder.AppendFormat("GeometryType: {0}\n", layerDetails.GeometryType);
            stringBuilder.AppendFormat("MinScale: {0}\n", layerDetails.MinScale);
            stringBuilder.AppendFormat("MaxScale: {0}\n", layerDetails.MaxScale);

            // adiciona campos (fields)
            stringBuilder.AppendLine("-----------------------");
            stringBuilder.AppendLine("Fields");
            stringBuilder.AppendLine("-----------------------");

            foreach (var field in layerDetails.Fields)
                stringBuilder.AppendLine(field.Name);

            this.txtResultado.Text = stringBuilder.ToString();
        }
    );
}

Esse método realiza o tratamento do método callback por meio de uma expressão lambda ( (r) =>) e mostra algumas das informações recuperadas do serviço.

Done !

O código-fonte do exemplo e do método de extensão você encontra aqui.

Espero que tenham gostado.

Extendendo a ESRI ArcGIS API For Microsoft Silverlight/WPF – Parte 1

Venho trabalhando com a ESRI ArcGIS For Microsoft Silverlight/WPF já há algum tempo (+- 5 meses) e percebi que diversas funcionalidades que existem em suas APIs irmãs (ArcGIS Server API For Flex e ArcGIS Server API For JavaScript) não estão disponíveis. Esse post irá demonstrar uma maneira de estender a API da ESRI para Silverlight/WPF com o objetivo de adquirir maiores informações sobre os serviços de mapas dinâmicos, ou seja, serviços de mapa que não possuem cache.

Observação: Esse post leva em consideração que o leitor possua conhecimento de desenvolvimento de aplicações de mapeamento na plataforma ESRI e que já possua um conhecimento mínimo da ArcGIS API For Microsoft Silverlight/WPF. No final do post vou indicar alguns links com maiores informações.

Introdução

O objetivo da ArcGIS API For Microsoft Silverlight/WPF é integrar serviços de mapa publicados no ArcGIS Server, dados espaciais armazenados no SQL Server 2008 (ESRI MapIt) ou até mesmo serviços do Bing Maps com aplicações desenvolvidas em Silverlight ou WPF.

O ArcGIS Server é um software servidor desenvolvido pela ESRI que permite a criação e a distribuição de dados espaciais por meio de serviços web. Uma característica interessante do ArcGIS Server é que este possui uma API que disponibiliza os serviços de mapas por meio de uma interface REST, ou seja, podemos utilizar os recursos e operações de cada serviço por meio de URLs.

A idéia então é criar um método de extensão na classe ArcGISDynamicMapServiceLayer que recupera essas informações usando a ArcGIS Server REST API e deserialização de dados no formato JSON.

Instalação e Requisitos Mínimos

Primeiramente temos que fazer o download da versão 2.0 Beta da ArcGIS Server API For Microsoft Silverlight/WPF, que somente funciona com o Silverlight 4 e consequentemente com o Visual Studio 2010 e Expression Blend 4. Para utilizar essa API você deve possuir um login na ESRI e então fazer o download do instalador neste link.

Por questões práticas irei pular as instruções de instalação e os requisitos mínimos para utilizar a API. Para mais informações veja esse vídeo.

Hands-On

A classe ArcGISDynamicMapServiceLayer possui uma propriedade que é um array de layers refresentados pela classe LayerInfo, que representa informações relativas a um layer. Infelizmente existem poucas informações disponíveis (ID, Name, DefaultVisibility e SubLayerIds). A ArcGIS REST API disponibiliza além dessas informações diversas outras, tais como: escala de visualização (minScale, maxScale), atributos, tipo de geometria e muitos outros.

Observação: As APIs do ArcGIS Server para Flex e JavaScript possuem essas informações, mas a API do Silverlight/WPF não. É bem possível que o que irei demonstrar aqui possa surgir em versões futuras da API.

Um exemplo desse recurso em formato JSON pode ser encontrado no exemplo da ESRI do link abaixo.
http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/1?f=json&pretty=true

Vejam uma parte desse recurso abaixo.

{
  "id" : 1, 
  "name" : "states", 
  "type" : "Feature Layer", 
  "geometryType" : "esriGeometryPolygon", 
  "description" : "This service provides ... ", 
  "definitionExpression" : "", 
  "copyrightText" : "(c) ESRI and its data partners", 
  "minScale" : 0, 
  "maxScale" : 0, 
  "extent" : {
    "xmin" : -178.217598362366, 
    "ymin" : 18.9247817993165, 
    "xmax" : -66.9692710360024, 
    "ymax" : 71.4062353532711, 
    "spatialReference" : {
      "wkid" : 4326
    }
  }, 
  "displayField" : "STATE_NAME", 
  "fields" : [
    {"name" : "OBJECTID", "type" : "esriFieldTypeOID", "alias" : "OBJECTID"}, 
    ...
    {"name" : "Shape_Area", "type" : "esriFieldTypeDouble", "alias" : "Shape_Area"}
  ], 
  "parentLayer" : null, 
  "subLayers" : []
}

Silverlight Class Library

Vamos agora criar o nosso projeto no Visual Studio 2010. No Startup Page clique em New Project, escolha o template Silverlight Class Library e dê o nome ArcGISServerAPIExtension.

Escolha a versão 4 do Silverlight como target da class library.

Helper Classes

Vamos agora criar algumas classes que irão representar as informações que iremos recuperar do serviço dinâmico de mapa.

Para isso primeiramente devemos fazer referência ao assembly System.Runtime.Serialization e System.ServiceModel.Web. O primeiro possibilita a criação ou implementação de contratos usando os atributos DataContractAttribute e DataMemberAttribute, já o segundo possui a classe DataContractJsonSerializer que realiza serialização em objetos no formato JSON.

Observação: A classe DataContractJsonSerializer encontra-se no assembly System.ServiceModel.Web mas no namespace System.Runtime.Serialization.Json. Não me perguntem o porquê.

Vamos fazer a referência também do assembly ESRI.ArcGIS.Client que contém a classe ArcGISDynamicMapServiceLayer.

A classe FieldDetails irá representar algumas informações referentes a um dos atributos de um layer.

[DataContract]
public class FieldDetails
{
    [DataMember(Name="name")]
    public string Name { get; set; }

    [DataMember(Name = "type")]
    public string Type { get; set; }

    [DataMember(Name = "alias")]
    public string Alias { get; set; }
}

O parâmetro Name do DataMember mapeia a propriedade à informação correspondente disponibilizada no serviço de mapa. Perceba que os nomes mapeados encontram-se no array fields do exemplo do recurso REST mostrado anteriormente.

A classe LayerDetails irá representar algumas informações de um layer e seu conjunto de atributos em forma de um array

[DataContract]
public class LayerDetails
{
    [DataMember(Name = "id")]
    public int ID { get; set; }

    [DataMember(Name = "name")]
    public string Name { get; set; }

    [DataMember(Name = "description")]
    public string Description { get; set; }

    [DataMember(Name = "geometryType")]
    public string GeometryType { get; set; }

    [DataMember(Name = "minScale")]
    public double MinScale { get; set; }

    [DataMember(Name = "maxScale")]
    public double MaxScale { get; set; }

    [DataMember(Name = "fields")]
    public FieldDetails[] Fields { get; set; }
}

Observação: Por praticidade não irei mapear todas as informações do layer. Se necessário, realize o mapeamento na classe acima.

Uma outra classe chamada GetLayerDetailsResult também será criada com o objetivo de retornar as informações para um método callback, que será explicado posteriormente.

public class GetLayerDetailsResult
{
    public LayerDetails Result { get; set; }
    public Exception Error { get; set; }
    public object UserState { get; set; }
}

Extension Method

Vamos então criar uma nova classe em nosso projeto com um método chamado GetLayerDetails que será o método de extensão da classe ArcGISDynamicMapServiceLayer.

public static void GetLayerDetails(this ArcGISDynamicMapServiceLayer dynamicLayer, 
    int layerId, object userState, 
    Action<GetLayerDetailsResult> resultCallback)
{
    var webClient = new WebClient();
    webClient.DownloadStringCompleted += (s, a) =>;
    {
        // verifica erro
        if (a.Error != null)
        {
            // executa método callback (retorna null, se erro)
            resultCallback(
                new GetLayerDetailsResult {
                    Error = a.Error, 
                    Result = null, 
                    UserState = userState }
            );
            return;
        }

        // transforma string em uma stream na memória
        var bytes = Encoding.Unicode.GetBytes(a.Result);
        var memStream = new MemoryStream(bytes);

        // deserializa a stream com base no contrato definido na classe LayerDetails
        var dataContractJson = new DataContractJsonSerializer(typeof(LayerDetails));
        var layerDetails = (LayerDetails)dataContractJson.ReadObject(memStream);

        // executa método callback
        resultCallback(
            new GetLayerDetailsResult { 
                Error = null, 
                Result = layerDetails, 
                UserState = userState }
        );
    };

    // realiza download da string conforme URL padrão
    // [url do serviço]/[id do layer]?f=json }
    webClient.DownloadStringAsync(new Uri(dynamicLayer.Url + @"/" + layerId + "?f=json"));
}

Os comentários são auto-explicativos, mas vou explicar algumas partes do código.

O método possui os seguintes parâmetros:
  • layerId – o ID do layer cujas informações serão recuperadas.
  • userState – um objeto que representa o estado do usuário.
  • resultCallback – método que será chamado quando o método GetLayerDetails terminar o processamento.
Utilizamos a classe WebClient para realizar uma requisição na URL do serviço de forma assíncrona e recuperar uma string com o conteúdo da requisição, ou seja, um conteúdo tal como o que foi apresentado no exemplo da ESRI de um recurso REST.

Com a string, transformamos a mesma em um objeto MemoryStream e utilizamos a classe DataContractJsonSerializer para deserializar a string com conteúdo JSON em um objeto da classe LayerDetails.

O método resultCallback é executado e entrega para o cliente da classe informações sobre o processamento por meio da classe GetLayerDetailsResult. Com essa classe podemos verificar se ocorreu um erro (propriedade Error) ou recuperar as informações do layer (propriedade LayerDetails), que é o resultado do processamento do método.

A implementação desse método surgiu com a necessidade em um projeto de recuperar as escalas máxima e mínima de visualização de um layer para habilitar ou desabilitar o mesmo em uma legenda de acordo com o zoom atual do mapa. O único plágio foi copiar o nome do método da API do Flex, que você encontra aqui.

Em uma segunda parte desse post, explicarei como utilizar essa classe.

Links diversos:

ArcGIS Server
http://bit.ly/pLj3T

ArcGIS Server REST API
http://bit.ly/8KLgfe

ESRI Products
http://bit.ly/cyduyV

ArcGIS API for Microsoft Silverlight/WPF
http://bit.ly/cgUZ5o

DataContractAttribute Class
http://bit.ly/9286VZ

DataContractJsonSerializer Class
http://bit.ly/bobuuI

WebClient Class
http://bit.ly/aDEK9N