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.  ;)

Find Edges of Polygon

Ok so over the last couple of days we have come up with a solution to grab information to do some caching.  One of the things that I am doing is figuring out where i need to move to grab the next set of data.  Well i wrote a little something i will post as a comment in a bit that will give the coords of Northern, Southern, Eastern and Western most portions of the zip code.

With this now i will be able to do a little math a populate a center point for my zip codes that is not listed in my feature collection.  I am sure there is more that one could do as well.

     getting bounds of zip

//this function will get you the most nortern, southern, eastern and western lat/lng of the feature collection you pass it and will also give you the center 

public function findBoundsPointsAndCenter(fc:FeatureCollection):LatLngCollection
{
//get init value so we can have
     var TempLLC:LatLngCollection = PolygonFeature(fc.getAt(0)).getLatLngs();
     var WestLatLng:LatLng = new LatLng(0,0);
     var EastLatLng:LatLng = new LatLng(0,0);
     var NorthLatLng:LatLng = new LatLng(0,0);
     var SouthLatLng:LatLng = new LatLng(0,0);
//going to fill them with values because they didn’t set right above for some reason??
     NorthLatLng = LatLng(TempLLC.getAt(0));
     SouthLatLng = LatLng(TempLLC.getAt(0));
     WestLatLng = LatLng(TempLLC.getAt(0));
     EastLatLng = LatLng(TempLLC.getAt(0));
//loop through each poly and find most extreme point
     for(var a:int = 0;a < fc.getSize();a++)
     {
          var PolyLLC:LatLngCollection = PolygonFeature(fc.getAt(a)).getLatLngs();
          for(var b:int = 0; b < PolyLLC.getSize();b++)
          {
               if(LatLng(PolyLLC.getAt(b)).lat > NorthLatLng.lat)
                    NorthLatLng = LatLng(PolyLLC.getAt(b));
               if(LatLng(PolyLLC.getAt(b)).lat < SouthLatLng.lat)
                    SouthLatLng = LatLng(PolyLLC.getAt(b));
               if(LatLng(PolyLLC.getAt(b)).lng > EastLatLng.lng)
                    EastLatLng = LatLng(PolyLLC.getAt(b));
               if(LatLng(PolyLLC.getAt(b)).lng < WestLatLng.lng)
                    WestLatLng = LatLng(PolyLLC.getAt(b));
               }
          }
//get the Average Lat Lng which is the amount of variance + lat or lng
var AvgLat:Number = EastLatLng.lat + ((WestLatLng.lat – EastLatLng.lat)/2);
var AvgLng:Number = NorthLatLng.lng + ((SouthLatLng.lng – NorthLatLng.lng )/2);
var centerLatLng:LatLng = new LatLng(AvgLat,AvgLng);
var FourEdgePointsLLC:LatLngCollection = new LatLngCollection;
FourEdgePointsLLC.add(NorthLatLng);
FourEdgePointsLLC.add(SouthLatLng);
FourEdgePointsLLC.add(WestLatLng);
FourEdgePointsLLC.add(EastLatLng);
//throw the info on the screen to make sure it works
var CenterPOI:Poi = new Poi(centerLatLng);
var NorthPOI:Poi = new Poi(NorthLatLng);
var SouthPOI:Poi = new Poi(SouthLatLng);
var EastPOI:Poi = new Poi(EastLatLng);
var WestPOI:Poi = new Poi(WestLatLng);
NorthPOI.setLabel(
‘North’);
SouthPOI.setLabel(
‘South’);
EastPOI.setLabel(
‘East’);
WestPOI.setLabel(
‘West’);
myMap.addPoi(NorthPOI);
myMap.addPoi(SouthPOI);
myMap.addPoi(EastPOI);
myMap.addPoi(WestPOI);
myMap.addPoi(CenterPOI);
return FourEdgePointsLLC;
}

Distance and Bearing

Something i just found that i would love to have is for my LatLng to give me another LatLng with taking distance and bearing(by degree).  This would be an awesome addition to be able to draw other features, but for now i will have to settle for doing the calcs myself.  I am now looking up the functions in Flex to see if i can, but here is the formula as i got from http://www.movable-type.co.uk/scripts/latlong.html

 Formula:   

lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(brng))
    

lon2 = lon1 + atan2(sin(brng)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))

A lesson learned

OK, today was one of the most awesome days since my mapQuest experience has begun.  Thanks to Ant who has posted comments here a few times i was able to speak with Kevin who has intimate knowledge of the system and was informed about how they do things.  I was going in a good direction with caching( using XML ) but when I want to Hydrate a MqObject i can just say

MqObject:MqObject = new MqObject();
MqObject.loadXML(myXML);

BAM!  there is your object all ready to go.  * not actual code i used

That right there is pretty now.  I thought i had tried this once before but not yielding the results I had hoped, evidentally not using the right syntax or thought of the object so i had wrote a convaluted process to hydrate it, “Took the long Road”.   

So if we cache the XML which is how it was suggested to me to do, it will be easy to create, and wow it is.  I save a ton of time now with the way we are doing things.  Just gotta make the caching utility i need now. 

Kudos to Ant and Kevin.  Ant is awesome for taking care of me.  In my company that is something to be expected of each and every one of us.  Exceptional customer service even if you are not someone who usually does customer service.  Not sure if you have read the QBQ book or not but that is required reading in my company and it was definatly used this fine day.  If you wanna check out the book here it is.  http://www.qbq.com/

Thanks Hope you all have a great night!

Good programming practice with mapquest fuctions & Freebies (Merry Christmas)

So when i started writing the mapquest functions i have, i wrote the same ones over and over again until i got familiar.  I the broke out my own file and just called it mapQuest something or other and put all my mapquest functions in it.  Once i did that I made it where you can pass in listener functions to the functions that way i am not tied down to one particular function.  I am sure this Loose Coupling idea has gone through all your heads, but how about this.  I am gonna give you some code that can do some cool stuff for you.

 Here are some that i will share, perhaps later i will give you some cooler ones:

public function configGeocodeExecObject():Exec
{
 var exec:Exec = new Exec(“geocode.access.mapquest.com”, MQServerPath, MQServerPort);
 exec.addEventListener(Exec.EVENT_DO_TRANSACTION_ERROR, onSearchError);
 exec.setClientId(MQServerClientId);
 exec.setPassword(MQServerPassword);
 return exec;
}
//This function will configure an EXEC object for the map server of Mapquest
public function configMapExecObject():Exec
{
 var exec:Exec = new Exec(“map.access.mapquest.com”, MQServerPath, MQServerPort);
 exec.addEventListener(Exec.EVENT_DO_TRANSACTION_ERROR, onSearchError);
 exec.setClientId(MQServerClientId);
 exec.setPassword(MQServerPassword);
 return exec;
}

public function geocodeZipCode(zipcode:String,listenerEvent:Function):void 
{
  var address:Address = new Address();
 address.setPostalCode(zipcode);
 address.setCountry(‘US’);
 var exec:Exec = configGeocodeExecObject();
 exec.addEventListener(Exec.TRANS_TYPE_GEOCODE, listenerEvent);
 exec.addEventListener(Exec.EVENT_DO_TRANSACTION_ERROR, onSearchError);
 exec.geocode(address);
}

/*This is the function call to make the call to the function below.  Make sure that you call the mouseX and mouseY position items because if you don’t then you are gonna have issues with getting the wrong zip code show up.  I worked on this problem for over 5 days.  There are about 4 different X/Y position items you can use but the only proper one for the whole map is the mouseX/mouseY.  The event.x or something like that works on just the polygons spots but not the non-poly spots.
 reverseGeoXnYPoints(myMap.mouseX,myMap.mouseY,getZipInfo,onSearchError);*/
 
//this function will take two x y points and hit mapquest to find out what zip code the location belongs.
public function reverseGeoXnYPoints(x:Number,y:Number,listenerOkEvent:Function,listenerFailEvent:Function):void
{
 var myPnt: IPointLL = myMap.pixToLL(new PointXY(x,y));
 var myLatLng: LatLng = new LatLng();
 myLatLng.setLatLng(myPnt.lat,myPnt.lng);
 var exec:Exec = configMapExecObject();
 exec.addEventListener(Exec.TRANS_TYPE_REVERSE_GEOCODE, listenerOkEvent);
 exec.addEventListener(Exec.EVENT_DO_TRANSACTION_ERROR, listenerFailEvent);
 exec.reverseGeocode(myLatLng, “tana”);
}
//the following function will parse an XML object that pertains
//to a FeatureCollection and make a featureCollection so you can then put it on your map
private function makeFc(newXML:XML):FeatureCollection
{
 var fc:FeatureCollection = new FeatureCollection;
 var fltFeatureCount:Number = Number(newXML.attribute(‘Count’));
 //the following will make the object for each polygon portion of the feature collection
 for(var a:int = 0;a < fltFeatureCount;a++)
 {
  var FeatureItem:XML = newXML.PolygonFeature[a];
  var llc:LatLngCollection = new LatLngCollection();
  var formatStyleLat:Number = Number(.000001);
  var formatStyleLng:Number = Number(.000001);
  var j:Number = Number(FeatureItem.LatLngs.attribute(‘Count’));
  var BaseLatLng:LatLng = new LatLng();
  BaseLatLng.setLatLng(Number(FeatureItem.LatLngs.Lat[0])*formatStyleLat,Number(FeatureItem.LatLngs.Lng[0])*formatStyleLng);
  llc.add(BaseLatLng);
  for(var i:Number = 1;i<j;i++)
  {
   llc.add(revDeltas(llc.getAt(i-1),Number(FeatureItem.LatLngs.Lat[i])*formatStyleLat,Number(FeatureItem.LatLngs.Lng[i])*formatStyleLng));
  }
  var pf:PolygonFeature = new PolygonFeature;
  llc.loadXml(llc.saveXml());
  pf.setLatLngs(llc);
  pf.loadXml(pf.saveXml());
  fc.add(pf);
 }
 fc.loadXml(fc.saveXml());
 return fc;
}
//This function will return you a latlng from the deltaLatLng items
private function revDeltas(baseLatLng:LatLng,deltaLat:Number,deltaLng:Number):LatLng
{
 var retLatLng:LatLng = new LatLng();
 retLatLng.setLatLng(deltaLat + baseLatLng.getLatitude(),deltaLng + baseLatLng.getLongitude());
 return retLatLng;
}

Polygons and labels

So there is more to report when it comes to throwing Poly’s on the screen.  Well, it looks like there is not a great way to do labels for your poly’s which stinks, but there are work arounds i am sure.  I posted something on this in the forums on MapQuest site with a response which will be the first solution since someone actually did it.

 What I would like:  I would love to have a label attribute for a polygon overlay, or any type of overlay on a map, and if that label attribute is assigned a value, it should be shown on the screen.  I understand there is a lot to think about, colors and what not, but that is what i am looking for.

Solution 1:  Find a point, preferably the center of the poly, throw a POI on that point with an invisible image and give it a label.  Poof you are done.  Easy enough and POI’s go on quick. 

Possible solution to a crazy guy:  So i was dreaming of a possible solution and thought of this one.  What it i gathered all the points under the polygon, and threw in a text item on top of that array of items, i am sure it problably would not work, but it was a thought i guess.  POI’s would probably go much faster.

Back to school on Polygons and map placement, (HI OverlayCollection)

Ok, so i am back to working on the UI for the map so my customers can really be effective with the way it sits.  Until today whenever i put a polygon on the screen i just wrote it straight to the map, so something like

myMap.addOverlay(myOverlay);

This is good and all, but here is the problem.  If you were to have more than one overlay that was realted and you wanted to manipulate it, you would have an extreamly hard time with it written directly to the map.  Try this proposal. 

Use an overlayCollection(olc)!  This is the coolest thing because now you have all the polygons that relate to one another, you can just throw them in this thing, make sure you assign a key to all of them and now whenever you want to manupluate the way one looks just loop through the overlayCollection, find the key of the item that you are looking for and make the change, then that is it.  It will write it out on the screen for you. 

 One issue you might encounter is if your polygons share a border, if so the last border laid goes on top. 

  • solution 1:  remove the polygon and add it back, this will happen after the map redraws the polys initially and your borders will appear all cool and such, here is the code:
    myMap.removeOverlay(PolygonOverlay(olc.get(a)));
    myMap.addOverlay(PolygonOverlay(olc.
    get(a)));
  • Possible solution 2:  make your other borders as transparent as possible, this will allow the black to bleed through?  have not tried this though

So in summary i think that you should never put a polygon on the map straight away unless you plan on destroying it and never manipulating it.  Put it in the overlayCollection then add them to the map from it.

That’s all for now, come back soon and i will try to impart some more wisdom in your skull!

writeExternal Problem ?, can i serialize?

So I am looking to do some caching like i said and I am trying to figure out the best way to do it.  First i tried saving out the XML but it just takes too long to hydrate a feature collection object from the XML.  <Hint>  “It would be cool if a feature collection would build it’s features from the XML” </hint>  but since it can’t my function has to loop too much to give a good result.  So i am not going to use the XML to hydrate the object from cache. 

So I now want to do what i wanted to do before, and that is to actually write the feature collection out in cache but to do that i need to serialize the item.  Well the featureCollection as it is, not serializable without using their method, writeExternal.  Which wants to use the IDataOutput interface item and makes a serialized item. 
Code looks as such

var obj:ByteArray = new ByteArray;
fc.writeExternal(obj);

 Well when it sends the object along it goes and gives an error as such:

RangeError: Error #2006: The supplied index is out of bounds. at flash.utils::ByteArray/writeUTF()
      at com.mapquest::MQObject/writeExternal()[C:\projects\um_source-5-2\Common\com\mapquest\MQObject.as:193]
      at MapLOW/MapLOW::addSingleZipToDb()[C:\*\*.as:94] So it is blowing up in the flash utils for some reason after it comes from the MQObject.as function set.  Not sure why it is calling the WriteUTF function but i can’t see the code that is calling the function. 

I tried just calling

byteArray.writeObject(fc)

With a somewhat similar results… this is because the featureCollection is not serializable as it is.  So i am hoping to hear back from the MapQuest people soon to figure out what might be the cause of this problem.

 If you know the solution to help me get the featureCollection serialized please feel free to leave it here.

I also want to give a special thanks to the MapQuest guys, thanks for your help in everything, I am glad you guys are there for your customers 100 percent.  There are a lot of API’s to support so i know it can’t be easy.

Trying to do some manipulation

Ok, so I am working on some caching so i can speed things up a little and here is the problem i am finding. In trying to get only the zip code i want to cache as a feature collection i need to make the feature collection. So this is a snippet of what i am doing:

<code>
private function addZipToCache(fc:FeatureCollection):void
{
var SaveResult:FeatureCollection = new FeatureCollection;
var SaveResultXML:XMLList = new XMLList;

for(var j:int = 0; j < fc.getSize();j++)
{
if(fc.getAt(j).getName() != strZipToSave)
{
fc.remove(j);
j-=1
}

}
fc.saveXml();

zipPolyManager.setFcOfZip(strZipToSave,fc);
}
</code>

The problem is that once it Saves it does not update the XML like it should supposedly with saveXML. I am working on a way around this if someone happens to read this and figures out why this is not working please let me know. Post more about this later.

Alright this is later, and this is what i have figured out.
to make the xml save you must say fc.loadXML(fc.saveXML()), that will take care of saving it.  The next thing is getting flex to pass this out so i can cache it.  Post more as i learn about this situation.

Also still hoping to hear about the possibility that you can request a certain zip code when asking for the polygons, but still not sure.