Yet Another GIS Blog
GIS, Geography, Programming, and Neogeography

Movie Links Visualizer

Thursday, 7 January 2010 15:57 by boxshapedwo

After spending the last month in java land in a perpetual state of frustration, I needed a break, and went back to the blissful world of Flex.  I even toyed around with JavaFX.  The only reason I can see that taking off, is because of the multitude of open source Java libraries available.  Anyway, this isn't about Java.

 I needed to have a confidence booster and have a little fun, so I decided to take on a little project.  I am a big movie fan, and sometimes I like to find out if an actor (I'm using actor as a gender neutral for both men and women) as acted any movies with another actor.  There wasn't a real easy way to do this on IMDB so I thought why not create my own.  Unfortunately, IMDB does not have an API to pull movie and actor names from, but I did find the free movie database.  It's free for my purposes at least, since this was just a pet project.  To display the links and movies, I used the FLARE library's force directed layout.  I suppose one of the other layouts would also work.  Because of the ease of use of Flare and FLEX I was up and running in a day and half, and most of that half was tweaking to get the different things to work right.  I know the user interface is a bit crappy, but I was mostly interested in the back end and having something work.

Currently under "beta", and perhaps perpetually so.  Here are some quick instructions.

You can search for just one actor to see their movies by just entering the name in the first box and clicking find.  You can search for both actors and find their links and all their movies, or check the checkbox to just see the links.  Double click on the box to get more info about the movie, and double click on the actor to get more info about the actor.  Hover over the items to find the name of the movie or actor.  You can hover over the link to find out what job they had (actor, producer, etc...), but this doesn't list all the jobs, hence why this is still under beta.  Pause and Resume buttons will pause the layout from moving the whole time.

The app is available here, and the source code is available here.  You will need your own API key though.

Tags:   ,
Categories:   Programming | Visualizations
Actions:   E-mail | del.icio.us | Permalink | Comments (25) | Comment RSSRSS comment feed

Shapefiles, Actionscript 3.0, and Google Maps

Tuesday, 28 April 2009 23:10 by boxshapedwo

 I'm working an Adobe AIR application and I wanted to be able to have the user select a shapefile, and then parse it to create a KML file.  I didn't want to have the user be responsible for creating a KML file.  I thought I might try and crack the shapefile enigma since it is a well documented format, but that would have taken time and I suddenly realized I'm not actually a developer :).  Instead, I found this set of Actionscript Classes to parse a shapefile in Flash.  Unfortunately, I didn't find a very good tutorial on how to work with the classes.  The example is a little confusing (at least for me) and also uses a far file.  I'd never heard of far compressed files.  So I took the classes and created my own parser.  I thought I would post a tutorial on how to use these shapefile classes in conjunction with AIR and the Google Maps API for flash.  This technique would work with flex as well, I just didn't want to have to write the code to upload a file.  I presume a few things with this.  The shapefile you are using for this should already have a geographic projection (e.g. latitude and longitude Geographic NAD 83).  In order to use the Google Maps API with AIR, you need a URL with a key associated with it.  Below are two zipfiles available for download.  The testfile.zip is the shapefile I was using.  The vanrikom.zip is the downloaded actionscript classes from the Google Code repository.  I had trouble downloading the using an svn so I did it manually.  I'll save you the time by making it available here...unless the original author asks me to remove them.  There are parts that I find confusing with the way the reader was set up.  For some reason polyline inherits from polygon.  Intuitively to me it should be the other way around...but like I said, I'm not a developer.

This was all done using FlashDevelop and the Flex SDK 3.  There are 4 custom classes in addition to the mxml file.  Each are shown here.

This is what the Main.mxml file looks like.  It should be relatively straight forward if you are familiar with mxml.  The map component comes from the Google Maps API swc that you should download from the above link.  I have added comments so hopefully the code is explanatory.  I apologize for how crappy this looks, but the formatting doesn't work to well with .net blog engine.

<?xml version="1.0"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:maps="com.google.maps.*">
    <mx:Script>
        <![CDATA[
  
        import com.google.maps.LatLng;
        import com.google.maps.LatLngBounds;
        import com.google.maps.Map;
        import com.google.maps.MapEvent;
        import com.google.maps.MapType;
        import org.bsw.flex.shapeParse;
        import org.bsw.flex.polylineClass;
        import com.google.maps.overlays.Polyline;
       
       
        private function onMapReady(event:Event):void {

            //This is a custom class that parses the shapefile. The constructor takes three arguments.  The path, the filename, and the name of a field

           //I don't cover attributes in this example but show how to access them

            var parser:shapeParse = new shapeParse("C:\\Workspace", "testfile", "Id");

            if (parser.pc != null)
            {

                //I'll explain this one later

                var bnds:LatLngBounds = parser.pc.getBounds.latlongBounds;
               

                //I use the bounds of the shapefile to set the extents and the zoom

                this.map.setCenter(bnds.getCenter());
                this.map.setZoom(map.getBoundsZoomLevel(bnds));
                this.map.enableScrollWheelZoom();
                this.map.continuousZoomEnabled();
               
                for (var i:int = 0; i < parser.pc.length; i++)
                {
                    var p:polylineClass = parser.pc.getPolylineAtIndex(i);
                    var gp:Polyline = p.gmapPolyline();
                    map.addOverlay(gp);
                }   
            }

        }
        ]]>
    </mx:Script>
    <mx:Canvas width="100%" height="100%">
        <maps:Map id="map" mapevent_mapready="onMapReady(event)"
            width="100%" height="100%" key="your key" url="your url"/>       
    </mx:Canvas>
</mx:WindowedApplication>

shapeParse.as

package org.bsw.flex
{
    import flash.geom.Point;
    import flash.utils.ByteArray;
    import mx.collections.ArrayCollection;
    //be sure to import these
    import org.vanrijkom.shp.*;
    import org.vanrijkom.dbf.*;
    //I think these are only available in AIR
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    /**
     * ...
     * @author dsl
     */
    public class shapeParse
    {
        private var filePath:String;
        private var fileName:String;
        private var idFieldName:String;
       
        //Custom classes
        public var pc:polylineCollection = null;
        public var shapeBounds:boundingBox;
       
        public function shapeParse(filePath:String, fileName:String, idFieldName:String)
        {
            this.fileName = fileName;
            this.filePath = filePath;
            this.idFieldName = idFieldName;
            init();
        }
       
        private function init():void
        {
            //get access to the two files *.shp and *.dbf
            var shpfile:File = File.desktopDirectory.resolvePath(filePath + "\\" + fileName + ".shp")
            var dbffile:File = File.desktopDirectory.resolvePath(filePath + "\\" + fileName + ".dbf")

            var shpFS:FileStream = new FileStream;
            var dbfFS:FileStream = new FileStream;
           
            //this is the key to using the shapefile classes.  You need a bytearray to send to the
            //shpheader class
           
            var shpBA:ByteArray = new ByteArray;
            var dbfBA:ByteArray = new ByteArray;
            try {
                shpFS.open(shpfile, FileMode.READ);
                dbfFS.open(dbffile, FileMode.READ);
                shpFS.readBytes(shpBA);
                dbfFS.readBytes(dbfBA);
                dbfFS.close();
                shpFS.close();
            }catch (e:Error)
            {
                trace(e.message);
            }
           
            //Shapeheader has the base information
            var shp:ShpHeader = new ShpHeader(shpBA);
            var dbf:DbfHeader = new DbfHeader(dbfBA);
           
            //Check what type of shapefile it is.  I only worked with polylines.
            if (shp.shapeType == ShpType.SHAPE_POLYLINE);
            {
                //custom class
                pc = new polylineCollection();
               
                //the boundsXY property for the shapeheader class does not seem to function properly
                //shapeBounds = new boundingBox(shp.boundsXY);
               
                //To good all the records in the shapefile you need an array, and then use the ShpTools class
                var polyArray:Array = ShpTools.readRecords(shpBA);
                //loop through the records
                for (var iPoly:int = 0; iPoly < polyArray.length; iPoly++)
                {
                    //custom polylineclass
                    var internalPoly:polylineClass = new polylineClass
                    //from the vanrijkom classes, create a shpPolyline from the array item
                    var poly:ShpPolyline = polyArray[iPoly].shape as ShpPolyline;
                    //the polyline contains an array of "rings"  These are the actual polylines.
                    var ring:Array = poly.rings;
                    //loop through the rings
                    for (var iRing:int = 0; iRing < ring.length; iRing++)
                    {
                        //get the point collection from the rings
                        //(e.g. all the points that make up the line
                        //segments that make up the polyline)
                        var pntArray:Array = ring[iRing];
                       
                        if (pntArray != null)
                        {
                            //Loop through to get all the points
                            for (var j:int = 0; j < pntArray.length; j++)
                            {
                                //Access shpPoint
                                var pnt:ShpPoint = ShpPoint(pntArray[j]);
                                //trace("X " + pnt.x + " Y " + pnt.y);
                                //Add the points to polylineClass
                                internalPoly.addPoint(new Point(pnt.x, pnt.y));
                            }
                        }
                    }
                    pc.addPolyline(internalPoly);
                    //This is how to access the attributes.
                    var dr:DbfRecord = DbfTools.getRecord(dbfBA, dbf, iPoly);
                    var xsID:String = dr.values[idFieldName];
                    trace(xsID);
                }
            }
           
            //place holders to work with points
            if (shp.shapeType == ShpType.SHAPE_POINT)
            {
               
            }
            //place holder to work with polygons
            if (shp.shapeType == ShpType.SHAPE_POLYGON)
            {
               
            }
           
        }
       
    }
   
}

polylineClass.as

package org.bsw.flex
{
    import com.google.maps.LatLng;
    import com.google.maps.overlays.Polyline;
    import com.google.maps.overlays.PolylineOptions;
    import flash.geom.Point
    import flash.geom.Rectangle;
    import flash.sampler.NewObjectSample;
    import mx.collections.ArrayCollection;
   
    /**
     * ...
     * @author ACE
     */
    public class polylineClass
    {
        //collection of points that make the polyline
        private var pointColl:ArrayCollection;
       
        public function polylineClass()
        {
            pointColl = new ArrayCollection;
        }
       
        //add points to the collection
        public function addPoint(pnt:Point):void
        {
            pointColl.addItem(pnt);
        }
       
        //start point of the polyline
        public function get StartPoint():Point
        {
            return Point(pointColl[0]);
        }
       
        //endpoint of the line
        public function get EndPoint():Point
        {
            return Point(pointColl[pointColl.length - 1]);
        }
       
        public function get Length():Number
        {
            var runningTotal:Number = 0;
            for (var i:int; i < pointColl.length; i++)
            {
                var pnt1:Point = pointColl[i];
                var pnt2:Point = pointColl[i + 1];
                var dX:Number = pnt1.x - pnt2.x;
                var dY:Number = pnt1.y - pnt2.y;
                var dist:Number = Math.sqrt((dX * dX) + (dY * dY))
                runningTotal += dist;
            }
            return runningTotal;
        }
       
        //this returns a polyline that works with the google maps api
        public function gmapPolyline(pOpt:PolylineOptions = null):Polyline
        {
            //default polyline style...setup through polylineoptions
            if (pOpt == null)
            {
                pOpt = new PolylineOptions({
                    strokeStyle: {
                        thickness: 2,
                        color: 0x123456,
                        alpha: 1,
                        pixelHinting: true
                    }
                    });
            }

            //a polyline uses an array of LatLng (from google maps api)
            var latlngArray:Array = new Array;
           
            for (var i:int; i < pointColl.length; i++)
            {
                var pnt:Point = Point(pointColl[i]);
                var ll:LatLng = new LatLng(pnt.y, pnt.x);
                latlngArray.push(ll);
            }
            //create and return the polyline (gmaps) type
            var poly:Polyline = new Polyline(latlngArray, pOpt);
            return poly;
        }
       
        //Found the boundxy from vanrijkom classes was inaccurate, so I created my own bounding box
        //this creates the bounding box
        public function get Bounds():boundingBox
        {
            var right:Number = -(Math.pow(10,10));
            var left:Number = Math.pow(10,10);
            var top:Number = -(Math.pow(10,10));
            var bottom:Number = Math.pow(10,10);
            for (var i:int; i < pointColl.length; i++)
            {
                var pnt:Point = Point(pointColl[i]);
                if (pnt.x < left)
                {
                    left = pnt.x;
                }
                if (pnt.y < bottom)
                {
                    bottom = pnt.y;
                }
                if (pnt.y > top)
                {
                    top = pnt.y;
                }
                if (pnt.x > right)
                {
                    right = pnt.x;
                }
            }
           
            var bnds:boundingBox = new boundingBox(left, right, top, bottom)
            return bnds;
        }
    }
   
}

polylinecollection.as

package org.bsw.flex
{
    import mx.collections.ArrayCollection;
    import flash.geom.Rectangle;
    /**
     * ...
     * @author dsl
     */
    public class polylineCollection
    {
        //stores a collection of PolylineClasses
        private var coll:ArrayCollection;
       
        public function polylineCollection()
        {
            coll = new ArrayCollection;
        }
       
        public function addPolyline(poly:polylineClass):void
        {
            coll.addItem(poly);
        }
       
        public function getPolylineAtIndex(i:int):polylineClass
        {
            return polylineClass(coll[i]);
        }
       
        public function get length():Number
        {
            return coll.length;
        }
       
        //calculates the bounds for all the polylines in the collection
        public function get getBounds():boundingBox
        {
            var right:Number = -(Math.pow(10,10));
            var left:Number = Math.pow(10,10);
            var top:Number = -(Math.pow(10,10));
            var bottom:Number = Math.pow(10, 10);
           
            for (var i:int = 0; i < coll.length; i++)
            {
                var cP:polylineClass = polylineClass(coll[i]);
                var bnds:boundingBox = cP.Bounds;
                if (bnds.left < left)
                {
                    left = bnds.left;
                }
                if (bnds.bottom < bottom)
                {
                    bottom = bnds.bottom;
                }
                if (bnds.top > top)
                {
                    top = bnds.top;
                }
                if (bnds.right > right)
                {
                    right = bnds.right;
                }
            }
            //var rect:Rectangle = new Rectangle(left, top, left - right, top - bottom)
            var newbnds:boundingBox = new boundingBox(left, right, top, bottom)
            return newbnds;
        }
       
    }
   
}

boundingbox.as

package org.bsw.flex
{
    import com.google.maps.LatLngBounds;
    import flash.geom.Rectangle;
    import com.google.maps.LatLng;
   
    /**
     * ...
     * @author dsl
     * */
    public class boundingBox
    {
        public var left:Number = 0;
        public var right:Number = 0;
        public var top:Number = 0;
        public var bottom:Number = 0;
       
        //Apparently there is no constructor overloads for AS3
        public function boundingBox(left:Number, right:Number, top:Number, bottom:Number)
        {
            this.left = left;
            this.right = right;
            this.top = top;
            this.bottom = bottom;
        }
       
        //public function boundingBox(rect:Rectangle)
        //{
            //this.left = rect.left
            //this.right = rect.right;
            //this.top = rect.top;
            //this.bottom = rect.bottom;

        //}
       
        //returns a LatLngBounds from the google maps api
        public function get latlongBounds():LatLngBounds
        {
            var bounds:LatLngBounds = new LatLngBounds(
                    new LatLng(bottom, left),
                    new LatLng(top, right));
            return bounds;
        }
    }
   
}

Here is the flashdevelop project:

ShapeReaderAIRTest.zip (936.60 kb)

vanrijkom.zip (17.38 kb)

testfile.zip (1.33 kb)

This is what it should look like.


Flex Label Component Rotation and TextField Rotation

Saturday, 25 April 2009 11:43 by boxshapedwo

I have had some frustrations trying to get a label to rotate 90 degrees in Flex, both as an mxml component and in Actionscript.  The key to this problem has to do with embedding fonts.  The easiest way to embed a font in flex is to use a style component. 


<mx:Style>
@font-face {
src:local("Times New Roman");
fontFamily: myTNR;
advancedAntiAliasing: true;
}
</mx:Style>

The style component is essentially CSS, so you can store the style.  Now when you add a label component to your mxml then you can set the fontFamily property equal to myTNR.

 <mx:Label text="Label" rotation="90" fontFamily="myTNR"/> 

What happens though when you are working with an Actionscript class and not with an mxml file?  In your main mxml file, add the same style component.  In your Actionscript class add a variable before your label variable of the type TextFormat.

var fmt:TextFormat = new TextFormat;
fmt.font = "myTNR"; 
fmt.size = 11; 
//This should be true  if it worked.
trace(Application.application.systemManager.isFontFaceEmbedded(fmt);
var lbl:Label = new TextField; //using a textfield variable
lbl.defaultTextFormat = fmt;
lbl.embedFonts = true;
lbl.text = "some text"; 
lbl.x = 0;
lbl.y = 0; 
//add the textfield as a child to some component 
addChild(lbl);
//then rotate 
lable.rotation = -90;

There are other ways to embed fonts, but I found this to be the easiest way.

Tags:   , ,
Categories:   Neogeography
Actions:   E-mail | del.icio.us | Permalink | Comments (5) | Comment RSSRSS comment feed

Venturing deeper in to the murky waters of the Geoweb...

Tuesday, 27 January 2009 14:19 by boxshapedwo
or Neogeography...or Web 2.0...or whatever you want to call it...

Basically to keep up with the ever changing world of the GIS Analyst/Geographer, I've set upon myself the tiny minor little task of learning how to create Rich Internet Applications (RIA). I already know a little about AJAX, and have used it for small things. For those that don't know what AJAX is, it stands for Asynchronous Javascript and XML. Basically it is a way for a webpage to stay put, and the content to change. Whereas before to change the content you had to go to another webpage, this really slows things down, and isn't a good user experience. So AJAX talks to the server via Javascript and the server sends back information in XML format, and Javascript parses that info. The advantage of this process is that doesn't require any third-party extensions to get a Flashy (Adobe Flash that is) experience. I don't particularly like programming in Javascript. And in some instances you have to set up your code depending on browser the user is using, because not each browser is the necessarily the same.

So along comes Microsoft and their new product called Silverlight. The so-called "Flash-Killer." Well, it's not going to kill Flash, but some healthy competition doesn't hurt anyone. The appeal was that I could program in VB.NET to create the RIA, and it could all be developed using the Web Developer 2008 Express Edition. Develop Flash-like apps for free? Sounds better than 600$ for the cost of Adobe Flash software. The disadvantage is that Silverlight has 1% of the market to Flash's 98% (and an extra 1% for all the people still using Netscape Navigator 4), so no one really has the plugin to view the application. Forcing people to download a plug-in leaves me with a bad taste in my mouth (hence the appeal of AJAX). Granted by the time everyone upgrades to Windows 7, Silverlight will be pre-installed.

Now I'm not just doing this for fun, but it is related to an actual project. The project lead has a Mac, and Silverlight plugins aren't available for the mac (they are now). So I switched my attention to Adobe Flash, and learned about Adobe Flex. Flex is for developers (or dabblers like me) and Flash is for designers, to put it succinctly. There is a lot of overlap between them, such as the main language - ActionScript. Also, Flex 3.0 SDK is free, so you can, in theory, create Flex apps for free. They also sell Flex Builder (an IDE), or as a 60 day trial. Since I'm at an academic institution, they also provide it for free.

I was a little daunted at trying to learn another programming language (or half-learning like I usually do), but I've been pleasantly surprised by the whole experience. Flex Builder is just ok as an IDE, I much prefer working in Web Developer Express (it has much better intellisense), but I'm also used to working in it. I think that in general Flex is easier to work with than Silverlight because it has the documentation and community support/examples that Silverlight doesn't (yet?). I have a copy of Adobe Flex 3 Bible by Gassner and that is helping quite a bit. Flex also has a lot of built-in functions to make typical tasks much easier, like fading in or out. I found Silverlight's animation less intuitive. An extra bonus is that Google released a Google Maps control for flash/flex. There is a Virtual Earth wrapper for Silverlight that is really good to work with, but Google Maps has better imagery in the project area.

Anyway, if you've wanted to start working with RIA's, I think I would recommend Flex first, then AJAX, then Silverlight.

Here is a beta version of the project I've been working on. I attempted to set up the Flex front end to be generic so that all the content is received via RSS, so in theory I could just switch urls to a different RSS that read a different database. Of course, the code is a little sloppy as it was a learner project, but I should be able to clean it up for the final incarnation. There are little buggy problems with this, but I wanted to get a test up on the web. All the markers are stored as geometry in a SQL Server 2008 database and put into a GeoRSS so that it's readable by Flex. The "media" is stored in a separate table with each item using a foreign key reference to the spatial data, so that they are "geotagged." This way, as the database is updated so is the little Flex program. In the beta version of the blogspot engine, they have geotagging, so hopefully I'll be able to add blog entries as media content as well. I could try that, but then would need to add the entries to the media content table. So far images and videos are stored in a bucket on my host's server, with the link stored in the table. Any feedback or suggestions would be appreciated.