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

Comments

5/14/2009 1:12:15 AM #

Lakshmanan


        Hi
        Really excellent article. Great work dude.

        Lakshmanan
      

Lakshmanan India |

5/21/2009 7:35:01 AM #

Lakshmanan


        Dude,

        I'm facing one issue in above sample. When I try to add my own data instead of ArcGIS online , am getting error "Security SandBox Violation". I have placed cross domain xml in web server like this below.

        I have developed similar application using ESRI Flex API there is no issue on that. Whereas security box violation error appears when I use above sample.



        <cross-domain-policy>
        <site-control permitted-cross-domain-policies = "by-content-type" />
        <allow-access-from domain="*"/>
        <allow-access-from domain=“*” secure  ="true"/>
        <allow-access-from-domain="*.google.com" secure = "true"/>
        <allow-access-from-domain="maps.googleapis.com" secure = "true"/>
        <allow-access-from-domain="*.corp.google.com" secure = "true"/>
        <allow-http-request-headers-from domain = "*" headers="SOAPAction"/>
        </cross-domain-policy>


        Please help me on this. This is very urgent

        Lakshmanan
      

Lakshmanan India |

5/21/2009 9:19:59 PM #

james


        @Lakshmanan,

        Cross domain security is not my forte, but perhaps this article on "How to Create a crossdomain.xml file" would be helpful:

        http://curtismorley.com/2007/09/01/flash-flex-tutorial-how-to-create-a-crossdomainxml-file/

        Just to get something working, you could start with Code example 2 that allows all domains, and then tighten things up from there.

        Hope this helps!
      

james United States |

5/22/2009 1:37:33 AM #

Lakshmanan


        Hi James

        Thanks dude. I will follow up the link. I would like to chat with you one day. let me know when you are free. Cheers. Drop me an email or ping me in my blog.

        Lakshmanan
      

Lakshmanan India |

5/27/2009 8:39:21 AM #

Flavio Carmo


        Hi James,

        This is great! For months i was trying to create this mashup, but i couldnt find any example like this.

        My mashup now works 100%!

        But i cant add a image from a map cache... can u help me? i have a aerial foto map cache and i need to overlap with my google map. Can be done? what changes in this code?

        Again, Nice job!!
      

Flavio Carmo Brazil |

5/27/2009 10:57:23 PM #

Lakshmanan


        Hi

        Thanks for the sample. This works great. I want to show details on the Info Window while clicking the marker . In markeroptions I have used custom icon. Can you help me out.
      

Lakshmanan India |

6/2/2009 9:28:16 PM #

james


        Hi guys, Thanks for the positive feedback. Sorry it's taken me a minute to respond. I'm in the middle of moving so I've been pretty busy offline.

        @Flavio - I haven't worked out how to add a custom tile overlay to Google Maps API for Flash yet. It's a topic I'm planning to cover in a future post - maybe within the next month or so. In the meantime, you could have a look at some of the samples on google code:

        http://code.google.com/apis/maps/documentation/flash/demogallery.html?searchquery=tilelayeroverlay&classname=

        @Lakshmanan - I'll be posting Part 2 of this series soon, and it includes some code to add a marker and info window. Hopefully I'll finish the post after I move and it should be up early next week.
      

james United States |

7/6/2009 3:27:22 PM #

james


        @Flavio - I just added a new article on how to add an ArcGIS Server cached tile overlay to a Google Maps API for Flash application. It might answer your previous question...

        http://www.jamesrichards.com/post/2009/07/06/How-To-Display-an-ArcGIS-Server-Cached-Tile-Layer-as-a-Custom-Map-Type-with-the-Google-Maps-API-for-Flash.aspx
      

james United States |

7/6/2009 9:15:32 PM #

trackback


        Trackback from Programming and the GeoWeb

        How To: Display an ArcGIS Server Cached Tile Layer as a Custom Map Type with the Google Maps API for Flash
      

Programming and the GeoWeb |

2/2/2010 3:03:22 PM #

alex

I am totally new to flex, action scripts. I just downloaded James's codes. I only changed to <gm:Map3D id="map" from <gm:Map id="map" and it works with my other flex 3d map sample. It looks great. Thank you very much James. I am exploring the possibility to add ArcIMS layers on the map instead of ArcGIS server services. Any suggestions? We may not give up the good old ArcIMS for a while.

alex United States |

2/5/2010 10:22:57 AM #

james


        Hi Alex,

        Thanks for your note. I'm happy the sample was helpful for you. I haven't used ArcIMS in quite some time, but the process should be pretty similar. You would need to make the request to ArcIMS and then parse the returned ArcXml instead of parsing the JSON returned from ArcGIS Server. You could use the ActionScript XML() object for that task:

        http://www.adobe.com/support/flash/action_scripts/actionscript_dictionary/actionscript_dictionary827.html

        Hope this helps...
      

james |

Comments are closed

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!