More Java Topology Suite.
So one of my colleagues whom is heavily involved in our current mapping project has been working with our polygons and trying to come up with some different solutions to make things faster, more scalable and workable. I networked with several people to try to find some fast easy workarounds and thanks to a couple of them pointing the way we were able to accomplish some things. With that, we loved the Java Topology Suite. Check out this post on the JTS and how we made things work.
http://webmonkeyswithlaserbeams.wordpress.com/2009/03/04/the-sweet-java-topology-suite-part-ii/
Enjoy
Adding and Removing the same POIs
So we have the situation where we will want to add and remove the same set of POIs on the map at any given time. One issue we are having with that is a problem with the AS3 function on the map called removePOI(poi). The function will remove it visually from the map, but the map still has the object reference in it’s memory. So if try to add the POI once it has been added then removed, the map will add it with the same key, as it should, but you will get an error if you try to remove it. I have not seen any fixes in the unsupported version for Flex either.
So to clerify:
myMap.removeShape(myMap.getByKey(shapeId));
AND
myMap.removePoi(Poi(myMap.getByKey(shapeId)));
will only visually remove the POI object from the map, not relationally (physically)
However, the function myMap.removeAllPois() will remove all of the POIs from the map in every way. The map no longer has a reference to them at all. That works out well, if you can remove all of the POIs. The problem comes in when you only need to remove a couple.
WORK AROUND:
if(showTypePoi())
{
if(myMap.getByKey(myPoi.id) == null)//if it is not on the map put it on
addPoi(myPoi);
else//map still has reference just show the object
Poi(myMap.getByKey(myPoi.id)).draw();
}
else//remove it from the map
{
myMap.removeShape(myMap.getByKey(myPoi.id));
}
Extending the TileMap & some components
So we had an issue arrise. Well we always have issues, but this time it was very needed. We have users that need to manipulate a group of polygon overlays all at one time. So we needed a fun little package to put them in. We added this cool new Object that just Extended the Polygon Overlay. This worked out handy to package them up, but then we needed a way to quickly work with them and add them to the map, move them off at the same time, or do other things of this nature as a collective group. So we Extended the TileMap. This was easy and all we did was to make a couple of functions that would take the New Object that we made and apply all the overlays that were in the object to the map. This worked out really well. So now we can add and remove them. But we also have another problem.
The tilemap has Overlays and they have a Key. This is good, but the key can be the same, and if you use the function getByKey, it would only return you one, of the possibly many items that were there. Not good for us, or anyone really, but we needed to fix this. Well since we are working with our own object, then we can go ahead and write it so we do something like this.
A new Shape Collection which has a function like this:
public function getMultipleByKey(key:String):Array
{
var a:Array = new Array();
var i:int = 0;
if(this.sc.get(0) is PolygonOverlay)
{
for(i=0; i<this.sc.getSize(); i++)
{
if(PolygonOverlay(this.sc.get(i)).getKey() == key)
a.push(PolygonOverlay(this.sc.get(i)));
}
}
if(this.sc.get(0) is Poi)
{
for(i=0; i<this.sc.getSize(); i++)
{
if(Poi(this.sc.get(i)).getKey() == key)
a.push(Poi(this.sc.get(i)));
}
}
return a
}
So in the case above, we can call this function and it will get us a shape collection of the multiple items that we wanted. You don’t have to extend the map to get this functionality, just the ShapeCollection. We extended a few of the common object we use readily. This is one of the many things that we have done. I will try to post some more about what we have done as i see the community might like to know.
Welcome to Cali
Well, I made it to Cali this weekend. I am hanging out at a starbucks right now just trying to get everything together. should be checking out Max tonight sometime. If you are at MAX come look around the MapQuest booth, i will probably be torchering the MapQuest guys. Hope you have a great week. I will try to check in a couple times while I am here and give some thoughts on how Max will help our program. Also check out the MapQuest booth as a small part of my project is on their demo site.
Learning how to draw.
Wow, this post is making me feel like i am back in kindergarten. Let’s learn how to draw, first take your pencil and a piece of paper an… well i tried that, now it’s hard to read the other stuff on the monitor! Ok, well so I have taught myself how to draw a line over the last couple of days and i have learned a couple of things. It is easy to draw a line, only took me about 8 hours to get the rough cut, then another 8 to tweak it a bit. I would suggest understanding it by first checking out the LineOverlay and the single line points.
STEP 1: Drawing a simple line on the screen
oh, I am not going to give you the code, just the advice like before, draw something simple, all you really need is two LatLng’s, LatLngCollection,IPointLLCollection and a LineOverlay. Ok i am a sucker, so here is a little help:
//first get LatLng1 & LatLng2 as LatLngs with accurate values
//then persue the rest of the following
var line:LineOverlay = new LineOverlay();
private var linePointCollection:LatLngCollection = new LatLngCollection();
linePointCollection.add(LatLng1);
linePointCollection.add(LatLng2);
var myIpColl:IPointLLCollection = linePointCollection;
line.setShapePoints(myIpColl);
myMap.addOverlay(line);
something like that should get your first version working on the map.
So let’s think about doing something a little more productive now. Let’s actually make an Object that will take in some information and do the heavy lifting and we can move the code off and really do some cool stuff with making a line.
Making the Object:
Let start out making our class
Package ActionScript
{
/*This object will be created to get you a Line to draw or display on the map the user can have a distance for the line. */
import com.mapquest.*;
import com.mapquest.tilemap.overlays.LineOverlay;
import com.mapquest.tilemap.pois.Poi;
import mx.collections.ArrayCollection;
public class LineMaker
{
So all of the stuff up there look familiar? Well I hope so, I will not spend time talking about it. Now lets make a couple private variables so we can have a start and end to the line
private var startPosition:Poi;
private var endPosition:Poi;
Please notice these are POI’s. This is a good way to start if you want MapQuest to get you some LatLng’s so you can put some simple ones one the board to start.
private var arrLinePoints:ArrayCollection = new ArrayCollection();
private var linePointCollection:LatLngCollection = new LatLngCollection();
private var line:LineOverlay = new LineOverlay();
private var totalDistance:Number = 0.0;
//…the end of the function would be down past all the cool code
Now we added the above to make sure we can insert LatLng’s at any point of the Object, get the total distance of the users line, and also to have the items to make a line.
Now you could add some public functions that would allow the user to create a line with two points, so you can do it quickly. Send a POI to strip out the LatLng into the ArrayCollection. Add a LatLng to the Array collection. That should be a good start.
Now you will need to also be able to make the lineOverlay, retrieve the line overlay, get the distance of the line, retrieve the distance of the line, and probably 1,000’s of others. Those are my basics for the LineObject. I also have a function that will keep changing the last items of the array so you can see what the calculations and placement of the new line would be. Ok, let’s get to the part where we put the line on the screen.
The following function can be implemented when a person clicks a draw button or whatever you like.
So there are some public functions like this.
public var myLine:LineObject = new LineObject();
public function doDraw():void
{
if( btnLine.label == ‘Draw Line’)
{
//we can add the click event to the map when they click the button
myMap.addEventListener(MouseEvent.CLICK, getAddPoint,true);
}
else
{
//we can remove our added listeners
myMap.removeEventListener(MouseEvent.CLICK, getAddPoint,true);
myMap.removeEventListener(MouseEvent.MOUSE_MOVE,tempLineDraw,true);
}
}
/*this function would add a point every time the mouse is clicked. It also adds an event to the map that will keep us up to date on where the user is pointing the mouse*/
public function getAddPoint(e:MouseEvent):void
{
var pntXY:PointXY = new PointXY(myMap.mouseX,myMap.mouseY);
var iPoint:IPointXY = pntXY;
var ll:LatLng = new LatLng(myMap.pixToLL(iPoint).lat,myMap.pixToLL(iPoint).lng)
myLine.addLatLng(ll);
if(myLine.getNumberPoints() > 1)
{
/*for this version we can just remove all other overlays, but you will want to point to this specific one, so it might be worth it to have your cool LineObject tell you it’s previous state.*/
myMap.removeAllOverlays();
myMap.addOverlay(myLine.getLine());
currentCenterOfMap = myMap.getCenter();
}
else
{
myMap.addEventListener(MouseEvent.MOUSE_MOVE,tempLineDraw,true);
}
}
/*this function tells us where the user is pointing the map and then it updates our cool little LineObject so it keeps the line information up to date.*/
public function tempLineDraw(e:MouseEvent):void
{
var pntXY:PointXY = new PointXY(myMap.mouseX,myMap.mouseY);
var iPoint:IPointXY = pntXY;
var ll:LatLng = new LatLng(myMap.pixToLL(iPoint).lat,myMap.pixToLL(iPoint).lng)
myLine.replaceLast(ll);
myMap.addOverlay(myLine.getLine());
}
So this should get your motor cranking and off to a good start drawing. I am still having a really hard time getting the line to follow the cursor. I have seen a great set of tools over at: http://sxsw.mapquest.com/Web_JavaScriptSamples/customcontrols.html and the rectangle, circle and line are exactly what I am looking to do in AS3 and Flex 3, but I am having a hard time like i said getting the line to follow. I might get it if i spend another few hours on it, but if you have a tip or suggestion about it, feel free to let me know.
How To Give the User A Choice of POI’s
Ok, so I have some code that I would love to share with you fellow AS3 developers. I have been playing over that last few days with some of the new features in the 5.3 version of MQAPI for AS3 and I made a custom mxml component. This is a very simple component, and will allow the users to see county and state of multiple POI’s that are returned from a GeoCode search. Of course if you want them to see something else, well you can change it, but at least this will give you a start.

So the first thing you will need is something that contains a locationCollection you could just do a search so you can load this thing up:
private function getChoice(lc:LocationCollection,pc:PoiCreator):int
{
var choicePanel:PoiChoices = new PoiChoices();
choicePanel.lc = lc;
choicePanel.parentMap = myMap.getTileMap();
Application.application.addChild(choicePanel);
}
this should open the panel and allow you to do what you need to do.
Breakdown of the code
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute” x=”50″ y=”5″ width=”400″ height=”300″ creationComplete=”init();”>
<mx:VBox id=”main” width=”100%” height=”100%” horizontalScrollPolicy=”off” verticalScrollPolicy=”off”>
<mx:VBox id=”choiceHolder” width=”100%” height=”100%” />
<mx:Button id=”closeChoices” click=”this.parent.removeChild(this);” label=”Close”/>
</mx:VBox>
<mx:Script>
<![CDATA[
//here we just import everything that we need to use
import com.mapquest.tilemap.ShapeCollection;
import com.mapquest.tilemap.TileMap;
import mx.core.Application;
import com.mapquest.tilemap.pois.Poi;
import mx.controls.CheckBox;
import ActionScript.PoiCreator;
import mx.controls.Alert;
import com.mapquest.*;
import com.mapquest.LocationCollection;
//we set a couple things so you can send in the information to build it
[Bindable] public var lc:LocationCollection = new LocationCollection();
//we need to pass a reference to the map so this thing can show the POIs on the map
[Bindable] public var parentMap:TileMap;
public var currentPoiChoice:Poi;
//public var blnPauseMouseOvers:Boolean = false;
//this function acutally makes all the items to build this guy, it loops over the collection and builds the items in code. this reduces possible erros
public function init():void
{
this.title = ‘Point Of Interest Choices (’+lc.getSize()+’)';
for(var i:int =0;i < lc.getSize();i++)
{
var myChk:CheckBox = new CheckBox();
myChk.label = GeoAddress(lc.get(i)).getCounty()+’/'+GeoAddress(lc.get(i)).getState();
myChk.name = i.toString();
choiceHolder.addChild(myChk);
//adding listeners so if the user hovers over the item, it shows on the map and removes when they leave it
myChk.addEventListener(MouseEvent.MOUSE_OVER,tempPlacePoi,false);
myChk.addEventListener(MouseEvent.MOUSE_OUT,removePOI,false);
//if the user checks/unchecks it, a function removes or keeps it displayed
myChk.addEventListener(MouseEvent.CLICK,checkPoi,false);
}
}
private function checkPoi(e:MouseEvent):void
{
if(CheckBox(e.currentTarget).selected)
{
CheckBox(e.currentTarget).removeEventListener(MouseEvent.MOUSE_OUT,removePOI,false);
currentChoice = int(e.currentTarget.name);
placePoi(currentChoice);
}
else
{
var GeoA:GeoAddress = lc.getAt(int(e.currentTarget.name));
var shpColMapPois:ShapeCollection = parentMap.getPois();
for(var i:int = 0;i < shpColMapPois.getSize();i++)
{
if(Poi(shpColMapPois.get(i)).getLatLng().lat == GeoA.getLatLng().lat && Poi(shpColMapPois.get(i)).getLatLng().lng == GeoA.getLatLng().lng)
{
parentMap.removePoi(Poi(shpColMapPois.get(i)));
}
}
CheckBox(e.currentTarget).addEventListener(MouseEvent.MOUSE_OUT,removePOI,false);
}
}
//this prepares to put it on the map on hover
private function tempPlacePoi(e:MouseEvent):void
{
currentChoice = int(e.currentTarget.name);
placePoi(currentChoice);
}
//this function acutally creates the POI it on the map
private function placePoi(intItem:int):void
{
//the PoiCreator is a AS3 object that i have made to display a POI with information you pass it.
var poiC:PoiCreator = new PoiCreator();
var GeoA:GeoAddress = lc.getAt(intItem);
poiC.createPoiGeoAddress(GeoA,this.addPoiToMap,null,’<br/>Address:<br/>’+GeoA.getStreet()+’<br/>’+GeoA.getCity()+’, ‘+GeoA.getState()+GeoA.getPostalCode());
}
//this function removes the item from the map when the user is no longer hovering over
private function removePOI(e:MouseEvent):void
{
parentMap.removePoi(currentPoiChoice);
}
//this actually adds the items to the map
public function addPoiToMap(p:Poi):void
{
parentMap.addPoi(p);
currentPoiChoice = p;
}
]]>
</mx:Script>
</mx:Panel>
The only other thing that you would need is my custom POI object, which is simple, it does the geoCoding for me if i need it, or it just makes the POI with information passed and returns the POI, you could just have something that does all that.
Back to Mapping, YAY!
Well now that phase 1 has been complete, it is time to start the second phase of the mapping application that I have been blogging about. So to do so, we have upgraded. Not only from the older mapquest 5.2 API to the new MapQuest 5.3 API but also to Flex 3 from Flex 2!! So today, I took my new flex 3 mapping project, empty canvas, and just threw the map on the screen. Now, I am not running the swiftest computer out there… although that is soon to change, but it does good at getting the job done. The first thing I noticed was how much faster the load time was to put the map on the screen. It improved enough that I noticed it before looking at the other version I have out there. I have to say, this next thing I am impressed with, I noticed that the map moved much more fluidly than it did before. Same machine, same setup, except now using Flex 3 and MQ 5.3. I don’t get that long loading grey screen of wait any longer, and those loading blocks have dwindled to few at the edges. Before, the whole screen flashed, now just a couple blocks!! Way to go MAPQUEST! This is exciting to me. My users will be able to get things much faster now than before. I will be exploring the next couple of days what I can do with the new setup. Thanks MapQuest for the new upgrades in performance. Even now, just the little I have seen with the map alone, I am excited.
6 month revist
OK, well i want to spend a little time talking about the project I just finished with making MapQuest Flash Maps in flex.
Project Refresher:
This project was to bring in demographics data and other informational data per zip code from internal systems, and using those choice zip codes to display zip code polygons on the map, somewhere between 50 and 2100 zip codes, effectively. The zip codes will be colored based on system or demographics data, whichever the user chooses, and displayed to the user like on might see a weather map on TV.
Project Hardships:
Multiple Polygons: Some of the hardships that were found early on while i was learning was displaying a large amount of zip codes. We found out that we were not able to ask for a certain set of zip codes from MapQuest, so we needed a way to get all of the ones that we wanted to see quickly. We determined that we should cache them on a local server so we can access them faster and more readily. The only problem was figuring out how to grab the object once it was cached. We soon found that using the Feature Collection XML for the code would be appropriate for the storage.
We started development with that in mind, and it worked well, but it still took a lot of time to get the XML across the wire. So we used gzip and ByteArrays. I should mention we have a Java Back-end that makes many calls to cache our data, and then we have a flex front end that uses our chached data. This data is taken and we had to use a XML processor to make the XML in JAVA, GZIP it and cache it until called. Once the call is made we bring it in flex, unzip it and write it out. This was fast but still took some time. To make it use less time we used the Generalize function, which would then remove some points and make the polygon smaller, but we started getting some serious skew through the straight edges where the zips cross tile lines. We found a cool project with called JTS(Java Topology Suite)and it would make a single polygon where polys share lines with one another (get rid of those pesky tile lines),so we iincorporated some of that idea in our project. That turned out beatuiful. We now can generalize with the JTS as well and get really nice results. We are still tweaking some things but all in all, incorporating their ideas really saved us.
Working with Multiple Polygons on the screen: I had to figure out how to manipulate the zip code polygons on the screen by changing their color, and their outline color as well. I found that instead of putting them just right on the map, if you use a OverLayCollection, you can just sift through that and change the ones you need. That works well.
Lack of Polygon Info: There are some things you just need to know about a polygon, like where it’s furthest edge points are. I needed to find the north-most, south-most, east-most and west-most points of a poly, i wrote a function to do that, you can see it below in the Find Edges of Polygon posting I did.
Silly programming naming: I do it to, programmers write names for functions that seem to make sense, and you really have to pay attention to the api, but if you want to get the XML for a object in Flex/Flash, you say object.saveXml(), and that will return the XML to you. If you want to load an object up with some XML just do object.loadXml(myXmlObj). Thinking back, that is really pretty clear, that is more of my error.
Not incorporating objects soon enough: I actually wrote object that deal with a POI. So if you have tons, instead of doing a batch of them, because they are not all the same probably, or if you have just one, you can fill this object up with an address, or something that resembles and address, and icon, and other information that you want the point to have and then let it go. It will put itself on the map when it is done. Also did the similar things to deal with the caching calls. That way i can break them up and I make one return before the system sends the next one out. Seems to work well.
Some sort of limiting factor: We had something that was going on and when we made a call to mapquest it would return partial XML, not sure why, but it would. We changed some of the specifics of the call(radius call) like the radius and the number of results, but that didn’t help too much. We were still a an impass with that. Having a way to handle it would have been great other than it crashing our systems.
Batch Geocoding: You can only do a hundred at a time, but i don’t really care now, i do them all one at a time. I can put about 350 on a screen in around a min. I wouldn’t want to do this with too many more, but 350 is abnormal for us. But 350 polygons isn’t, kinda funny.
Seriously Easy Tasks
Getting a map up and running is a breeze, just throw on the tilemap component and that is it.
GeoLocation calls are a breeze, getting that setup and working is also really easy. I would write a object to do it, one for batches that manages a group of the same type of objects too if that is needed.
TIPS
Connecting to their servers: When you connect to their server make sure you are using the right password and username. They are both listed in the account managment part of TRC. But make sure you use the Registry Passwordnot the password to log into TRC.
Getting the correct x and y position of the click on the map: myMap.mouseX,myMap.mouseY others will work, but only in certain situations, these work constantly.
Items under clicked spot: myMap.getObjectsUnderPoint(myPnt) as Array Use that to try to find the type of object you are looking for under your click. You might need to set a key or some sort of identifier to make sure that you can pick the object contents out of the lineup.
My Wishlist:
Mapquest accept a single call for a zip code. I can live without it now, but that would make life a lot easier. Especially when some zip codes are very large, like over 100 shape items, and take up a lot of resource power get get two that might be on different sides of the country.
MapQuest data implement JTS solution to get rid of tile lines, but i do that now too. It save a lot of time. Also let us pass in how generalized we might want to get the polys back and generalize them for us before return, that would save MapQuest gigs perhaps.
Lables for Polygons, like where you could see it blended into the polygon.
Summary
Spending the last 8 months working with MapQuest and 6 months on a project, i found that working with the product is really good. The support is out of this world, and they get back with you on a really quick basis. Also, I find the map to usually render in a relatively quick time. Getting the program tweaked to display the items quickly was a difficult 2 month task, i mean really, 2100 zip code polygons is a lot. Lots of exploration on our part, using gzip bringing it in with Java and out into Flex/Flash. Having the multiple API accessibility was great, we have a talented group in our office and they saved me some work, and i got to learn some Java in the midst of the project.
A new friend called JTS!
Well, we found something new today. This guy will actually do tons of work to clean up polygons. It is what PostGres GIS was modeled after and converted from and is a live project. Here is a link http://www.vividsolutions.com/jts/discussion.htm . As it is a Java app and i have some java backend stuff to do some pulling, it can easily be added into the project and serve up hot data, either on the fly or store it to something. Just a easy conversion to a different format polygon to join the polygons then convert them back and Whammo Bammo you got a untiled looking polygon. Great for on the go lookin good maps. I will post more on this later, just really excited and it works, even on multiple part zips.
Less expensive Polygons
As i grow to love this software more and more, i find different things that can take away from the user experience. I have found that putting polygons on the screen(mostly zip code polygons) is expensive if you use oh about 250 or more. I am working on ways around this and we are figuring some things out. One reason why a polygon is expensive is that in flash a polygon is simple correct… yes correct. Zip Code polygons are a little troublesome because of the number of points. They have great detail which is awesome when you gotta see where an exact measurement is located, but if you just need some round abouts, well, it hurts the client. By putting many of them on the screen at once your clients computers memory grows and grows. We are thinking that if we can remove about half the points, it should put them on the screen quicker, reduce the amount of memory the take up and provide the quickness that we are looking for. With that said, we are not completely sure about it. It is a good theory, but it has not been practiced to date, at least not by us.
So what i am looking to do is perhaps find a less precise data set, or rip up the current one in real time while at a certain zoom level. This should enable the XML to drop in size so the memory is not so full, and also kick up the coolness of the program. The biggest concern i have is leaving holes in the area. If i make the change to one, i have to find anyone that is using that same line and change his as well, so either my ripping algorithm must be killer, or i must be able to decipher who is using a common point. Please post your thoughts, knowledge or expertise about this topic. I am interested in hearing it. I hope that there are still some people paying attention.
-
Recent
- More Java Topology Suite.
- Adding and Removing the same POIs
- Extending the TileMap & some components
- Welcome to Cali
- Learning how to draw.
- How To Give the User A Choice of POI’s
- Back to Mapping, YAY!
- 6 month revist
- A new friend called JTS!
- Less expensive Polygons
- Find Edges of Polygon
- Distance and Bearing
-
Links
-
Archives
- March 2009 (1)
- January 2009 (2)
- November 2008 (1)
- September 2008 (1)
- August 2008 (2)
- May 2008 (1)
- April 2008 (1)
- March 2008 (1)
- February 2008 (2)
- January 2008 (1)
- December 2007 (5)
- October 2007 (2)
-
Categories
-
RSS
Entries RSS
Comments RSS