Wednesday, February 20, 2008

Placing a label on the map

This examples shows how to place a label on the map. Actually, you can place any UIComponent. But make sure to set the width and height of the component :-)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:awx="http://www.arcwebservices.com/2007/awx"
xmlns:test="com.esri.test.*"
layout="absolute"
>
<test:LabelStyle id="labelStyle"/>
<awx:Framework apiKey="19640523"/>
<awx:Map createDefaultLayers="false">
<awx:MarkerLayer>
<test:LabelMarker geoX="45" geoY="45" style="{labelStyle}"/>
</awx:MarkerLayer>
</awx:Map>
</mx:Application>

The LabelStyle places the overlay on the map in pixel coordinates.

package com.esri.test
{
import com.esri.aws.awx.geom.PathType;
import com.esri.aws.awx.map.layers.overlays.OverlayObject;
import com.esri.aws.awx.map.layers.overlays.style.BaseStyle;
import com.esri.aws.awx.map.projection.IProjectionModel;

public class LabelStyle extends BaseStyle
{
override public function draw(
overlayObject:OverlayObject,
projectionModel:IProjectionModel
):void
{
var pathType:int = overlayObject.shape.getPathIterator(projectionModel).next(m_coords);
if (pathType == PathType.NOOP)
{
overlayObject.visible = false;
}
else
{
overlayObject.visible = true;
overlayObject.x = m_coords[0];
overlayObject.y = m_coords[1];
}
}
}
}

And here is the MarkerLabel class:

package com.esri.test
{
import com.esri.aws.awx.map.layers.overlays.Marker;

import mx.controls.Label;

public class LabelMarker extends Marker
{
override protected function createChildren():void
{
super.createChildren();
var label : Label = new Label;
label.text = "Hello, World!";
label.width = 200;
label.height = 24;
addChild( label );
}
}
}

BTW - There has to be a way to calculate the width and height of a string using the Font class (not sure if this class even exists) or something. I guess that is another blog entry :-)

Sunday, February 10, 2008

Sending EMail From Flex App

Check out this mailto: RFC.
var urlRequest : URLRequest = new URLRequest( "mailto:?subject=MySubject&body=MyBody");
navigateToURL( urlRequest, "_self" );
Make sure to add the _self target to not open a blank browser window.

Monday, February 4, 2008

Calling Find service from AJAX

Here is a sample that shows how to call the Find service from AWXBridge.  Try 'Disneyland' (no quotes :-)

<html>
<head>
<title>Find Sample</title>
<style>
body {
margin: 0px;
}
</style>
</head>
<body>
<div id="awx" style="width:100%; height:500px;">
<a href="http://www.adobe.com/go/getflashplayer">Download latest Adobe Flash Player</a>
</div>
<input id="placeInput" type="text">
<input type="button" value="Find" onclick="doFind()">
<script type="text/javascript" src="http://api.arcwebservices.com/awx/v4/awxbridge-4.0.js"></script>
<script language="JavaScript" type="text/javascript">

var m_root;
var m_map;

function onCreationComplete()
{
try
{
m_root = FABridge.awx.root();

m_root.installFindActivator();

m_map = m_root.createMap();

m_root.addChild(m_map);
}
catch(ex)
{
alert(ex.message);
}
}

function onResult(result)
{
try
{
var arr = result.getFindResults();
for (var key in arr)
{
var findResult = arr[key];
var point = findResult.getPoint()
m_map.setCenter(point);
m_map.setScale(1000000);
break;
}
}
catch(ex)
{
alert(ex.message);
}
}
function onFault(fault)
{
try
{
alert(fault.getMessage());
}
catch(ex)
{
alert(ex.message);
}
}
function findPlace(place)
{
try
{
var responder = m_root.createResponder(onResult, onFault);
var find = m_root.getFind();
find.findLocation(place, null, responder);
}
catch(ex)
{
alert(ex.message);
}
}
function doFind()
{
findPlace( document.getElementById("placeInput").value);
}

AWXBridge.insertFramework("awx", {apiKey:"7cf5e2b1c266030dad57a03e41474775"});

</script>
</body>
</html>

Screen cleaner

Check this out - way too funny for the dog lovers out there :-)

AWX and AJAX

I've seen a lot of interest lately in using AWX within an AJAX application. The preferred way is to use the AWXBridge API. This is based on Adobe's FABridge technology, and relies on a set of patterns to "bridge" the JavaScript and ActionScript world. Check out this example to see it in action. Here is a quick primer:
A JavaScript root object is created to refer to an embedded Flex application - the FABridge property, in this case 'example', is a reference to the DIV id where the Flex application SWF will reside.
var flexApp = FABridge.example.root();
Any ActionScript object with an empty constructor can be created from JavaScript using its full name:
var chart = FABridge.example.create("mx.charts.ColumnChart");
An AS object public property can be retrieved from JS using a getter function, where the name of the function is based on the property name prefixed with "get" and the first letter of the property is uppercased:
flexApp.getSlider().getValue();
An AS object public property can be modified from JS using a setter function, where the name of the function is based on the property name prefixed with "set" and the first letter of the property is uppercased:
var currentCheckValue = flexApp.getCheck().getSelected();
flexApp.getCheck().setSelected( ! currentCheckValue );
An AS object public function can be accessed directly.
var flexApp = FABridge.example.root();
flexApp.testFunc( "Hello, Actionscript World! Love, Javascript..." );
So, armed with a that tidbit of knowledge, to bridge AJAX and AWX, please refer to the ASDoc to get a list objects, properties and functions that you can call. A bit more of an insight, the awxbridge.swf file that is downloaded into the div element contains a set of public helper functions to create a set of AWX classes. You can find the list of functions here and a how some of them are used here.

Sunday, February 3, 2008

Tracking the mouse on the map

This is done by setting the mouseHandler property on a map instance to an implementation of the IMouseHandler interface. The IMouseHandler interface has the classic mouseUp, mouseDown, mouseMove abstract functions. In addition, there are two abstract functions; set map() and cleanup(). The cleanup method is called on any previously set handler before a new mouse handler is set. A map reference is "injected" (a al IoC) onto the mouse handler when it is assigned to the map using the setter function. AWX provided a base implementation (BaseMouseHandler) that has a protected helper function to convert a stage mouse location to a GeoPoint. Another provided implementation, BaseOverlayMouseHandler associates a mouse handler with a map layer. This class can be used to draw overlays on map using the mouse such as redlines and rubberband rectangles. The following implementation enables a user to draw a geodesic circle on the map. You can see it in action here. Select the "Draw Circle" from the context menu to draw a circle. Download the full source code from here.

package com.esri.sample
{
import com.esri.aws.awx.geom.CircleShape;
import com.esri.aws.awx.geom.GeoPoint;
import com.esri.aws.awx.map.handlers.BaseOverlayMouseHandler;
import com.esri.aws.awx.map.layers.OverlayLayer;
import com.esri.aws.awx.map.layers.overlays.Circle;
import com.esri.aws.awx.map.layers.overlays.style.IStyle;
import com.esri.aws.awx.map.layers.overlays.style.PolygonStyle;
import com.esri.aws.awx.map.projection.ProjUtils;

import flash.events.Event;
import flash.events.MouseEvent;

[Event(name="complete", type="flash.events.Event")]

public class CircleMouseHandler extends BaseOverlayMouseHandler
{
private var m_down : Boolean = false;
private var m_center : GeoPoint;
private var m_shape : CircleShape;
private var m_circle : Circle;

public function CircleMouseHandler(
overlayLayer : OverlayLayer
)
{
super(
overlayLayer,
new PolygonStyle,
false
);
}

override public function onMouseDown(event:MouseEvent):void
{
m_down = true;
m_center = stage2GeoPoint( event );
if( m_center )
{
m_shape = new CircleShape( m_center, 1, CircleShape.METERS);
m_circle = new Circle( m_shape, style );
overlayLayer.addOverlay( m_circle );
event.updateAfterEvent();
}
else
{
m_down = false;
m_circle = null;
}
}

override public function onMouseMove(event:MouseEvent):void
{
if(m_down)
{
var geoPoint : GeoPoint = stage2GeoPoint(event);
if( geoPoint )
{
m_shape.radius = geodeticInverse(m_center, geoPoint, ProjUtils.EARTH_RADIUS );
m_circle.invalidateDisplayList();
event.updateAfterEvent();
}
}
}

override public function onMouseUp(event:MouseEvent):void
{
m_down = false;
if( m_circle )
{
if( applyToMap == false)
{
overlayLayer.removeOverlay( m_circle);
}
event.updateAfterEvent();
dispatchEvent( new Event(Event.COMPLETE));
}
}

private function geodeticInverse(
fPt:GeoPoint,
tPt:GeoPoint,
earthRadius:Number
) : Number
{
var lam1:Number = ProjUtils.DDToRad(fPt.x), phi1:Number=ProjUtils.DDToRad(fPt.y);
var lam2:Number = ProjUtils.DDToRad(tPt.x), phi2:Number=ProjUtils.DDToRad(tPt.y);
var d_lam:Number = ProjUtils.Angle180(lam2 - lam1);

var cos_phi1:Number = ProjUtils.AngleIsHalfPi(Math.abs(phi1)) ? 0.0 : Math.cos(phi1);
var sin_phi1:Number = Math.sin(phi1);
var cos_phi2:Number = ProjUtils.AngleIsHalfPi(Math.abs(phi2)) ? 0.0 : Math.cos(phi2);
var sin_phi2:Number = Math.sin(phi2);

var cos_dlam:Number = ProjUtils.AngleIsHalfPi(Math.abs(d_lam)) ? 0.0 : Math.cos(d_lam);
var sin_dlam:Number = ProjUtils.AngleIsZero(Math.abs(d_lam)-Math.PI) ? 0.0 : Math.sin(d_lam);

var sigma:Number = Math.acos(sin_phi1*sin_phi2 + cos_phi1*cos_phi2*cos_dlam);
var distance:Number = sigma * earthRadius;
return distance;
}
}
}

Saturday, February 2, 2008

Reading Local Shapefiles

ESRI shapefiles are fairly ubiquitous. That is why I keep getting requests like this “Hey, I would like to render my local shapefiles on top the AWX map, can I do that ?” And I reply “Sure...but !” - Remember the security sandbox of the flash player; 1 - you can only communicate with the server that hosts the swf, or with a host that has a crossdomain.xml file, and 2 - there is no way that you can access your local disk drive directly - So, let’s turn these two restrictions into assets :-) I’m luck enough to run on a Mac, which comes with Apache Web Server built in. So, if I place a crossdomain.xml file at the base url of my local server, then I can access my local shapefiles. Cool eh ? Next, found this AS3 library that can read the shp and dbf file given a ByteArray instance. Here is a code snippet that I used on a project to overlay the continents shapes on top of the map.

private function onCreationComplete( event : FlexEvent ) : void
{
var urlStream : URLStream = new URLStream();
urlStream.addEventListener(Event.COMPLETE,
function( event : Event ) : void
{
var byteArray : ByteArray = new ByteArray();
urlStream.readBytes( byteArray, 0.0, urlStream.bytesAvailable);
urlStream.close();

var style : IStyle = new PolygonStyle( 0xCCCCCC, 0.25, 1, 0x0 );

var shpHeader : ShpHeader = new ShpHeader( byteArray);
while( byteArray.position < byteArray.length)
{
var shpRecord : ShpRecord = new ShpRecord( byteArray );
var shpPolygon : ShpPolygon = shpRecord.shape as ShpPolygon;
for each ( var arr : Array in shpPolygon.rings)
{
var coords : Array = [];
for each ( var sp : ShpPoint in arr )
{
coords.push( new GeoPoint( sp.x, sp.y));
}
var polygon : Polygon = new Polygon( new PolygonShape( coords), style);
polygonLayer.addOverlay( polygon );
}
}
}
, false, 0.0, true);
urlStream.load( new URLRequest("shapefiles/continents3.shp"));
}