Monday, November 22, 2010

How to sort a list using a delegate

Sorting is a fundamental part software development. This post will review a technique of sorting generic lists with inline delegates.

First, let’s define an elementary class that we will collect and ultimately sort.

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

The following class, Family, will create a collection of persons and exposes two sorting methods. One method will sort the collection based on age and the other on name. These methods use inline delegates to specify the sorting logic, or more specifically, the logic to compare two individual people. Both methods use inline delegates but use slightly different syntax. The last method will print the names and ages of people in the collection. To print this information you must have your solution set to debug mode and “Debug” selected in the output window.

public class Family {
    public List<Person> _people = new List<Person>();
    public Family() {
        this._people = new List<Person>();
        this._people.Add(new Person() { Name = "Bob", Age = 43 });
        this._people.Add(new Person() { Name = "Jim", Age = 10 });
        this._people.Add(new Person() { Name = "Larry", Age = 15 });
        this._people.Add(new Person() { Name = "John", Age = 30 });
        this._people.Add(new Person() { Name = "Mary", Age = 8 });
    }
    public void SortFamilyByAge() {
        this._people.Sort(
            delegate(Person a, Person b) {
                return a.Age.CompareTo(b.Age);
            }
        );
    }
    public void SortFamilyByName() {
        this._people.Sort(
            (Person a, Person b) => {
                return a.Name.CompareTo(b.Name);
            }
        );
    }
    public void PrintFamily() {
        this._people.ForEach(
            p => {
                Debug.WriteLine(string.Format("{0} ({1})", p.Name, p.Age));
            }
        );
        Debug.WriteLine(Environment.NewLine);
    }
}

And finally, the following code will instantiate the family object and run the two sort methods, sort by age and sort by name.

Family family = new Family();
family.SortFamilyByAge();
family.PrintFamily();
family.SortFamilyByName();
family.PrintFamily();

The code will produce the following output.

How to sort a list using a delegate

Friday, November 19, 2010

How to export a map to PDF (client-side)

 How to export a map to PDF (client-side)

This post will describe a technique for exporting a map created with the ArcGIS API for Silverlight to a PDF document completely client-side (no server-side computing).

You will need:

  1. Microsoft Visual Studio 2010 (link)
  2. Microsoft Silverlight 4 Tools for Visual Studio 2010 (link)
  3. ESRI ArcGIS API for Silverlight (link)
  4. silverPDF from codeplex (link)

How to build a map that can be saved to PDF:

Step 1 – In VS2010, create a new Silverlight Application called “SilverlightApplication1”, accept all the default options.

Step 2 – Add a reference to the ESRI’s ArcGIS API for Silverlight and silverPDF from codeplex.

Step 3 – Add the following to MainPage.xaml

<UserControl x:Class="SilverlightApplication1.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="http://schemas.esri.com/arcgis/client/2009"
   mc:Ignorable="d"
   d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <esri:Map x:Name="Map">
            <esri:ArcGISTiledMapServiceLayer
Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" />
        </esri:Map>
        <Button x:Name=
"ButtonPdf"
HorizontalAlignment=
"Left"
VerticalAlignment=
"Bottom"
Margin=
"10,0,0,10"
IsEnabled="False"
Content="Create PDF"/
>
    </Grid>
</UserControl>

Step 4 – Add the following to MainPage.xaml.cs (code behind)

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Resources;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using PdfSharp.Drawing;
using PdfSharp.Pdf;

namespace SilverlightApplication1 {
    public partial class MainPage : UserControl {
        public MainPage() {
            InitializeComponent();

            // Listen to "Create PDF" button click
            this.ButtonPdf.Click += new RoutedEventHandler(this.Button_Click);

            // Enable "Create PDF" button only after the Imagery layer has loaded
            this.Map.Layers[0].Initialized += (s, e) => {
                this.ButtonPdf.IsEnabled = true;

                // Also, zoom to an adjusted full extent that fills the map canvas
                this.Map.ZoomTo(this.Map.Layers[0].FullExtent(this.Map));
            };
        }
        private void Button_Click(object sender, RoutedEventArgs e) {
            // Create a Save As dialog
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "PDF|*.pdf";

            // Show dialog. Exit if use closes dialog.
            if (!dialog.ShowDialog().Value) { return; }

            // Get the current tiled layer
            ArcGISTiledMapServiceLayer tiled = this.Map.Layers[0]
as ArcGISTiledMapServiceLayer;

            // Create a new dynamic layer from the same map service
            ArcGISDynamicMapServiceLayer dynamic = new ArcGISDynamicMapServiceLayer() {
                Url = tiled.Url,
                ImageFormat = ArcGISDynamicMapServiceLayer.RestImageFormat.JPG
            };

            // When the dynamic layer has initialized create the in-memory PDF document
            dynamic.Initialized += (a, b) => {
                dynamic.GetUrl(
                    this.Map.Extent,
                    (int)this.Map.ActualWidth,
                    (int)this.Map.ActualHeight,
                    delegate(string url, int width, int height, Envelope extent) {

                        // Download a new image of identical to what is currently
// displayed in the map

                        WebClient webClient = new WebClient();
                        webClient.OpenReadCompleted += (c, f) => {

                            // Use the dispatcher to force execution in the UI thread
                            Dispatcher.BeginInvoke(delegate() {
                                // Create the PDF document.
// Set document information properties.

                                PdfDocument document = new PdfDocument();
                                document.Info.Title = "World Imagery";

                                // Create a new page with the same dimensions as
// the browser map

                                PdfPage page = new PdfPage(document) {
                                    Height = new XUnit(this.Map.ActualHeight,
XGraphicsUnit.Presentation),
                                    Width = new XUnit(this.Map.ActualWidth,
XGraphicsUnit.Presentation)
                                };
                                document.Pages.Add(page);

                                // Create a graphics object for writing to the page
                                XGraphics graphics = XGraphics.FromPdfPage(page);

                                // Add the map image to the page
                                XImage map = XImage.FromStream(f.Result);
                                graphics.DrawImage(map, 0d, 0d);

                                // Save the PDF document to the user specified filename
                                document.Save(dialog.OpenFile());

                                // Inform the user that the PDF creation is complete
                                MessageBox.Show("PDF Creation Complete");
                            });
                        };
                        webClient.OpenReadAsync(new Uri(url));
                    }
                );
            };
            dynamic.Initialize();
        }
    }
    /// <summary>
    /// Extends the Esri Layer class with an alternative FullExtent property.
    /// Returns an adjusted extent conceptually similar to Image.Stretch==UniformToFill
    /// </summary>
    public static class LayerExtension {
        public static Envelope FullExtent(this Layer layer, FrameworkElement parent) {
            Envelope extent = layer.FullExtent;
            double ratioMap = parent.ActualHeight / parent.ActualWidth;
            double ratioLay = extent.Height / extent.Width;
            if (ratioMap < ratioLay) {
                return new Envelope() {
                    XMin = extent.XMin,
                    YMin = extent.GetCenter().Y - 0.5d * ratioMap * extent.Width,
                    XMax = extent.XMax,
                    YMax = extent.GetCenter().Y + 0.5d * ratioMap * extent.Width
                };
            }
            return new Envelope() {
                XMin = extent.GetCenter().X - 0.5d * extent.Height / ratioMap,
                YMin = extent.YMin,
                XMax = extent.GetCenter().X + 0.5d * extent.Height / ratioMap,
                YMax = extent.YMax
            };
        }
    }
}

The web application is now ready to run in debug.  When the application loads, use your mouse (and mousewheel) to navigate to your favorite location on the planet.  Click the “Create PDF” button to commence the PDF export.  You will first be prompted for the output location of the PDF document and then again when the export is complete.

Thursday, October 28, 2010

How to support drag’n’drop with Silverlight on Mac

Silverlight 4 introduced support for drag and drop. That is, developers have access to files like images or videos that are dropped into Silverlight-based web application.

Unfortunately, as discussed in this thread, this capability does not work very well on a Mac. The issue is described briefly in this MSDN article but this post will provide a easily to implement (and painless) workaround.

These changes only affect the HTML page hosting the Silverlight control.  You do not need to make any change to your Silverlight project.

To start, add the text highlighted below.

<body>
    <form id="form1" runat="server" style="height:100%">
   
<div id="silverlightControlHost">
       
<object id="plugin" data="data:application/x-silverlight-2," ...
            <param name="source" value="..."/>
           
<param name="onError" value="onSilverlightError" />
           
<param name="onLoad" value="onSilverlightLoad" />
           
<param name="background" value="white" />
           
<param name="minRuntimeVersion" value="4.0.50826.0" />
           
<param name="autoUpgrade" value="true" />
           
<a href="http://go.microsoft.com/fwlink/...
               
<img src="http://go.microsoft.com/fwlink/...
           
</a>
       
</object><iframe id="_sl_historyFrame" style=...
   
</form>
</body>
</html>

Next, immediately below the boilerplate “onSilverlightError” JavaScript function, append the following six functions.

function onSilverlightLoad(sender, args) {
    if (window.navigator.userAgent.indexOf('Safari') >= 0) {
        var objControl = document.getElementById('plugin');
        objControl.addEventListener('dragenter',
            onSilverlight_HandleDragEnter, false);
        objControl.addEventListener('drop',
            onSilverlight_handleDropEvent, false);
        objControl.addEventListener('dragover',
            onSilverlight_HandleDragOver, false);
        objControl.addEventListener('dragleave',
            onSilverlight_HandleDragLeave, false);
    }
}
function onSilverlight_HandleDragEnter(oEvent) {
    // Prevent default operations in DOM
    oEvent.preventDefault();
    var flag = oEvent.target.dragEnter(oEvent);
    // If handled, then stop propagation of event in DOM
    if (flag) { oEvent.stopPropagation(); }
}
function onSilverlight_HandleDragOver(oEvent) {
    // Prevent default operations in DOM
    oEvent.preventDefault();
    var flag = oEvent.target.dragOver(oEvent);
    // If handled, then stop propagation of event in DOM
    if (flag) { oEvent.stopPropagation(); }
}
function onSilverlight_HandleDragLeave(oEvent) {
    // Prevent default operations in DOM
    oEvent.preventDefault();
    var flag = oEvent.target.dragLeave(oEvent);
    // If handled, then stop propagation of event in DOM
    if (flag) { oEvent.stopPropagation(); }
}
function onSilverlight_handleDropEvent(oEvent) {
    // Prevent default operations in DOM
    oEvent.preventDefault();

    var newEvent = Silverlight_clone(oEvent);
    newEvent.clientX = 80;
    newEvent.clientY = 80;
    newEvent.x = 80;
    newEvent.y = 80;
    newEvent.offsetX = 80;
    newEvent.offsetY = 80;

    var flag = oEvent.target.dragDrop(newEvent);
    // If handled, then stop propagation of event in DOM
    if (flag) { oEvent.stopPropagation(); }
}
function Silverlight_clone(o) {
    var c = {};
    var p, v;
    for (p in o) {
        if (o.hasOwnProperty(p)) {
            v = o[p];
            c[p] = v;
        }
    }
    return c;
}
 

You are now good to go!

This code is based primarily on the solution provided by Olego in this forum.

Wednesday, July 21, 2010

Z Swipe Tool for ArcGIS Desktop 10

Z Swipe Tool for ArcGIS Desktop 10

Z Swipe Tool for ArcGIS Desktop 10

ESRI’s Applications Prototype Lab has just published a new addin to the code gallery called the Z Swipe Tool.  The addin allows users to filter features in ArcScene based on vertical position using a slider.  Under-the-hood, the addin works by dynamically amending the definition queries of participating layers.  The Z Swipe Window (pictured above) contains a tree view for users to select (or de-select) participating layers.

The addin is ideal for working with documents with vertically concurrent features such as floors and services of a high-rise building.

The addin is available here and includes full source code.

Tuesday, July 20, 2010

ArcGIS Diagrammer for ArcGIS 10 Released

ArcGIS Diagrammer

ArcGIS Diagrammer is now available for the newly released ArcGIS 10.  The application can be downloaded here from the new code gallery on the ArcGIS resource center.

If you still have ArcGIS Desktop 9.2 or 9.3 then you can still use the previous version available here on ArcScripts.

The most significant change or addition to Diagrammer in the 10 release is the addition of Terrain dataset support.  Unfortunately we could not add support for mosaic datasets because they currently do not support xml serialization, that is, terrains are currently omitted from xml workspace documents exported from geodatabases.  Diagrammer also needed to be updated to reflect changes to how ArcGIS 10 manages metadata.  Overall it is basically the same Diagrammer you love or hate.  :-)

If you think that concepts presented in ArcGIS Diagrammer (and other samples like XRay) should be formally ported into the core ArcGIS product then please voice your opinion on ESRI’s ideas forum.