Continuously Display Lat Lon Coordinates in a Bing Maps Silverlight App

by James Richards April 12, 2010

Overview

This article presents code and a brief tutorial showing how to continuously display the real world latitude / longitude coordinates of the mouse location in a Bing Maps Silverlight Application.

You can view a live sample or download the source code.

Tutorial

Create a new Bing Maps Silverlight application called LatLonApp using the steps shown in my previous Getting Started with the Bing Maps Silverlight control post.

Open the solution in Blend 3, and open the MainPage.xaml user control.

Select the Map control in the Objects and Timeline Window

image

In the upper right hand corner, enter the name “MyMap” for the Name property in the Properties window and hit return.

image

Select the TextBlock tool in the toolbar, and click and drag on the artboard to add new text block to the project.

image

Position the text block in the lower right, just above the scale bar. Change the name the text block to “Coords”.

In the Properties window, set the Text property to “Lat, Lon” and the justification to Right.

image

image

Ensure that the Horizontal Alignment is set to Right, the Vertical Alignment is set to Bottom, the Left and Top Margins are set to 0 and the Right and Bottom margins are set to 5 and 57 respectively.

image

Notice how the text on the scale bar has a 1 pixel white drop shadow. Next, we’ll duplicate that effect for the Coords text block.

Click on the Assets tab in the upper left, and then select the Effects category. This will display any effects you have registered with Blend on the right hand side of the split window.

image

Drag the DropShadowEffect onto the Coords text block.

image

This will add the effect to the text block, select the effect in the Objects and Timeline window, and display the effect’s properties in the Properties window on the right.

Change the Blur Radius to 1, the Color to White, and the Shadow Depth to 1.

image

Now the text block is styled in the same way as the scale bar text.

image

Select the Map in the Objects and Timeline window or on the artboard and click the Events icon in the upper right hand corner of the Properties window.

image

Find the MouseMove event, enter MyMap_MouseMove and hit enter.

image

This will create a new event handler and open up the code behind file MainPage.xaml.cs.

image

At this point you can either code up the event in Blend, or switch back to Visual Studio. I prefer to switch back to Visual Studio for the Intellisense. Note that you could also have switch back earlier and created the event in Visual Studio as well.

Make sure that all of the files are saved before switching back by choosing Save All from the File menu or pressing Ctrl+Shift+S.

When you switch back to Visual Studio it will notice that the files have been modified and present a dialog asking if you want to reload the file(s). Click Yes to All.

image

Open the MainPage.xaml.cs and add a using statement for the Microsoft.Maps.MapControl namespace.

Add the following code to the MyMap_MouseMove event.

private void MyMap_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    Point viewportPoint = e.GetPosition(MyMap);
    Location location;
    if (MyMap.TryViewportPointToLocation(viewportPoint, out location))
    {
        Coords.Text = String.Format("Lat: {0:f4}, Lon: {1:f4}",
            location.Latitude, location.Longitude);
    }
}

This code gets the current mouse position in Viewport coordinates and transforms the Point to a latitude longitude Location. If the transformation is successful, the latitude and longitude are rounded to 4 decimal places and the Coords text block is update to display the coordinate.

Press F5 to compile and run the project. As you move the mouse around the map, the coordinates are displayed above the scale bar.

image

Wrapup

In this article you learned how to continuously display the real world latitude / longitude coordinates of the mouse location in a Bing Maps Silverlight Application.

You can view a live sample of the application or download the source code.

Additional Resources

Tags: , , ,

Bing Maps | Silverlight

Getting Started with the Bing Maps Silverlight Control, Visual Studio 2008 and Blend 3

by James Richards April 08, 2010

Overview

This walkthrough shows how to create a new Bing Maps Silverlight Control project with either Visual Studio 2008 SP1 or Blend 3. While some of this material has been covered on other sites and blogs, I’m writing about it here for two reasons:

  1. Some of the other posts are now outdated, using older, beta or CTP versions of the APIs and development tools.
  2. In future posts about developing with the Bing Maps Silverlight Control, I want to be able to refer to this “Getting Started” guide so I don’t have to keep repeating the basics.

Download and Install the Development Tools

If you haven’t already done so, download and install the following applications, SDKs and Toolkits:

Required

VS2008

Microsoft Visual Studio 2008 SP1 (Includes .NET Framework 3.5 SP1)

Silverlight Microsoft Silverlight 3 Tools for Visual Studio 2008 SP1

Blend

Expression Blend 3 or Expression Studio 3
Bing Bing Maps Silverlight Control SDK v 1.0.1

Optional

SilverlightToolkit Microsoft Silverlight 3 Toolkit November 2009

 

 

[more]

Setup a Big Maps Developer Account Using Your Windows Live ID

image

If you don’t already have a Windows Live ID, you’ll need to signup for one on the Windows Live site: https://signup.live.com/signup.aspx

One you have your Windows Live ID, go the the Bing Maps Developer Portal: https://www.bingmapsportal.com/

Click on the “Create or view Bing Maps keys” link in the left menu:

image

When prompted, login with your Windows Live ID. After logging in, you will be redirected to the proper page.

Enter the Application Name and the URL, and click the Create Key button.

image

Here you can see I’ve created two keys. One for development and testing through the http://localhost url and one for running applications on a live URL (in this case, my blog.) I’ve blacked out the actual keys.

Create a New Bing Maps Silverlight Application with Visual Studio

When creating a new Silverlight application, you can start with either Visual Studio or Blend. First we’ll look at how to create the application with Visual Studio. Then in the next section we’ll see how it’s done with Blend.

Start Visual Studio 2008 and choose File > New > Project…

Select Silverlight from the Project types tree, and Silverlight Application from the Templates list.

Give your application a name and click OK.

image

A dialog will appear asking if you want to host the application in a new web site. Make sure this option is checked, select ASP.NET Web Application Project, and click OK.

image

After the project is created, add references to the Bing Maps assemblies.

Right click on the References folder in the BingMapsApp project and choose Add Reference…

image

Select the Browse tab and browse to the Libraries directory in the Bing Maps Silverlight Control installation folder. The default installation location is C:\Program Files\Bing Maps Silverlight Control\V1\Libraries.

Select both the Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll assemblies, and click OK.

image

image

Modify the xaml for the MainPage.xaml user control by adding an xml namespace declaration for the Bing Maps assembly.

image

xmlns:bing="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

Note: The samples on Microsoft’s Bing Maps Silverlight Control Interactive SDK all use the “m” prefix for the namespace, but I prefer to use “bing” as it makes the code a little more expressive. Hat tip to Bobby Diaz’s Bing Maps Silverlight MVVM Sample for that idea.

Add a <bing:Map …/> element inside the layout root and set the CredentialsProvider property to the key that you generated earlier.

image

At this point, you should be able to press F5 and see the default map in your browser.

image

If you see the “Invalid Credentials” message then double check to make sure you have properly set the CredentialsProvider property.

image

At this point I like to clean up the Web project a little bit.

Open up the BingMapsAppTestPage.aspx page, select everything from <!DOCTYPE … down to the bottom of the file, and Ctrl+C to copy it to the clipboard.

Open up the Default.aspx page, delete everything from <!DOCTYPE … down to the bottom of the file, and paste in the contents from the clipboard.

Save the Default.aspx file.

Delete the BingMapsAppTestPage.aspx page and the BingMapsAppTestPage.html file.

Right click on Default.aspx and choose Set As Start Page…

image

Press F5 to run the project and confirm that the control is now hosted in the default page.

 

 

Create a New Bing Maps Silverlight Application with Blend

Now that we’ve covered how to create a new Bing Maps Silverlight application with Visual Studio 2008, lets take a look at how to accomplish the same task with Blend 3.

Start Blend 3 and choose File > New Project…

Select Silverlight from the Project types tree, and Silverlight 3 Application + Website from the template list.

Give your application a name, and click OK.

image

When you create a new Silverlight project in Blend, the structure of the web site is different than if you had created it in Visual Studio. The Silverlight app is hosted in an html page rather than an ASP.NET aspx page.

image

After the project is created, add references to the Bing Maps assemblies.

Right click on the References folder in the BingMapsApp project and choose Add Reference…

image

Browse to the Libraries directory in the Bing Maps Silverlight Control installation folder. The default installation location is C:\Program Files\Bing Maps Silverlight Control\V1\Libraries.

Select both the Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll assemblies, and click OK.

image

image

Click the Asset Library button and type “map” in the search box to filter the assets to those controls that contain the phrase “map” in their name. Then select the Bing Maps Silverlight Control from the available choices.

image

Note: The preceding screen shot shows two controls named “Map”. The other one is ESRI’s ArcGIS API for Microsoft Silverlight/WPF Map Control. If you also happen to have this library installed, make sure you choose the correct Map.

After selecting the Map control, it will appear below the Asset Library button.

image

Double click the Map control button to add a map to the artboard at the default size.

image

Click on the Xaml button on the upper right hand side of the artboard to switch to Xaml view.

image

Notice that when you added the Map control Blend automatically added a namespace declaration for you.

image

Since the automatically generated namespace name is rather verbose, change it to “bing”.

image

Click the Design button on the upper right hand side of the code window to switch back to the Design view.

In the Properties window Layout bucket, the right and bottom margins will have defaulted to 240 and 180 respectively.

image

Set each of these properties to 0 so that the map fills the whole layout.

image

image

At the bottom of the Properties window, click arrow next to the Miscellaneous tab to expand it.

Paste your API Key into the text box for the CredentialsProvider property and hit return.

image

At this point you can click F5 to compile and run the project. Notice that the map does not fill the entire browser window like it did with the Visual Studio solution we built in in part 1.

image

This is because Blend sets an explicit Width and Height by default when creating a User Control as part of a new project, while Visual Studio sets DesignWidth and DesignHeight properties with the “Ignorable” namespace prefix.

Switch back to Xaml view, and replace the names of the Width and Height properties with d:DesignWidth and d:DesignHeight.

image

Now run the project again and the Map will fill the whole browser window.

As was the case in the Visual Studio walkthrough, if you see the “Invalid Credentials” message then double check to make sure you have properly set the CredentialsProvider property.

Wrapup

This post presented two methods for creating a new Bing Maps Silverlight Control project. The first method gave a walkthrough using Visual Studio 2008 SP1, and the second showed how to accomplish the same task with Blend 3. You can use either method, depending on which tool you are most comfortable with.

Additional Resources

Microsoft

Bing Maps MVVM Samples

Tags: , , ,

Bing Maps | Silverlight

How To Create an ArcSDE Spatial View With an Outer Join

by James Richards July 16, 2009

Overview

ArcSDE Spatial Views are a useful tool for organizing information from multiple feature classes and geodatabase tables into a single “virtual feature class” or table at the database level. Similar to a database view, they allow administrators to join commonly used information so that the users do not need to perform the same actions repeatedly in a client (in this case, ArcMap).

Spatial Views are created with the “sdetable –o create_view …” command. When creating a Spatial View in this manner, the default join type is INNER JOIN. This fact cannot be altered via the command line syntax. INNER JOIN is the most restrictive join type and only records that match on both sides of the join will be in the resulting view. But once a default INNER JOIN Spatial View has been created, the join type can be changed after the fact either with the ALTER VIEW SQL statement, or through a database administration tool.

This article demonstrates how to create a Spatial View and then change the join type using SQL Server Management Studio. The same principles can be applied to other databases using whatever management tool you have available. [more]

Discussion

Step 1: Create the Spatial View

To create the Spatial View, use the sdetable –o create_view command. The syntax for this command is:

sdetable -o create_view -T <view_name> -t <table1,table2...tablen>
-c <table_col1,table_col2...table_coln>
[-a <view_col1,view_col2...view_coln>] [-w <"where_clause">]
[-i <service>] [-s <server_name>] [-D <database>]
-u <DB_User_name> [-p <DB_User_password>] [-N] [-q]

The important parameters that we will be working with in this example are:

  • -T : The name of the view to create
  • -t : The list of tables to include in the view
  • -c : The list of columns to include in the view
  • -a : The list of field alias as they will appear in the view
  • -w : A where clause to define the view
  • -u : The name of a user with ArcSDE administration privileges
  • -p : The password for the aforementioned user

Here’s an example that joins a geodatabase table to a feature class on a common ID field called Join_ID:

sdetable –o create_view –T MyView –t “MyLayer, MyTable”
-c “MyLayer.OBJECTID, MyLayer.Shape, MyLayer.Join_ID, MyTable.Join_ID, MyTable.Field1”
-a “OBJECTID, Shape, Layer_Join_ID, Table_Join_ID, Table_Field1”
-w “MyLayer.Join_ID = MyTable.Join_ID”
-u ****** –p ******

One thing that’s a little odd about this syntax is that the join information is given in the WHERE clause. In a typical SQL CREATE VIEW statement, the join information would be found in the FROM clause.

Also note that if you want the resulting view to be a virtual feature class that can be used with ArcGIS Desktop, you will need to include the OBJECTID and Shape fields from the original feature class in the list of columns to include.

For reference, the sample data in MyLayer and MyTable looks like so:

image

Notice that there is no record with a Join_ID of 5 in MyTable, and no record with a Join_ID of 7 in MyLayer.

Step 2: Modify the View Definition in SQL Server Management Studio

As was mentioned in the overview, the default behavior of “sdetable –o create_view” creates a view with an INNER JOIN. If we add the newly created view to ArcMap, only the 5 matching records are shown in the view:

image

To modify the view definition so that it uses an outer join, open SQL Server Management Studio and drill down to the Views folder under the sde database. Next, right click on the view you just created and choose Modify.

 image

Change the phrase “INNER JOIN” in the FROM clause to “LEFT OUTER JOIN” and save the changes.

image

Now if we look at the View in ArcMap again, we will see all records from MyLayer, with null values for any records with a Join_ID in MyLayer that was unmatched in MyTable:

image

You can use other join styles such as FULL OUTER JOIN, but keep in mind that if the primary OBJECTID (or Row ID) column contains multiple null values, client behavior will become unpredictable.

Wrap Up

When creating ArcSDE Spatial Views using the “sdetable –o create_view” command, the default join type is the most restrictive INNER JOIN. But you can change the view definition to use other join types using the ALTER VIEW SQL command or your database’s management tool. The article showed an example of how to make such a change with SQL Server Management Studio.

I hope you find this helpful!

Resources

 

Tags: ,

ArcSDE | How To | Planet GS

ArcGIS Server WebADF: Adjusting the Zoom Scale for Find Address Task Results

by James Richards July 02, 2009

Overview

When working with the Find Address Task in the ArcGIS Server WebADF, the default zoom scale that is displayed when the user zooms to a found address might not be what you want. This article discusses how to change it using the ZoomToPointFactor property. [more]

Background

Let’s assume that you have a WebADF application with some Map Layers and a Find Address Task that is configured to use a Geocoding Service. This could be an application that you created from scratch in Visual Studio, or one that you created with ArcGIS Server Manager.

After entering an address:

image

The results are shown in the Task Results panel. From there you can right click on an address and choose Zoom To:

image

This will zoom the map to an area centered on the address point. The problem is that scale to which the map is zoomed might not be what you want. Often it’s zoomed out too far.

Solution

To adjust the zoom scale to your liking, add the ZoomToPointFactor property to the esri:TaskResults element on the ASP.NET Page hosting the application.

If you created the application with ArcGIS Server Manager using the default options, this would be found in the Default.aspx file in the C:\inetpub\wwwroot\[YourApplicationName] directory.

Open this file and search for the esri:TaskResults tag, then add the ZoomToPointFactor property and give it a numeric value. In the example below, I’ve set the property to “1000”.

image ArcGIS Server will divide the width and height of the map by this value when zooming to a point. For this example, the zoom area will be 1/1000th of the full extent. The larger the ZoomToPointFactor, the further in that the map will zoom. You will need to experiment to find the ratio that works best, as the full extent can vary greatly depending on the data in your application.

Also note that if you have any cached map services in your application, ArcGIS Server will snap to the cache scale that is closest to the calculated map extent. Consequently, you might not see the map extent change if you don’t modify the ZoomToPointFactor property by a large enough value to jump to another scale.

Resources

Tags: , , ,

.NET | ArcGIS Server | How To | Planet GS | WebADF

Google Maps API for Flash and ArcGIS Server REST API Integration – Part 2

by James Richards June 15, 2009

Overview

This is the second post in a series where I discuss techniques for interacting with the ArcGIS Server REST API from within a Flex 3 application built with the Google Maps API for Flash. If you haven’t read the first post yet, I encourage you to do so now.

The first post presented a simple example that demonstrated how to stream features from ArcGIS Server and overlay them on top of Google Maps data. In this second post, we will reorganize the code for better reusability and add some geocoding functionality.

image

I’ll keep this post pretty brief and only cover the major points. Feel free to check out the live example and dive into the source code! [more]

Reorganizing the Code

In the previous post all code was presented in-line in a single mxml file. While this helped illustrate the concepts simply and succinctly, the code was not really reusable other than by the old copy/paste method. In this updated example, all of the feature streaming and drawing code has been placed in its own PolygonOverlay class.

This new class has a number of advantages over the old one. Many of the hard coded values, such as minimum and maximum zoom levels, the tool tip field and symbolization options are now encapsulated in the class definition.

The PolygonOverlay class’s constructor takes a number of these options, making it more flexible. For example:

polygonOverlay = new PolygonOverlay(map, // Google Map
    parcelsUrl,         // Root url of layer in ArcGIS Server REST API
    17,                    // Minimum Zoom Level
    21,                   // Maximum Zoom Level
    "RNO",              // Tool tip field
    transparentFill,  // Default fill style
    greyStroke);     // Default line style

In the previous post, all graphics on the map were erased each time the polygons needed to be redrawn. This is a problem if there are other graphics on the map. Now that we have added geocoding functionality to the sample application, we will draw the geocode results as a marker on the map along with an info window. We don't want these graphics to be erased when the user pans or zooms and the parcel polygons are redrawn.

The new PolygonOverlay class handles this by keeping track of all the Google Polygon objects that it draws on the map. When it needs to redraw the polygons, only the previous polygons are removed from the map, while the other graphics are left on the map. Check out the PolygonOverlay.as class for more details.

Adding Geocode Functionality

The new sample application also adds geocoding using the ClientGeocoder class. The findAddress function sets up the client geocoder and dispatches the request:

private function findAddress():void {
    trace("findAddress() - entered");
    var address:String = addressText.text;
    trace("findAddress() - address: " + address);

    // Constrain geocoder to a viewport that is in/around Portland, Oregon
    var sw:LatLng = new LatLng(-122.76728, 45.45137);
    var ne:LatLng = new LatLng(-122.53211, 45.59233);
    var viewport:LatLngBounds = new LatLngBounds(sw, ne);
    var options:ClientGeocoderOptions = new ClientGeocoderOptions();
    options.viewport = viewport;

    // Setup client geocoder and dispatch request
    var geocoder:ClientGeocoder = new ClientGeocoder();
    geocoder.setOptions(options);
    geocoder.addEventListener(GeocodingEvent.GEOCODING_SUCCESS, geocoder_GeocodingSuccess);
    geocoder.addEventListener(GeocodingEvent.GEOCODING_FAILURE, geocoder_GeocodingFailure);
    geocoder.geocode(address);
}

One interesting thing that’s happening here is that we’re constraining the geocode request to a viewport that is in/around the Portland, Oregon area. Google’s geocoding service will attempt to find the best match in that area.

I don’t know much about Portland (I’ve never been there) but I do know that it’s home to a number of microbreweries as well as the historic Burnside Skate Park. Try entering these addresses and place names:

  • 206 SW Morrison St (Location of the Rock Bottom Brewery)
  • Burnside Skate Park

One of the interesting things about using the Geocoding Service with a viewport is that many place names get resolved to actual locations without having to go through a confirmation step. This won’t work everywhere for every place name, but it helps narrow things down a bit so Google’s Geocoding Service can work its magic.

Wrap Up

That’s all the detail I’ll cover in the post, but be sure to check out the source code for more goodies like how to add the address marker and pop up an info window.

Stay tuned for future posts, where we will continue to add functionality and improve upon the code.

Live example is here, and source code is here.

Resources

Downloads
Documentation and Sample Servers
Blogs and Forums
Other Items of Interest

Tags: , , , ,

ArcGIS Server | Flex 3 | Google Maps | How To | Planet GS

Google Maps API for Flash and ArcGIS Server REST API Integration – Part 1

by James Richards May 12, 2009

Overview

This is the first post in a series where I will discuss techniques for interacting with the ArcGIS Server REST API from within a Flex 3 application built with the Google Maps API for Flash.

With this post, we start with a simple example that demonstrates how to stream features from ArcGIS Server and overlay them on top of Google Maps data. In this case, we will be working with parcel data.

This first example serves to demonstrate the basic concepts as succinctly as possible. In future posts, I will gradually refactor and improve upon the code to create reusable components. [more]

Background

You may be asking yourself “ESRI has a Flex API, why aren’t we using that?” Yes, ESRI does have an excellent Flex API - but it lacks the ability to show Google Maps data or use the Google Geocode API.

This is probably more of a Google licensing issue than anything else. But as of today, if you want to display your own ArcGIS Server content on top of Google Maps data in a Flex/Flash project, you can’t use the ESRI Flex API to do it without violating the Google Maps Terms of Service.

If Google were to open up its TOS to allow direct access to the map tiles (as many have called for) that would solve part of the problem. But you would still have the issue of being restricted in what you can do with the results of a call to the Geocode API. My understanding is that you are only allowed to display the result(s) on a Google Map, not in any other mapping system.

In my experience, the Google Maps data and Geocode functions are superior to the ArcGIS Online equivalents that are accessible from the ESRI Flex API. If you want to take advantage of Google’s map content and Geocode functionality, it’s worth the effort to integrate the ArcGIS Server REST API with the Google Maps API for Flash .

Don’t take this to mean I don’t like ESRI’s Flex API – it has many strengths which I’ll cover in future posts. In the meantime, if you’re going that route, check out Thunderhead Explorer - an excellent blog by ESRI’s Mansour Raad featuring “Tips and Tricks with ArcGIS Flex Mapping API”.

Sample Application Concepts

Our simple sample application works as follows:

  • The Google Maps API for Flash is embedded in a Flex 3 application.
  • The parcels are served via an ArcGIS Server Map Service, accessible via ArcGIS Server’s REST API.
  • The parcels are displayed at zoom levels 17 - 19.
  • The parcels are fetched via an HTTPService each time the map extent changes.
  • The parcels are returned in JSON format.
  • The JSON response is decoded using the open source JSON decoder in as3corelib.
  • Each parcel’s geometry is converted to a Polygon Overlay and added to the map.
  • Parcels are drawn with dark grey lines when the normal map is selected and white lines when the Satellite or Hybrid map is selected.
  • When the user hovers the mouse over a parcel, it’s parcel number is displayed in a tooltip.

Here are a couple of screenshots:

image

image

Live example is here, and source code is here.

Discussion

Starting with a basic Flex Application, we have a single map with a specified “map ready” event:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:gm="com.google.maps.*"
    backgroundGradientColors="[0xFFFFFF, 0xAAAAAA]"
    height="100%" width="100%" >
    <gm:Map id="map"
        width="100%"
        height="100%"
        key="(Your API Key Here)"
        mapevent_mapready="map_mapReady(event)" />

    <mx:Script>
        // Omitted for clarity
    </mx:Script>
</mx:Application>

Next, we declare a few member variables at the top of the script block:

// Parcels URL and parameters
private var parcelsUrl:String = "http://sampleserver1.arcgisonline.com/" +
    "ArcGIS/rest/services/Portland/ESRI_LandBase_WebMercator/MapServer/1/query";
private var wgs84wkid:String = "4236";
private var toolTipField:String = "RNO";

// Parcels map service
private var parcelsService:HTTPService = new HTTPService();

// Parcels symbols
private var transparentFill:FillStyle;
private var greyStroke:StrokeStyle;
private var whiteStroke:StrokeStyle;

With the parcelsUrl variable, we are specifying an endpoint to an ArcGIS Server REST API query function on a Layer in a MapService. See the ESRI ArcGIS Server REST API Reference for more information on the specific URL format for calling this operation.

For this example, we have chosen an ESRI sample server that has a parcels layer available for Portland, Oregon. We can explore this MapService and other sample services using the interactive SDK located at the following URL: http://sampleserver1.arcgisonline.com/arcgis/rest/services

image 

By drilling down into the Portland Folder and MapServices, we can learn about the functionality and data layers that are available for consumption. If we drill down to the layer in question, we can see a few interesting things about it, including the field names and aliases.

We also see that the layer has a spatial reference well known id (wkid) of 102113 – also known as Web Mercator. This is great if you need to overlay the layer as map tiles on Google Maps, because Google’s map tiles are also in Web Mercator. However, streamed geometries with coordinates in the Web Mercator spatial reference will not overlay properly on top of Google Maps. They must be in WGS 84 (Lat/Lon).

Therefore, we have a wgs84wkid variable which holds the well known id for WGS 84. As you will see, the ArcGIS Server REST API allows us to specify input and output spatial references that are different from the spatial reference of the MapService – and it will perform the coordinate conversions on fly.

The map_mapReady function is filled in next:

private function map_mapReady(event:Event):void {
    // Setup parcel symbols
    transparentFill = new FillStyle({
        alpha: 0.0 });
    greyStroke = new StrokeStyle({
        alpha: 1.0,
        thickness: 1.5,
        color: 0x333333 });
    whiteStroke = new StrokeStyle({
        alpha: 1.0,
        thickness: 1.5,
        color: 0xffffff });
   
    // Add map controls
    var mtc:MapTypeControl = new MapTypeControl();
    map.addControl(mtc);
    var pc:PositionControl = new PositionControl();
    map.addControl(pc);
    var zc:ZoomControl = new ZoomControl();
    map.addControl(zc);
    var sc:ScaleControl = new ScaleControl();
    map.addControl(sc);
    var omc:OverviewMapControl = new OverviewMapControl();
    map.addControl(omc);

    // Add event listeners
    map.addEventListener(MapMoveEvent.MOVE_END, map_moveEnd);

    // Initialize map           
    map.setCenter(new LatLng(45.520211, -122.65553), 17,
        MapType.NORMAL_MAP_TYPE);
}

In the map_mapReady function, we setup a transparent fill style and stroke styles for the dark grey and white lines. We also initialize the map controls and add an event listener for the map’s “move end” event. This event is where we will call the ArcGIS Server REST API to get the parcels for the current map extent. Finally, we center the map and zoom in on an area of Portland so that we’ll have something to look at when the application starts.

The map’s “move end” event is where we send an http request to ArcGIS Server to get the parcels:

private function map_moveEnd(event:MapMoveEvent):void {
    // Get zoom level
    var zoom:Number = map.getZoom();
    trace("map_moveEnd - Zoom level: " + zoom.toString());

    // Only draw parcels if zoomed in far enough
    if (zoom < 17) {
        // This just clears the overlays if we're not zoomed in far enough.
        // This would be a problem if there are other overlays besides parcels.
        // We'll deal with that in a future post.
        map.clearOverlays();
        return;
    }

    // Get current extent
    var bnds:LatLngBounds = map.getLatLngBounds();
   
    // An ESRI envelope is configured as xmin, ymin, xmax, ymax
    var envelope:String = bnds.getWest().toString() + "," +
                          bnds.getSouth().toString() + "," +
                          bnds.getEast().toString() + "," +
                          bnds.getNorth().toString();
    trace("map_moveEnd - Envelope: " + envelope);
   
    // Cancel http service in case a request was sent
    // that hasn't come back yet
    parcelsService.cancel();

    // Setup http service and add listeners
    parcelsService.url = parcelsUrl;
    parcelsService.addEventListener(ResultEvent.RESULT, onParcelResult);
    parcelsService.addEventListener(FaultEvent.FAULT, onHttpFault);

    // Setup parameters to pass to the service
    var params:Object = new Object();
    params.geometry = envelope;
    params.geometryType = "esriGeometryEnvelope";
    params.inSR = wgs84wkid;
    params.spatialRel = "esriSpatialRelIntersects";
    params.returnGeometry = "true";
    params.outSR = wgs84wkid;
    params.outFields = toolTipField;
    params.f = "json";

    // Send the request
    parcelsService.send(params);
}

First, we get the current zoom level. If the level is less than 17, we clear all overlays and exit. This is a shortcut to keep the example simple. If we were drawing any other overlays besides parcels, this would obviously be a problem. We’ll look at a better way to handle clearing the parcels in a future post.

Next, we get the LatLngBounds of the map and convert it into a string representation of an ESRI envelope that can be passed to the ArcGIS Server REST API.

Before making a new request through the HTTPService, we cancel it in case a prior request was sent that hasn’t come back yet.

Next we assign the URL endpoint to the HTTP Service and set up the event listeners.

In Flex 3 (like in Javascript), you can create an anonymous object and add properties to it “on the fly”. Here we create a new Object named params and add all of the parameters we want to pass as arguments in the URL query string. See the ESRI ArcGIS Server REST API Reference for more information on what parameters are available when calling the query operation on a layer.

Once the params Object is constructed, it is passed to the send method on the parcelsService (HTTPService). The HTTPService converts each property into a query string argument before sending the request to the REST API for the MapService.

The “on parcel result” event will be fired by the HTTPService when a response is received.

private function onParcelResult(event:ResultEvent):void {
    //trace(event.result as String);

    // Use ascorelib JSON serializer to decode returned object
    var obj:Object = JSON.decode(event.result as String);
    if (obj.error != null) {
        Alert.show('An error occured on the server. The response message returned was:\n\n' +
            event.result, 'Server Error');
        return;
    }

    // Simply clear all the polygons from the map.
    // This would be a problem if there are other overlays.
    // We'll deal with that in a future post.
    map.clearOverlays();

    // Get the features array from the returned JSON
    var features:Array = obj.features as Array;

    // This code breaks multipolygons into a separate polygon for each
    // ring. Not ideal, but necessary at this zoom level because the only
    // way to create multi ringed polygons in GMaps Flash API (as of v 1.9c)
    // is with the Polyline Encoding Algorithm - and this algorithm rounds
    // to 5 decimal places - thus creating choppy looking polygons.
    // We'll discuss this further in a future post.
    for each (var feature:Object in features) {
        for each (var ring:Array in feature.geometry.rings) {
            var points:Array = new Array();
            for each (var point:Array in ring) {
                points.push(new LatLng(point[1], point[0]));
            }

            var polyOpts:PolygonOptions = new PolygonOptions();
            polyOpts.fillStyle = transparentFill;
            polyOpts.strokeStyle =
                (map.getCurrentMapType() == MapType.NORMAL_MAP_TYPE ?
                greyStroke : whiteStroke);
            polyOpts.tooltip = feature.attributes[toolTipField];

            var poly:Polygon = new Polygon(points, polyOpts);
            map.addOverlay(poly);
        }
    }               
}

The ResultEvent’s result property contains the JSON string returned from the server. We use the ascorelib JSON serializer to decode the returned string into an Object. From there, we can access the properties of the result object defined for the ArcGIS Server response.

The JSON Response Syntax is defined as follows:

{
"displayFieldName" : "<displayFieldName>",
"fieldAliases" : {
    "<fieldName1>" : "<fieldAlias1>",
    "<fieldName2>" : "<fieldAlias2>"
},
"geometryType" : "<geometryType>",
"spatialReference" : {<spatialReference>},
"features" : [
    {
    "attributes" : {
        "<fieldName1>" : <fieldValue11>,
        "<fieldName2>" : <fieldValue21>
    },
    "geometry" : {<geometry1>}
    },
    {
    "attributes" : {
        "<fieldName1>" : <fieldValue12>,
        "<fieldName2>" : <fieldValue22>
    },
    "geometry" : {<geometry2>}
    }
]
}

In this case, since we are requesting polygons, the JSON Response Syntax for the geometries is defined as follows:

{
"rings" : [
    [ [<x11>, <y11>], [<x12>, <y12>], ..., [<x11>, <y11>] ],
    [ [<x21>, <y21>], [<x22>, <y22>], ..., [<x21>, <y21>] ]
],
"spatialReference" : {<spatialReference>}
}

Once we get the features array from the Object we iterate each feature, get its attributes and geometry, construct a Google Maps Polygon Overlay, and add it to the map.

When constructing the Polygon, a PolygonOptions object is created to define its look and behavior. We use this object to define the symbology, choosing dark grey lines if the map type is Normal, and white lines if the map type is one of the others such as Satellite or Hybrid. The PolygonOptions object is also used to define the tool tip that will be displayed when the user hovers the mouse over the Polygon.

Note that if the ESRI polygon has multiple rings, we simply create a separate Google Polygon for each ring. This is necessary because the Google Polygon constructor can only take one array of points and does not handle multiple rings.

There is a way to create multi-ringed Google Polygons using the Polygon.fromEncoded() static method, but this turns out to be problematic due to the rounding errors introduced by the encoding algorithm.

Default 500 Feature Limit

ArcGIS Server MapServices have a default limit of 500 features that can be returned from a layer query request. If you Pan/Zoom around the live example application, you might notice missing parcels if you are in a dense area and the limit is reached. The limit can be changed by modifying the MaxRecordCount in the MapService’s configuration file on the server. But since we’re using an ESRI sample server, I can’t very well make that change.

Wrap Up

This simple application has served to demonstrate the basics of communicating with the ArcGIS Server REST API and overlaying the results on a Google Map built with the Google Maps API for Flash.

Stay tuned for future posts, where we will refactor and improve upon the code to create reusable components.

Live example is here, and source code is here.

Resources

Downloads

Documentation and Sample Servers

Blogs and Forums

Tags: , , , ,

ArcGIS Server | Flex 3 | Google Maps | How To | Planet GS

Reduce ArcGIS Server Boilerplate Code By Implementing IDisposable

by James Richards April 19, 2009

When programming with ArcObjects and ArcGIS Server, code often follows a common pattern of connecting to ArcGIS Server, getting hold of an IServerContext, doing some work, and releasing the context. This leads to a lot of unnecessarily repeated setup and teardown code, usually taking a form similar to the following example:

IServerContext context = null;

try

{

    IGISServerConnection conn = new GISServerConnectionClass()

        as IGISServerConnection;

 

    // Connect as ASPNET or NETWORK SERVICE.

    // Get optional host setting from web.config,

    // or use localhost if not found.

    string host = ConfigurationManager.AppSettings["AGSHost"];

    if (String.IsNullOrEmpty(host))

    {

        host = "localhost";

    }

 

    // Make connection

    conn.Connect(host);

    IServerObjectManager som = conn.ServerObjectManager;

    context = som.CreateServerContext("MyServer", "MapServer");

   

    // Do some stuff with the server context

}

finally

{

    if (context != null)

    {

        context.ReleaseContext();

    }

}

In this example, there is a lot of boilerplate happening just to get to the server context and do something interesting. Seeing this kind of code repeated throughout an ArcGIS Server project made me a little queasy as it violates the DRY Principle, so that got me thinking about how to eliminate it. [more]

I decided to create a wrapper class that handles making the connection and implements IServerContext and IDisposable. Inspiration for this idea comes from the .NET SqlConnection class, which also implements IDisposable and can therefore be instantiated inside of a using block to automatically close the connection and release any resources when the using block goes out of scope. (See this David Hayden post for a basic discussion of why this is a good thing.) Proper implementation of IDisposable is known as the Dispose Pattern and is documented both here and in Cwalina and Adams excellent Framework Design Guidelines.

With this new class, we can reduce the boilerplate code block to the following:

using (ArcGisServerContext context = new ArcGisServerContext("MyServer",

    ArcGisServiceType.MapServer))

{

    // Do some stuff with the server context

}

Notice also the use of an Enum to indicate the server type, thus eliminating one of the error prone hard coded strings as well. (In a real application, I would also retrieve the value “MyServer” from a config or resource file.) The Enum is defined like so:

public enum ArcGisServiceType

{

    MapServer,

    GeocodeServer,

    GlobeServer,

    GPServer,

    GeoDataServer,

    GeometryServer,

    ImageServer,

}

and the ArcGISServerContext class is implemented as follows:

using System;

using System.Configuration;

using ESRI.ArcGIS.Server;

public class ArcGisServerContext : IServerContext, IDisposable

{

    #region Fields

    private IServerContext serverContext;

    #endregion

 

    #region Constructors()

    // Here you can add overloaded constructors to take additional

    // arguments such as login credentials - Or you could alter the

    // constructor to use an ArcGIS Server Identity from web.config.

 

    // This example just connects as the current user, which will be

    // ASPNET or NETWORK SERVICE if running under IIS on Wind XP or 2003.

    public ArcGisServerContext(string serviceName,

        ArcGisServiceType serviceType)

    {

        IGISServerConnection conn = new GISServerConnectionClass()

            as IGISServerConnection;

 

        // Get optional host setting from web.config,

        // or use localhost if not found.

        string host = ConfigurationManager.AppSettings["AGSHost"];

        if (String.IsNullOrEmpty(host))

        {

            host = "localhost";

        }

 

        // Make connection and cache IServerContext

        conn.Connect(host);

        IServerObjectManager som = conn.ServerObjectManager;

        serverContext = som.CreateServerContext(serviceName,

            serviceType.ToString());

    }

    #endregion

 

    #region IServerContext Implementation

    // These methods just pass through to the cached server context

 

    public object CreateObject(string clsid)

    {

        return serverContext.CreateObject(clsid);

    }

 

    public object GetObject(string name)

    {

        return serverContext.GetObject(name);

    }

 

    public object LoadObject(string str)

    {

        return serverContext.LoadObject(str);

    }

 

    public void ReleaseContext()

    {

        serverContext.ReleaseContext();

    }

 

    public void Remove(string name)

    {

        serverContext.Remove(name);

    }

 

    public void RemoveAll()

    {

        serverContext.RemoveAll();

    }

 

    public string SaveObject(object obj)

    {

        return serverContext.SaveObject(obj);

    }

 

    public IServerObject ServerObject

    {

        get { return serverContext.ServerObject; }

    }

 

    public void SetObject(string name, object obj)

    {

        serverContext.SetObject(name, obj);

    }

    #endregion

 

    #region IDisposable Implementation

    // Implementation of the Dispose Pattern

 

    public void Dispose()

    {

        Dispose(true);

        GC.SuppressFinalize(this);

    }

 

    protected virtual void Dispose(bool disposing)

    {

        // Release the server context

        if (disposing)

        {

            if (serverContext != null)

            {

                serverContext.ReleaseContext();

            }

        }

    }

    #endregion

}

 

It’s really pretty simple, and it sure is nice not to have to copy / paste or keep re-typing the same boilerplate code over and over.

Hope this helps!

Tags: , , ,

.NET | ArcGIS Server | Planet GS

A Simple Daily Backup Strategy for Subversion Using 7-zip and Dropbox

by James Richards February 17, 2009

Background

I’m using Subversion (via VisualSVN Server) for source control. I recently decided to add daily offsite backup to my setup. This was relatively easy to accomplish with 7-zip, Dropbox, and a batch file.

VisualSVN Server is a free package that makes setting up and managing Subversion on Windows really easy. It includes Subversion, an Apache web server, and a GUI management console.

Dropbox is an easy to use online file storage and synchronization service. It’s free for accounts up to 2GB.

7-zip is a free, open source zip library that can be controlled from the command line. [more]

Setup

First I signed up for a Dropbox account, and downloaded and installed their synchronization software. After setup, I opted to move the “My Dropbox” folder to C:\Dropbox so it wouldn’t be located in my user profile directory. This was easy to do – I just selected Preferences… from the Dropbox system tray icon, clicked the Move… button, and specified the new directory.

Next I downloaded and installed 7-zip. Pretty self explanatory.

After all the components were installed, I made sure that the locations of the svnadmin and 7-zip utilities were added to the PATH environment variable. Right-click on My Computer, and choose Properties… Then click the Advanced tab and the Environment Variables button. Under the System Variables list, scroll to find the Path variable, select it and click Edit. On my system, I added the follow two entries to the end of the Path variable: “C:\Program Files\VisualSVN Server\bin\;C:\Program Files\7-Zip\;” (no quotes). When finished click OK 3 times.

Batch File

Now that all the pieces are in place, here’s a simple batch file that exports a repository to a temporary folder, zips it up, copies the zip file to the Dropbox folder and cleans up after itself. The script is called svnbackup.bat and takes a single argument, which is the name of the Repository to backup.

mkdir C:\Temp\%1
svnadmin hotcopy D:\Repositories\%1 D:\Temp\%1
7z a C:\Temp\%1.zip D:\Temp\%1*
copy C:\Temp\%1.zip "C:\Dropbox\My Dropbox\Repositories\%1.zip"
del C:\Temp\%1.zip
attrib -r C:\Temp\%1\format
attrib -r C:\Temp\%1\db\format
del /q /s C:\Temp\%1\*.*
rd /q /s C:\Temp\%1

Once the zip file is dropped in the Dropbox folder, synchronization automatically kicks in and the zip file is backup up to the Dropbox servers.

Notes

A couple of things to note. On my system, Subversion is set up to keep all repositories in the D:\Repositories directory. Also, I realize I could have made a variable for the temp directory, but I was being lazy. Finally, when deleting the temporary directory, I was trying to figure out how to tell attrib to recursively walk the subdirectories to clear all of the read only flags, but I couldn’t get it to work. Since there were only two read only files in the tree, I opted to punt and make two calls.

Automation

The final step to this solution is to automate the backup of all repositories on a daily basis. I created a second script which calls the svnbackup script with the name of each repository. It’s called svnbackup_all.bat, and here’s an example of what would go in there.

call C:\Scripts\svnbackup MyRepository
call C:\Scripts\svnbackup My2ndRepository

Once this script was in place I added a Scheduled Task to run it every night – while I’m sleeping more soundly with the knowledge that my source code will not be lost in the event of a major disaster.

Resources

VisualSVN Server
7-zip
Dropbox
Chad Myers DevEx article on Repeatability which includes a nice overview of VisualSVN Server
Google Search: 7-zip command line

Tags: , , , ,

7-zip | Dropbox | How To | VisualSVN

How To Use URL Rewriting for Extensionless WCF Svc Services with Helicon Tech ISAPI Rewrite

by James Richards February 02, 2009

Recently I was working on a WCF Service and wanted to serve it up with extensionless URLs. I downloaded and installed ISAPI Rewrite (Lite version) but struggled to come up with the correct regex expression to get it working.

I found many good blog postings and clues from Scott Gu, Scott Hanselman, and Jeff Atwood, and a thread on Stack Overflow, but none of these answered this specific question. As Scott Hanselman noted, URL rewriting can seem like "freaking voodoo".

So I moseyed on over to the Helicon Tech support forum and [more] asked the question. Anton (who I assume is one of the good folks at Helicon Tech) swiftly and correctly answered the question with the following example:

RewriteBase /
RewriteRule ^MyProject/MyService/products(.*)?$ MyProject/MyService.svc/products$1 [NC,L]

You can follow the complete thread here.

I hope this helps!

Tags: , , , , ,

.NET | ASP.NET | How To | REST | WCF

Powered by BlogEngine.NET 1.6.0.0
Theme by Mads Kristensen | Modified by Mooglegiant
Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 United States License.

Welcome

James Richards

Hi, I'm James Richards the CTO and co-founder of Artisan Global LLC. We make location-aware mobile apps with maps. I'm the author of QuakeFeed and I helped launch Zaarly at LASW Feb 2011. I also enjoy surfing, snowboarding, golfing, yoga, and music. I love my family: Linda, Sequoya and our cats Remy and Twiggy. Thanks for stopping by, I hope you find something helpful here.

Subscribe by RSS   Follow me on Twitter   Connect on Facebook   View my profile on LinkedIn


Amazon Associates

Some of my posts may contain Amazon Associates links. I only include these links when it makes sense within the context of the article. If you are in the market for one of these items, please consider clicking on an affiliate link to make your purchase. You still get the same great deal from Amazon and it will help me pay for hosting and bandwidth. Thanks!