Home > ActionScript, Flex > Working with ESRI SHP Files in ActionScript


Working with ESRI SHP Files in ActionScript

September 4th, 2009

There are lots of heavy weight solutions to drawing maps in Flex and Flash but for a recent project I needed something lightweight to display on a web portal. By lightweight, I mean something that will load quickly over a modem connection or, in our client’s situation, an air card. We already had sales alignment maps created in the ESRI SHP format so the goal was to utilize them in the Flash environment to make displaying maps more interactive. This post will cover the basics of loading SHP files and rendering them with basic ActionScript code. No Flex components were used to keep the footprint small.

The first thing any developer might do is search the internet for someone who has already provided a solution. I was in luck and found a SHP file and DBF file loader. However it wasn’t well documented and there weren’t any examples so I had to do some trial and error to get it to work. In any case, here is the link to Edwin Vanrijkom’s code that I used to open the files.
http://code.google.com/p/vanrijkom-flashlibs/

ESRI SHP files are composed of two key files, first is the *.shp file which contains coordinate information and second is the *.dbf file which contains data that corresponds to the shapes. Loading the files is pretty straight forward:

/* load shp file */
var request:URLRequest = new URLRequest(fileName + ".shp");
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE,onShpLoaded);
loader.addEventListener(IOErrorEvent.IO_ERROR,onShpLoadFailed);
loader.load(request);
...
/* load dbf file */
var request:URLRequest = new URLRequest(fileName + ".dbf");
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE,onDbfLoaded);
loader.addEventListener(IOErrorEvent.IO_ERROR,onDbfLoadFailed);
loader.load(request);

Treat the returned data from these requests as BinaryArrays and you should be able to utilize Edwin’s code to parse it and create shape objects for each shape in the file:

/* container for all the new display objects created from SHP file data */
var shapeDisplayObjects:Array = new Array();

/* first extract the shp file header data */
var shp:ShpHeader = new ShpHeader(shpBinaryArray);
/* also extract the dbf file header data */
var dbf:DbfHeader = new DbfHeader(dbfBinaryArray);

/* next read all the shp records from the shp file data */
var shps:Array = ShpTools.readRecords(shpBinaryArray);

/* parse the shps and create a display object for each one */
for (var shapeItr:int=0; shapeItr<shps.length; shapeItr++)
{
	var shapeObject:ShapeObject = new ShapeObject();
	shapeObject.shape = shps[shapeItr].shape;
	shapeObject.data = DbfTools.getRecord(dbfBA, dbf, shapeItr);
	shapeDisplayObjects.push(shapeObject);
}

The details of the ShapeObject created for each shape above can be found in the link to the source at the bottom of this post. It has some basic functions implemented like mouse over events and tooltips. The only thing I will explain here is how to actually draw the shape using the data extracted from the SHP file.

Edwin’s code converts the data into ShpPolygons. Each ShpPolygon has a series of rings which make up the shape. The rings are composed of an array of ShpPoints which are coordinates in latitude and longitude. The coordinates have to be converted to pixel space before we render them, otherwise we will have trouble displaying maps in negative quadrants of the globe. Here is a simple function to translate them to a flat perspective. If you want to display your maps in 3D you’ll have to do some more internet searching :)

/**
 * converts lat/long coordinates to pixels based on shape MAGNIFICATION
 */
public static function latlonToXY( lon:Number, lat:Number ):Point
{
	var p:Point = new Point();
	p.x = (180+lon)*MAGNIFCATION;
	p.y = (90-lat)*MAGNIFCATION;
	return p;
}

Now we can draw the shape by iterating through its rings and converting each coordinate to XY pixel space.

var g:Graphics = graphics;
g.clear();

var location:Point = latlonToXY(bounds.x,bounds.y);
x = location.x;
y = location.y;

var rings:Array = shape.rings;
//loop through the rings
for (var ringItr:int = 0; ringItr < rings.length; ringItr++)
{
	// get the array of points for the ring
	var pntArray:Array = rings[ringItr];

	if (pntArray != null)
	{
		// start drawing the shape with the first point in the ring
		var shpPnt:ShpPoint = pntArray[0] as ShpPoint;
		var pnt:Point = latlonToXY(shpPnt.x,shpPnt.y);

		g.moveTo(pnt.x-location.x,pnt.y-location.y);
		g.lineStyle(1,
			(isMouseOver)?borderOverColor:borderColor,
			(isMouseOver)?borderOverAlpha:borderAlpha,
			false,LineScaleMode.NONE);
		g.beginFill((isMouseOver)?overColor:color, 1);

		//Loop through the remaining points in the ring
		for (var pntItr:int = 1; pntItr < pntArray.length; pntItr++)
		{
			shpPnt = pntArray[pntItr] as ShpPoint;
			pnt = latlonToXY(shpPnt.x,shpPnt.y);

			// draw a line to the next point
			g.lineTo(pnt.x-location.x,pnt.y-location.y);
		}

		g.endFill();
	}
}


So that sums up the basics of loading and rendering ESRI SHP Files. Download the source code to see how it all comes together and check out the demo below. The swf file size is only 12Kb so if you have small enough SHP files you are looking at a great low-bandwidth solution.





Christopher ActionScript, Flex

  1. No comments yet.
  1. No trackbacks yet.
You must be logged in to post a comment.