MapQuest Developer Blog

  • Create a Geo File

    In my previous MapQuest posts I've mostly been demonstrating how to use MapQuest to display the the various types of geo-formats (KML, GeoRSS, etc). Building on the examples of map event interaction from my previous posts, we can also build an interactive map interface where users can build their own geo-format files. Here's an example where users can interactively click on the map to create a polyline.

    <html>
    <head>
    <title>Create a Geo File</title>
    <script src="http://btilelog.access.mapquest.com/tilelog/transaction?
      transaction=script&key=YOUR-API-KEY&ipr=true&itk=true&v=5.2.0"
      type="text/javascript">&t;/script>
    <script language="javascript">
    MQInitDojo(initMap);
    var myOverlayColl = new MQOverlayCollection();
    var myShapePts = new MQLatLngCollection();
    var myOL = new MQLineOverlay();
    var allclicks  = new Array(0);
    function showClick(event) {
        // log all clicks
        allclicks.push(event.ll.getLatitude(),
          event.ll.getLongitude());
        // add clicks to polyline overlay
        myShapePts.add (new MQLatLng(event.ll.getLatitude(),
         event.ll.getLongitude()));
        myOL.setShapePoints(myShapePts);
        myOverlayColl.add(myOL);
        myMap.replaceOverlays(myOverlayColl);
        // clear previous markers and add new start and end points
        myMap.removeAllPois()
        myMap.addPoi(new MQPoi(new MQLatLng(allclicks[0], allclicks[1])));
        if ( allclicks.length > 1 ) {
            myMap.addPoi(new MQPoi(new
             MQLatLng(allclicks[allclicks.length-2],
             allclicks[allclicks.length-1])));
        }
    }
    function display() {
        alert (allclicks);
    }
    function initMap() {
       myMap = new MQTileMap(document.getElementById('mapDiv'),8,
        new MQLatLng(33.173676, -116.714889));
       myMap.addControl(new MQLargeZoomControl(myMap));
       MQEventManager.addListener(myMap,"click",showClick);
    }
    </script>
    </head>
    <body>
    <div id="mapDiv" style="width:384px; height:384px; border:2px solid"></div>
    <a href="#" onclick="display();">Display Coordinates</a>
    </body>
    </html>
    

    As you can see, I'm simply using an 'alert' to display the captured latitudes and longitudes of the polyline that the user created. Obviously the next task is to transform that array of coordinates into your favorite geo-format. Here is a screenshot of the result:

  • Share-A-Map AIM Plugin

    Developer David Bello has written a great plug-in for AIM called "Share-a-Map." That clever name may have something to do with why we're covering it here.

    Right from the copy in the AIM Gallery:

    Share-a-map is an AOL AIM plugin that let you share maps with a friend via AIM in an interactive way. This plugin can be extremely useful when you want to share a location, route or place with a friend or relative, plus other features like add images and hand drawing overlays, find an address or point, save your map, among others.

    We've been playing with it here in the office and it is a really sweet way to do some map collaboration.

    The Share-a-Map plug-in is Windows only and available in the AIM Plugin Gallery.

  • Adding Rollover Functionality to Overlays: Part 2

    This method, although similar to the method I covered in my last post, uses the InfoWindow of the map rather than a rollover associated with a Point Of Interest (POI). The InfoWindow is populated with the title and content when the overlay is moused-over, and follows the cursor, similar to a tooltip.

    The biggest disadvantage with this method comes from the need to step outside of the API to attach a mousemove event. This means that the solution needs to take browser differences into consideration. In order to simplify the code, and because I generally use ASP.NET for most of my work, you will notice that I have utilized a few shortcuts provided by the Microsoft AJAX Library.

    The most important things to understand here are the loc object and the ScrollTest function. Sys.UI.DomElement.getLocation() is a function provided by the Microsoft AJAX Library which returns an object with two properties, x and y. These coordinates represent the location of the element relative to the window. The ScrollTest function returns an object representing the amount that the window has been scrolled. Using these two values, along with coordinates returned by the mousemove event object, the coordinates of the mouse in relation to the map can be discovered. These coordinates are used to place the InfoWindow on the map.

    Listing 1 - The AddRollover function
    
     // This solution uses the Microsoft AJAX Library.
     // Get it here: http://www.asp.net/ajax/downloads/
    
     // This function is necessary since different
     // browsers implement this in slightly different ways.
     function ScrollTest(){
         var elemental = document.documentElement;
         var lefty = (elemental.scrollLeft ?
                elemental.scrollLeft :
                document.body.scrollLeft);
         var toppy = (elemental.scrollTop ?
                elemental.scrollTop :
                document.body.scrollTop);
    
         return { top : toppy, left : lefty }
     }
    
     function AddRollover(roMap, roOverlay, roTitle, roContent){
         var mapDiv = $get('mapWindow');
    
         roOverlay.setAltColorAlpha(roOverlay.getAltColorAlpha() - 0.2);
         roOverlay.setAltFillColorAlpha(roOverlay.getAltFillColorAlpha() - 0.2);
    
         function MouseFollow(e) {
             var scrooll = ScrollTest();
             var loc = Sys.UI.DomElement.getLocation($get('mapWindow'));
             // $get() is a short form for getElementById()
             var point = new MQPoint(e.clientX + scrooll.left - loc.x,
                 e.clientY + scrooll.top - loc.y);
             roMap.openInfoWindow(point);
         }
    
         MQEventManager.addListener(roOverlay, "mouseover", function(){
             // Each map only has one InfoWindow, so we need to set these values
             // every time the mouse is over an overlay
             roMap.setInfoTitleHTML(roTitle);
             roMap.setInfoContentHTML(roContent);
    
             // $addHandler is simply a cross-browser method for attaching an event handler, provided
             // by the Microsoft AJAX Library
             $addHandler(mapDiv, 'mousemove', MouseFollow);
             roOverlay.setAltStateFlag(true);
         });
    
         MQEventManager.addListener(roOverlay, "mouseout", function(){
             $removeHandler(mapDiv, 'mousemove', MouseFollow);
             roMap.getInfoWindow().hide();
             roOverlay.setAltStateFlag(false);
         });
     }
    	
  • Map It! - Building a MapQuest Mac OS X Dashboard Widget - Part 5 - Enhancing Map It!

    It is not down in any map; true places never are. - Herman Melville

    In Part 1 I showed you how to get started with the MapQuest Platform by getting a developer key. In Part 2, I put that key to use by providing access to a basic map in the Map It! widget. Part 3 showed you how to incorporate basic geocoding. In Part 4 I discussed more advanced geocoding topics - including handling multiple matches and specifying geocode search options. In this installment I'll discuss adding some options to the widget. Specifically I'll discuss setting the default zoom level when adding an API, specifying point of interest icons, and the default map type.

    The Widget Options

    The back of the Map It! widget is shown in Figure 1. The user can set defaults for zooming, map view, and the point of interest icon. Each of these values are saved in the widget's preferences and loaded when the widget is launched.

    Figure 1

    Setting the Options At Startup

    The widget load function is called when the widget is first loaded. The preferences are created if they don't already exist.

    function load()
    {
        setupParts();
    
      // check the current prefrences version -
      // if not available or version changed recreate them
      if(loadPref("version") != "0.1") {
        // store the current version
        createPref("version","0.1");
        createPref("zoom","10");
        createPref("view","map");
        createPref("icon","pin");
      }
    
         defZoom = parseInt(loadPref("zoom"));
      defView = loadPref("view");
      defIcon = loadPref("icon");
    
      // Create the Map...
      myMap = new MQTileMap(document.getElementById('myMap'));
      var myLZControl = new MQLargeZoomControl(myMap);
      myMap.addControl(myLZControl,
        new MQMapCornerPlacement(MQMapCorner.TOP_LEFT, new MQSize(1,1)));
      var myVControl = new MQViewControl(myMap);
      myMap.addControl(myVControl,
        new MQMapCornerPlacement(MQMapCorner.TOP_RIGHT, new MQSize(20,20)));
      // set the default map view
      myMap.setMapType(defView);
    }
    

    When an address is plotted, the code has been changed as reflected:

    //create a new icon object
    myIcon = new MQMapIcon();
    
    // set the icon image: icon file location, width, height,
    // recalc infowindow offset,
    // is it a PNG image?
    if(defIcon == "pin") {
      myIcon.setImage("images/pinpoint_red.gif",32,32,true,false);
    } else if(defIcon == "star") {
      myIcon.setImage("images/starsmall_red",18,18,true,false);
    } else if(defIcon == "x") {
      myIcon.setImage("images/xspot.gif",17,17,true,false);
    }
    
    // create a point
    myPoint = new MQPoi(newCenter);
    
    // set the custom icon
    myPoint.setIcon(myIcon);
    
    // recenter the map on the point, the second parameter
    // specifies the zoom level
    myMap.setCenter(newCenter,defZoom);
    

    A new MQMapIcon object is created to to hold the custom icon. The setImage method is called to load the icon image, and the setIcon method of the MQTileMap object is called to associate the icon with the point of interest. Now when a search is run, the default custom icon is displayed as shown in Figure 2.

    Figure 2

    Conclusion

    In Part 6 I'll discuss adding direction capabilities to the widget. For your reference, here are some references to the MapQuest APIs:

  • MapNews - A Map Based News Browser - Part 3 - AOL National News RSS Feed

    "In theory, there is no difference between theory and practice. But, in practice, there is." - Jan L. A. van de Snepscheut

    In theory, the project to place RSS news feeds on a map could be very easy. GeoRSS is standard for encoding geographic locations in RSS feeds. RSS feeds that have this encoding are the perfect data source for this project. But, in practice, the feeds I've been looking at do not have GeoRSS information. So, I'm going to start by seeing what I can get done without GeoRSS.

    The source of news that I'm working with is the AOL national news RSS feed. The description element of each feed starts with a location. I extracted the location, geocoded each location with the MapQuest (MQ) geocoding service, and then placed point of interest (POI) markers on the map.

    In my last post, I laid out my design for MapNews. In this post, I present the working application and code I've written based on the MQ client Javascript tool kit. The following screen shot of MapNews shows POI markers for cities that have news stories in an example feed. I've clicked on the POI marker for Salt Lake City to expose links to news stories in the information window.

    The HTML and Javascript for MapNews are shown below. I used prototype for an AJAX fetch of the RSS feed and also for parsing the RSS XML. On line 7, I included prototype 1.6 since the MQ Javascript library version seemed to be missing the AJAX component (also set ipr=false on line 6). I installed the PHP version of the MQ proxy on my server and pointed the MQExec object to the proxy on lines 13-26. I installed the MQ API Javascript files on my server and referenced them on lines 8-10 (excluding mqcommon.js which is included in line 6).

    Lines 34-43 define the Feed class that represents each feed item and associated geocoding results. I fired off an AJAX request to fetch the RSS file on lines 46-54 and handled the return in processRss (lines 57-71). Lines 60-63 pull the item description and link straight from the RSS feed (the prototype function cleanWhitespace (line 126) comes in handy here).

    The function parseForCityAndState (called on line 64) uses a regex to find a city, and perhaps, a state in the description RSS element. The regex on line 114 will make more sense if you consider the following examples of the AOL RSS feed:

          <description>
            ST. LOUIS (AP) - Substandard care at a southern Illinois ...
          </description>
          <description>
            ALBANY, N.Y. (AP) - It's the perfect tax: Government  ...
          </description>
        

    I, basically, looked for text starting the description prior to the literal '(AP)', and if a comma was present, I inferred that the state was the text after the comma.

    With the city and state in hand, I call the MQ geocoding service (line 100-111). The geocoding service worked well; I had only 1 failed geocoding out of 35 items, many of which had only a city name. I picked the first element of the returned MQGeoAddress collection (line 109) and skipped items for which the geocoding failed (line 76).

    Finally, the geocoded items are converted to POI makers with associated descriptions and links (lines 88-97). I collected the items by location to have only one information window per location with multiple items (lines 77-84). This was accomplished by building a hash with keys of MQLatLng.toString() (which is a concatenation of longitude and latitude) and values of a list of Feed objects.

    I'm pretty happy with the application so far. There is one problem, however. I'm firing off many geocoding requests (one for each RSS item) from the client and the page load is too slow. I envision that the fix will be to use the batch geocoding method from the MQ API, or, to do the geocoding once per RSS feed on the server.

    The next steps for MapNews are to consume GeoRSS feeds and to adopt batch geocoding for feeds without GeoRSS encoding.

    001 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    002 <html>
    003  <head>
    004   <title>MapNews</title>
    005   <link rel="stylesheet" href="mapnews.css"
    type="text/css"/>
    006   <script src="http://btilelog.access.mapquest.com/tilelog/transaction?
        transaction=script&key=mjtd%7Clu6y290rn9%2C22%3Do5-0utn1
        &ipr=false&itk=true&v=5.2.0"
        type="text/javascript"></script>
    007   <script src='prototype-1.6.0.2.js'
        type='text/javascript'></script>
    008   <script src='mqutils.js'
        type='text/javascript'></script>
    009   <script src='mqobjects.js'
        type='text/javascript'></script>
    010   <script src='mqexec.js'
        type='text/javascript'></script>
    011   <script language='javascript'>
    012
    013    var g_proxyServerName = '';
    014    var g_proxyServerPort = '';
    015    var g_proxyServerPath = '/mq/JSReqHandler.php5'
    016
    017    var g_serverName = 'geocode.dev.mapquest.com';
    018    var g_serverPort = '80';
    019    var g_serverPath = 'mq';
    020
    021    var g_geoExec = new MQExec(
    022     g_serverName, g_serverPath, g_serverPort,
    023     g_proxyServerName, g_proxyServerPath, g_proxyServerPort
    024    );
    025
    026    var g_mqMap;
    027
    028    function startMap() {
    029     g_mqMap = new MQTileMap(document.getElementById('mapWindow'), 2,
    030       new MQLatLng(39.81,-98.56), "map");
    031     getRss();
    032    }
    033
    034    function Feed(){}
    035    Feed.prototype = {
    036     title: '',
    037     link: '',
    038     description: '',
    039     city: '',
    040     state: '',
    041     country: 'USA',
    042     geoAddress: null
    043    };
    044
    045
    046    function getRss() {
    047     new Ajax.Request('news_top_nat.xml', {
    048      method:'get',
    049      onSuccess: function(transport){
    050      processRss(transport.responseXML);
    051     },
    052     onFailure: function(){ alert('Something went wrong...') }
    053     });
    054    }
    055
    056
    057    function processRss(root) {
    058     var feeds = new Array();
    059     Element.select(root, 'item').each(function(item, i) {
    060      var feed = new Feed();
    061      feed.title = getChildsText(item, 'title');
    062      feed.link = getChildsText(item, 'link');
    063      feed.description = getChildsText(item, 'description');
    064     parseForCityState(feed);
    065
    066     geoCode(feed);
    067     feeds.push(feed);
    068
    069     });
    070     placeOnMap(feeds);
    071    }
    072
    073    function placeOnMap(feeds) {
    074     var locToFeed = new Hash();
    075     feeds.each(function(feed) {
    076     if(feed.geoAddress) {
    077       var thisKey = feed.geoAddress.getMQLatLng().toString();
    078     if(locToFeed.keys().indexOf(thisKey) > -1) {
    079       locToFeed.get(thisKey).push(feed);
    080     } else {
    081         var a = new Array();
    082         a.push(feed);
    083         locToFeed.set(thisKey, a);
    084       }
    085      }
    086     });
    087
    088    locToFeed.values().each(function(feeds) {
    089      var html = '';
    090      feeds.each(function(feed) {
    091       html += "<a href='#{link}'>#{title}</a>
              <br>".interpolate(feed);
    092      });
    093      var poi = new MQPoi(feeds[0].geoAddress.getMQLatLng());
    094      poi.setInfoTitleHTML(feeds[0].city);
    095     poi.setInfoContentHTML(html);
    096     g_mqMap.addPoi(poi);
    097     });
    098    }
    099
    100    function geoCode(feed) {
    101     if(feed.city != '') {
    102     var address = new MQAddress();
    103     address.setCity(feed.city);
    104     address.setState(feed.state);
    105     address.setCountry(feed.country);
    106
    107     var gaCollection = new MQLocationCollection("MQGeoAddress");
    108     g_geoExec.geocode(address, gaCollection);
    109     feed.geoAddress = gaCollection.get(0);
    110    }
    111    }
    112
    113    function parseForCityState(feed) {
    114     var match = feed.description.match(/^([^(]+)\(AP\)/);
    115     if(match) {
    116      var cityAndMaybeState = match[1].split(',');
    117      feed.city = cityAndMaybeState[0];
    118      if(cityAndMaybeState.length > 1) {
    119        feed.state = cityAndMaybeState[1];
    120      }
    121     }
    122    }
    123
    124    function getChildsText(item, whichChild) {
    125     var child = Element.select(item, whichChild)[0];
    126     return Element.cleanWhitespace(child).firstChild.nodeValue;
    127    }
    128   </script>
    129  </head>
    130
    131  <body onload="startMap();">
    132   <h1>MapNews</h1>
    133   <hr>
    134   <div id="mapWindow" style=""></div>
    135   <hr>
    136  </body>
    137 </html>
    
  • Ribbit, Kayak, and MapQuest Mashup is a Winner

    Earlier this month we told you about a "Great Mashup using APIs from Ribbit, Kayak, and MapQuest" by Andrew Powell, shamelessly plugging it to help him win a Wii.

    Well, not only did Andrew win the 306|Flex API Contest and the Wii, he also won some help restyling the UI.

    Check out the restyled and award-winning Ribbit, Kayak, MapQuest Hotel Search Mashup. Congrats Andy!

  • Map It! - Building a MapQuest Mac OS X Dashboard Widget - Part 4 - Advanced Geocoding

    Younger hackers are hard to classify. They're probably just as diverse as the old hackers are. We're all over the map. - Larry Wall

    In Part 1 I showed you how to get started with the MapQuest Platform by getting a developer key. In Part 2 I put that key to use by providing access to a basic map in the Map It! widget. Part 3 showed you how to incorporate basic geocoding. In this installment I'll discuss more advanced geocoding topics - including handling multiple matches and specifying geocode search options.

    Multiple Matches

    The version of the Map It! widget developed in Part 3 added the ability to plot a point of interest on the map. In the Java application that was developed to return the coordinates of an address, only the first match is returned. What if the user enters a street address without the house number? Geocoding would actually return multiple results. We need to change the getLocation method developed in Part 3 to return each match. On a search without a house number for example, the geocoding process would return points for each range of addresses. Listing 1 shows how the getLocation method has been modified:

    Listing 1

      public static String getLocation(String street, String city,
        String state, String zip, String country)
         {
          String result;
            /*
            MapQuest.Exec is the MapQuest client object.
            All server requests, such as Geocode and Search, are part of the
            Exec object.
            */
            Exec geocodeClient = new Exec();
    
            geocodeClient.setClientId ("** YOUR CLIDENT ID **");
            geocodeClient.setPassword ("** YOUR PASSWORD **");
            geocodeClient.setServerName("geocode.dev.mapquest.com");
    
          // create a new address object
          Address originAddress = new Address();
    
          // create a new location collection to save the results
          LocationCollection geocodeResults = new LocationCollection();
    
          // save the parsed address
            originAddress.setStreet(street);
            originAddress.setCity(city);
            originAddress.setState(state);
            originAddress.setPostalCode(zip);
            originAddress.setCountry(country);
    
            try
            {
               // This is the first communication with the MapQuest server
               // Try converting the address to coordinates
               geocodeClient.geocode(originAddress,geocodeResults);
    
             // create a new GeoAddress
               GeoAddress geoAddress = new GeoAddress();
    
           // any results returned?
           if(geocodeResults.getSize() > 0) {
            // loop through each result
            for(int i=0; i < geocodeResults.getSize(); i++) {
                  geoAddress = (GeoAddress)geocodeResults.getAt(i);
              // append the coordinates and information about that
              // matching location
              result = result +
               String.valueOf(new Double(geoAddress.getLatLng().getLatitude())) +
               "|" + String.valueOf(new
               Double(geoAddress.getLatLng().getLongitude())) + "|" +
                 geoAddress.getStreet() + "|" + geoAddress.getCity()  + "|" +
                   geoAddress.getState() + "|" + geoAddress.getCountry() + "|" +
                 geoAddress.getPostalCode() + "\n";
            }
           }
            }
            catch (Exception e)
          {
          // anything wrong - print an error message
          return "ERROR";
          };
          // if the address is not found - report NOT FOUND
            return "NOT FOUND";
         }
    

    For each match that is returned by geocoding, the result string is appended with the coordinates, and street, city, state, zip, and country of that location. Each parameter is separated by | for easy parsing in JavaScript.

    Geocoding Options

    The API documentation indicates that the default geocoding options will typically be adequate for most developers. However, there are a few options you can specify that ma be of interest. These include:

    • MatchType - A constant that corresponds to the granularity of the desired match.
    • QualityType - The minimum confidence necessary for a match.
    • MaxMatches - the maximum number of matchers to return.

    These options are explained in detail in Chapter 3 of the Java API Developer Guide.

    To specify an option you need to create a GeocodeOptions object, then specify the MatchType, QualityType, or MaxMatches properties. (The constants for each option are specified in Chapter 3). As shown in Listing 2, the GeoCodeOptions object is passed as a parameter to the geocode method.

    Listing 2

    // create a geocode options object
    GeocodeOptions geocodeOptions = new GeocodeOptions();
    
    // return only exact matches
    geocodeOptions.setQualityType(QualityType.EXACT);
    
    // create the options collection object
    // then add the option to the options collection
    GeocodeOptionsCollection geocodeOptionsCollection =
      new GeocodeOptionsCollection();
    geocodeOptionsCollection.add(geocodeOptions);
    try {
      // perform the search
      Client.geocode(originAddress, geocodeResults, geocodeOptionsCollection);
    }
    

    Selecting the Best Match

    Now that multiple locations are returned for a search, those results need to be displayed to the user in a combo box, as shown in Figure 1.

    Figure 1

    When the users selects an address from the combo box that point is displayed on the map. To do this we need to change the onAddressSearch JavaScript function introduced in Part 3, as follows:

    function onAddressSearch(event) {
        // user hit a return?
          if(event.keyCode == 13) {
          if(!$(address).value.blank()) {
    
          var street = "";
          var city = "";
          var state = "";
          var zip = "";
          var country = "";
    
           $(lblErrorMsg).innerText = "";
             $(multMatches).style.visibility = "hidden";
           $(lblMultMatch).style.visibility = "hidden";
    
             var splitAddr = $(address).value.split(',');
    
           // if we only get 2 elements assume address, zip
           if(splitAddr.length == 2) {
            street = splitAddr[0];
            zip = splitAddr[1];
    
            // for three elements assume address, city, state
            } else if(splitAddr.length == 3) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
    
            // for 4 elements assume address, city, state, zip
            } else if(splitAddr.length == 4) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
            zip = splitAddr[3];
    
            // for 5 elements assume address, city, state, zip, country
            } else if(splitAddr.length == 5) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
            zip = splitAddr[3];
            country = splitAddr[4];
    
            } else {
            // error! unknown address
    
            $(lblErrorMsg).innerText = "Error! Unknown Address Format!";
            return;
           }
    
           var result = widget.system('java -classpath .:mq.jar GetLocations
             -street "' + street + '" -city "' + city + '" -state "' + state + '"
             -zip "' + zip + '" -country "' +country + '"',null).outputString;
    
           // any errors?
           if(result == "ERROR") {
            $(lblErrorMsg).innerText = "Error! Unknown Address Format!";
           // address not found?
           } else if (result == "NOT FOUND") {
            $(lblErrorMsg).innerText = "Address Was Not Found!";
           } else {
    
           // *** CHANGED TO SUPPORT MULTIPLE LOCATION ***
    
           // split on return - a line is printed for each individual result
           var eachresult = result.split('\n');
    
           // just one result returned? - then just plot the point
           if(eachresult.length == 1) {
              // get the coordinates from the returned string
              var coords = result.split('|');
              // create a new point based on the coordinates
              newCenter = new MQLatLng(parseFloat(coords[0]),parseFloat(coords[1]));
              // create a point
              myPoint = new MQPoi(newCenter);
              // recenter the map on the point,
              // the second parameter specifies the zoom level
              myMap.setCenter(newCenter,10);
              // add the point as a Point of Interest
              myMap.addPoi(myPoint);
           } else {
              // clear out existing items
              $(multMatches).options.length = 0;
              for(var i=0; i < eachresult.length; i++) {
                // parse each returned location
                var location = eachresult[i].split('|');
                // 7 items on the result line?
                if(location.length == 7) {
                  var locationtext = location[2] + "," + location[3] +
                    "," + location[4] + "," + location[5] + "," + location[6];
                  var objNewOption = document.createElement("OPTION");
                  $(multMatches).options.add(objNewOption);
                  // add the location text
                  objNewOption.text = locationtext;
                  // add the coordinates as a | separate string to the value...
                  objNewOption.value = location[0] + '|' + location[1];
                }
              }
              // show the label and combo box...
              $(multMatches).style.visibility = "visible";
              $(lblMultMatch).style.visibility = "visible";
            }
           }
            }
        }
      }
    

    A separate line is returned for each matching location. The text is displayed for each option, and the value is set to the coordinates. When the user selects an item, the coordinates value is retrieved and the point is plotted on the map.

    Conclusion

    In Part 5 I'll discuss enhancements to the Map It! widget, including setting the default zoom level when adding an API, specifying point of interest icons, and the default map type. For your reference, here are some references to the MapQuest Platform:

  • MapNews - A Map Based News Browser - Part 2 - Initial Design

    "If you don't know where you are going, any road will get you there." - Lewis Carroll

    Like many developers I often start a project by jumping in and writing code. For this project I'm going try something a little different and create a road map for my mapping project.

    My project is named MapNews. In my prior post, I covered the project concept: present a map with markers/information windows which show news headlines by location.

    Doing the design up front is a bit of a jump off of a cliff for me. I've had only a light reading of the MapQuest Platform documentation and am going to proceed to layout the design. This should be interesting; as the project unfolds, I'll be able to look back and see my misconceptions exposed.

    Without further ado, I present the following sketch of my design:

    One implicit choice I've made here is to use JavaScript on the client to access the MapQuest functionality. Other language choices and/or server side implementation are allowed as the MapQuest Platform has bindings for Java, .NET, CPP, and ActionScript.

    The server will hold RSS feeds arranged by topics such as National or Business news. A complete system would include a server side process to collect and cache RSS feeds. I'm focusing on the MapQuest API so I may not build out this component. Instead, I'll just stage a couple of RSS XML files on the server.

    I plan to fetch the RSS feeds with an AJAX component after the initial load of index.html to the browser. I'll also need some JavaScript to parse the RSS feeds and discover which items have associated locations and then geocode the locations via the MapQuest API. MapNews JavaScript will also have to invoke the MapQuest JavaScript API to create the map and place markers and information windows on the map.

    I've shown the processes executing on the client. However, the physical location of the JavaScript files is also important and is driven by browser security restrictions (scripts can only call back to the domain from which they are served).

    For basic maps the MapQuest (MQ) JavaScript files can be directly included from a MapQuest server. For more complex functionality such as searching and routing the MapQuest JavaScript files need to be located on your server. The MQ JavaScript files then communicate with MapQuest servers via a proxy located on your server. MapQuest provides proxy implementations in several languages (Java, PHP5, .Net, ROR, and Flash). I have PHP on my server so that's an easy choice.

    That is about all I have in my initial design. The next steps are to get the infrastructure set up. I plan to follow the instructions in the MapQuest Advantage Developer Guide to configure and install the proxy. Then, I am off to the fun parts of writing the RSS parsing code and using the MQ API to bring MapNews to life.

  • Great Mashup using APIs from Ribbit, Kayak, and MapQuest

    "Ribbit / MapQuest / Kayak Mashup" is the title of a post by Andrew Powell from Universal Mind, introducing a great hotel search app he built using APIs from (big surprise!) Kayak, Ribbit, and our very own AS3 API.

    Give it a read and check out Andrew's hotel search app. We also support shameless promotion, so help Andrew win a Wii by voting for his application on the 360|Flex Contest Page.

    screenshot of hotel search mashup
  • Map It! - Building a MapQuest Mac OS X Dashboard Widget - Part 3 - Adding Geocoding

    Somewhere there is a map of how it can be done. - Ben Stein

    In Part 1 I showed you how to get started with the MapQuest Advantage API by getting a developer key. In Part 2 I put that key to use by providing access to a basic map in the Map It! widget. In this installment I'll show you how to incorporate basic geocoding.

    About Geocoding

    Geocoding is the process of converting an address into latitude and longitude coordinates that uniquely identify a location, and you can use to plot on a map. Applications using the MapQuest Platform can calculate the latitude and longitude of:

    • Street addresses and intersections, the highest accuracy geocoding methods.
    • Street blocks, including the nearest block to an invalid house number.
    • Postal codes, including ZIP, ZIP+2, and ZIP+4 codes.
    • City centers.
    • US state and Canadian province centers.
    • Country centers.
    • Centers of other administrative areas that are used internationally.

    The Map It! application will allow users to enter an address in one of the following forms:

    • street address, zip
    • street address, city, state
    • street address, city, state, zip
    • street address, city, state, zip, country

    Preparing an Address for Geocoding

    As shown in Figure 1, the user enters the address in the search text box in one of the acceptable forms.

    Figure 1

    The address needs to be separated into its constituent parts for geocoding. Since the user enters a comma separated string, the following JavaScript function is called on every key press. Once a return is entered (key code 13) the address is parsed.

    function onAddressSearch(event)
    {
      // user hit a return?
        if(event.keyCode == 13) {
        if(!$(address).value.blank()) {
    
          var street = "";
          var city = "";
          var state = "";
          var zip = "";
          var country = "";
    
          // clear the error message area
           $(lblErrorMsg).innerText = "";
    
          // the split function splits a string into an array based on a
          // character delimeter...
             var splitAddr = $(address).value.split(',');
    
           // if we only get 2 elements assume address, zip
           if(splitAddr.length == 2) {
            street = splitAddr[0];
            zip = splitAddr[1];
    
          // for three elements assume address, city, state
           } else if(splitAddr.length == 3) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
    
          // for 4 elements assume address, city, state, zip
          } else if(splitAddr.length == 4) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
            zip = splitAddr[3];
    
          // for 5 elements assume address, city, state, zip, country
           } else if(splitAddr.length == 5) {
            street = splitAddr[0];
            city = splitAddr[1];
            state = splitAddr[2];
            zip = splitAddr[3];
            country = splitAddr[4];
    
           } else {
            // error! unknown address
    
            $(lblErrorMsg).innerText = "Error! Unknown Address Format!";
            return;
           }
    .
    .
    .
    

    If the address is blank or not in the proper format and error message is displayed below the search text box. Once the address string has been parsed, it's time to convert the address to a latitude and longitude.

    The Need for a Proxy

    To call the Geocoding routines from your web application you need to install a proxy. The JavaScript API documentation describes the purpose of the proxy as twofold: "Circumvent the XML cross domain security issue. A browser can currently only request XML from the same domain as the HTML page arrived from. In other words, you cannot serve a page that requests XML directly from us, it has to come from you, so the Proxy is therefore a routing mechanism to make that happen. Secondly, the Proxy also adds in your clientID and password on the way through, so that your authorization credentials are not exposed in any manner within the web browser." The JavaScript API provides proxies in Java, PHP 5, .Net, Ruby on Rails, and a client side Flash proxy. Refer to Chapter 3 in the Advantage API Javascript Documentation for instructions on installing the proxy.

    Java Geocoding - Since a Proxy Doesn't Work for Dashboard Widgets

    All of the proxies, including the client side Flash proxy, assume that your application is running on a web server. As I mentioned in Part 2 the Geocoding process presents a limitation with Dashboard widgets, as they don't run on a web server, so proxies won't work. Instead, we'll need to access Geocoding through a Java application that the widget can call. The Java application is simple, it takes an address in command line parameters, and prints the first matching coordinates. You'll need to download the MapQuest Platform: Java SDK from http://developer.mapquest.com/downloads. This will provide the mq.jar file, which is required to access the MapQuest functions from your Java application.

    Getting the First Coordinate Match

    The GetLocation Java program in shown in Listing 1. When run it searches the command line for parameters that specify each component of the address. These components are passed to the getLocation method. A new geocodeClient object is created. You need to specify the username and password you obtained when you signed up for MapQuest access, as I described in Part 1 (link back ). An Address object is created, and initialized with the address specified on the command line. Even if only some of the attributes are specified, the geocoding process will be able to locate the coordinates. If a result is found, a string is created that contains the first matching coordinates. The latitude and longitude is separated by a | character.

    Listing 1

      public class GetLocation
      {
         public static void main (String[] args)
         {
            String street = "";
          String city = "";
          String state = "";
          String zip = "";
          String country = "";
    
          // parse the command line parameters
          if(args.length > 0) {
            for(int i=0; i < args.length; i+=2) {
              if(args[i].equalsIgnoreCase("-street")) {
                street = args[i+1];
              } else if(args[i].equalsIgnoreCase("-city")) {
                city = args[i+1];
              } else if(args[i].equalsIgnoreCase("-state")) {
                state = args[i+1];
              } else if(args[i].equalsIgnoreCase("-zip")) {
                zip = args[i+1];
              } else if(args[i].equalsIgnoreCase("-country")) {
                country = args[i+1];
              } else {
                System.out.printf("ERROR\n");
                return;
              }
            }
            // print the found coordinates
            System.out.printf("%s\n",getLocation(street,city,state,zip,country));
            return;
          } else {
            // if unable to parse the command line arguments
            // print ERROR
            System.out.printf("ERROR\n");
            return;
          }
         }//end main
    
         public static String getLocation(String street, String city, String state,
           String zip, String country)
         {
          String result;
            /*
            MapQuest.Exec is the MapQuest client object.
            All server requests, such as Geocode and Search,
            are part of the Exec object.
            */
            Exec geocodeClient = new Exec();
    
            geocodeClient.setClientId ("** YOUR CLIDENT ID **");
            geocodeClient.setPassword ("** YOUR PASSWORD **");
            geocodeClient.setServerName("geocode.dev.mapquest.com");
    
          // create a new address object
          Address originAddress = new Address();
    
          // create a new location collection to save the results
          LocationCollection geocodeResults = new LocationCollection();
    
          // save the parsed address
            originAddress.setStreet(street);
            originAddress.setCity(city);
            originAddress.setState(state);
            originAddress.setPostalCode(zip);
            originAddress.setCountry(country);
    
            try
            {
               // This is the first communication with the MapQuest server
               // Try converting the address to coordinates
               geocodeClient.geocode(originAddress,geocodeResults);
    
             // create a new GeoAddress
               GeoAddress geoAddress = new GeoAddress();
    
            // any results returned?
          if(geocodeResults.getSize() > 0) {
                // get the first result
                geoAddress = (GeoAddress)geocodeResults.getAt(0);
    
                // create a string to contain the latitude and longitutde
            result = String.valueOf(new Double(geoAddress.getLatLng().getLatitude())) + "|" + String.valueOf(new Double(geoAddress.getLatLng().getLongitude()));
            return result;
           }
            }
            catch (Exception e)
          {
          // anything wrong - print an error message
          return "ERROR";
          }
    
          // if the address is not found - report NOT FOUND
            return "NOT FOUND";
         }
      }//end class GetLocation
    

    Plotting the Point of Interest (POI)

    After parsing the entered address, the following code concludes the onAddressSearch function:

    // call the GetLocation Java Program - note the mq.jar file must be included in
    // classpath
    var result = widget.system('java -classpath .:mq.jar GetLocation -street "'
      + street + '" -city "' + city + '" -state "' + state + '" -zip "'
      + zip + '" -country "' +country + '"',null).outputString;
    
    // get the coordinates from the returned string
    var coords = result.split('|');
    
    // create a new point based on the coordinates
    newCenter = new MQLatLng(parseFloat(coords[0]),parseFloat(coords[1]));
    
    // create a point
    myPoint = new MQPoi(newCenter);
    
    // recenter the map on the point, the second parameter specifies the zoom level
    myMap.setCenter(newCenter,10);
    
    // add the point as a Point of Interest
    myMap.addPoi(myPoint);
    

    Once the coordinates are returned from the GetLocation Java program, a new MQLatLng object is created with the coordinates returned. A MQPoi (point of interest) object is created, then the map is centered on the new point, and an icon is added to mark it with the addPoi method of the MQTileMap object. The result of mapping a point in the Map It! widget is shown in Figure 1.

    Figure 1

    Conclusion

    In Part 4 I'll discuss Geocoding quality and how to handle multiple returned coordinates. For your reference, here are some references to the MapQuest Platform: