MapQuest Developer Blog

  • MapQuest Data Manager 101: Step 2 - Table Maintenance

    Welcome back! In the first article of this series, I walked you through the basics of creating and populating a custom table of location data using MapQuest Data Manager. In this article I will take you through the remaining functions that Data Manager offers, and make you a Data Manager expert extraordinaire!

    A Multi-Step Process

    As you might recall from the first article, to populate your custom table you perform the following steps:

    1. Create the custom table.
    2. Generate a text file that contains location information.
    3. Upload the text file.

    Although I showed you how to push data from staging to production, you will normally go through several iterations of data maintenance before doing this push (unless you are one of the few software developers who never make a mistake). These maintenance steps will likely include adding new records, removing existing records, and changing records that are already in the database--basic database maintenance type stuff.

    By the way, as I walk you through this article I am going to further refine my own personal collection of locations. I encourage you to come up with your own list of locations, even if you randomly pick them off a map. My locations are pretty standard fare in the D.C. area, but you might choose instead to come up with locations that are closer in proximity to where you live.

    With that said, let's look at the database maintenance functions that Data Manager provides for those of us prone to make mistakes. The first is the Clone Table function.

    Cloning a Table

    The majority of table maintenance functions are accessed on the Tables page. Remember that you need to log in at http://developer.mapquest.com before you can use any of these functions.

    Figure 1. The Tables page

    The Clone Table option, as the name implies, is very useful if you want to make a duplicate of an existing table. You can use it to copy the entire table, including its contents, or just the table schema. In this case, I have decided I no longer like the name of the table I created in the last article, so I am going to use Clone Table to make a copy of the table with a new name, and I will do this without losing any data!

    To begin, click Clone in the menu of options on the right of the page to load the Clone Table page.

    Figure 2. Clone Table page

    Two options specify how the table will be cloned: Clone Structure Only and Clone Data and Structure. Click the latter, type a new table name--I am using PhotoTrekLocs--in the New Table Suffix field, and then click Clone Table. Data Manager creates a copy of the PhotoTrekPOIs table named PhotoTrekLocs.

    It should be no surprise by now that while Data Manager is cloning your table, both the source and target tables will be locked, just as they are when Data Manager is in the midst of any operation. When the clone operation is complete, you will receive a notification e-mail.

    If you haven't guessed it yet, what you are actually accomplishing here is a rename of the PhotoTrekPOIs table. Cloning a table in this manner is the first step, and the second--after verifying the copy completed, of course--is deleting the original PhotoTrekPOIs table.

    After receiving an e-mail that indicates the table has been cloned, click Tables on the main Data Manager page. The first thing you will notice is that there are now two tables: the original PhotoTrekPOIs table, and the newly cloned PhotoTrekLocs table.

    Figure 3. Table list

    Change the "active" table by selecting PhotoTrekLocs in the Change Active Table drop-down list. You are making this selection to verify that the data was copied to the newly cloned table as expected. It really isn't a big deal at this point, because there are only two records in the table, but it is a good habit to get into. To verify the data is in the new table, click Edit Locations on the menu at left.

    Figure 4. Edit locations

    Next, click Find Locations. This will display a list of all of the records in the staging area--only two at this point.

    Figure 5. Listing the locations

    I cannot recommend highly enough that you take the time to go through this extra step of verifying your data, especially if your location table has a significant amount of records in it.

    Now that you have verified that the location data was copied correctly, you can go ahead and delete the PhotoTrekPOIs table.

    Deleting a Table

    To delete a table, begin by clicking the Tables menu option. This returns you to the Tables page, where both tables show up (see Figure 3). Go ahead and click the Delete icon that is in the same row as the PhotoTrekPOIs table. Data Manager politely asks if you really want to delete this table. Click YES-DELETE, and Data Manager will permanently remove this table.

    Figure 6. Verify table deletion

    Deleting a table is a synchronous "blocking" process, unlike most of the other Data Manager operations. When the table is deleted you will be returned to the Tables page, where you will now see only one table listed: PhotoTrekLocs. Deleting a table is just that easy, so be careful and make sure you have a backup copy of your data!

    Adding a New Location

    In the first article of this series, I showed you how easy it is to add a group of location records all at once. In fact, at the end of this article I will show how easy it is to add even more location records, using something called "incremental batch files."

    But what if you want to very quickly add a new location without going through the process of generating an import file? What if you don't know the entire address for the location, but know just enough to get close? The answer is on the Add Location page.

    Let me show you just how cool this feature is. To start, click Add Locations on the menu.

    Figure 7. Adding a new location, step 1

    On the first Add Location page, type as much information as you know about your location. What the form doesn't provide is fields to enter custom information; these will appear on the next page, so don't be concerned.

    For my example, I have typed "International Spy Museum" as my next location, using a record ID of "0000000050." I have added as much of the location information as I know. You should do the same with whatever location you choose to enter. When you are ready to move on, click CONTINUE-ENTER LOCATION, which tells Data Manager to proceed to the next page.

    Note that if you click DONE-ADD LOCATION, you will not have the option of adding any custom information, and the record will immediately be added to your staging database, as is.

    On the next page, your custom-defined fields are ready and waiting for you to populate them.

    Figure 8. Adding a new location, step 2

    Here I have entered the URL of the Spy Museum, as well as the appropriate location type. If you don't recall the location types I am using in this series, refer to the first article. You can, of course, use any location type you want, because this is a custom field. Now comes the fun part!

    After you click ADD LOCATION, Data Manager automatically geocodes the location and then takes you to an interactive page that displays a map of the location. On this page you have a variety of options to further refine your location.

    Figure 9. Adding a new location, step 3

    In addition to displaying the location information you have already entered, before adding custom fields, this page also displays the geocode quality of the location. Most importantly, however, are the options you have to customize and refine your location data on this page.

    Move - Clicking this will take you to an interactive map in which you can refine your location by simply clicking the map itself. I will cover the details shortly (see the section "Moving an Existing Location" later in this article).

    Edit - Clicking this will take you to the Edit Location page, which I also will cover shortly (see the section "Editing an Existing Location" later in this article).

    Add Another Location - Clicking this will, as you have no doubt guessed, display a blank form for entering your next location.

    Push to Production - Clicking this will push the entire contents of your staging database to production, overwriting any data you currently have stored there.

    Editing an Existing Location

    My second most favorite function for maintaining my data in Data Manager is Edit Locations. Edit Locations is a powerful feature that lets you filter your collection of locations to a manageable size, and then make changes to the locations one at a time.

    Start by clicking Edit Locations on the menu.

    Figure 10. Editing locations

    You use the first page to find locations; more specifically, you use it to find and display only the locations that match the search criteria you specify. So, not only can you use it to locate a specific record, you can also use it to locate a number of locations that share common information among them.

    In my case, two of the three locations I have specified share the same ZIP Code. So, if I type 20224 in the Zip or Postal Code field, and then click Find Locations, only locations I added where the ZIP was 20224 are displayed. In this case, there are only two. Depending on what you choose to use for search criteria, you might see more, or less. The important thing to realize is that this is an extremely powerful way to filter your location data down to a reasonable size. In addition, you can display a single record very easily if you simply enter that record's ID, or any other information that is unique to that record.

    Figure 11. A filtered location list

    Now, here is the real power of the Edit Locations function. Simply click the Edit icon that corresponds to the location you want to edit, and Data Manager displays a form for editing the existing record data--essentially the same form you would use for adding a new location.

    I am going to update the second record. After I click Edit next to record number 2, I notice that I previously misspelled "District of Columbia" as "Distric of Columbia," and I definitely want to change this.

    Figure 12. Editing a record

    After correcting my misspelling, I simply click DONE-UPDATE LOCATION, and my location data is saved in my staging area. It's that simple.

    Did you notice the Move Location link below the thumbnail map of your location? This is yet another way to access the interactive movement feature I mentioned earlier, a feature that I will cover very soon.

    Deleting an Existing Location

    On occasion, you might find that you want to remove a location from your MapQuest-hosted location table. The simplest way to do this is to locate the record, and then simply delete it using the Delete option in Data Manager.

    To delete a location you must first find it using the same method explained earlier, in the "Editing an Existing Location" section. First, click Edit Locations to display the Find Locations page. Enter enough information to find the location record you want to delete, and then click Find Locations. In my case, I am going to delete a record I added earlier in this article, the "International Spy Museum."

    Because there are only three records in my staging table, I am simply going to click Find Locations without typing any search criteria. The result is that all of my locations are displayed.

    Figure 13. Displaying all locations

    Now all I have to do is click the Delete icon in the row corresponding to record number 3, the "International Spy Museum" record I added earlier. When I do this, Data Manager displays a warning page, asking me to verify that I really want to delete the record.

    Figure 14. Verifying record deletion

    Because I really do want to delete this record, I click YES, which immediately removes it from my staging database. When the record is deleted, Data Manager confirms the deletion and then asks whether I want to return to my list of locations or push my changes to production. Although I have now deleted the record I added earlier, I am going to go ahead and push my staging database to production, because I also corrected a misspelling in another record.

    Moving an Existing Location

    Now let me show you the coolest feature that Data Manager offers for data maintenance. No matter how you get here, the Move Location page is a great way to visually refine an already geocoded location. In my example, below, I click Edit Locations on the menu, click Find Locations to display all my locations (all two of them!), and then click the Move icon beside my record with the ID "0000000020," which happens to be the Civil War statue.

    Figure 15. Moving a location

    The page that Data Manager displays includes a map of the location based on the current geocoding, along with two options: Enter a latitude and longitude or Re-geocode with MapQuest. Before I explain these two options, however, I want to show you just how cool this page really is.

    In many cases the location you want to enter does not lie at a specific house number on a street, or at a street intersection. In these cases you might think you either need to know the exact latitude and longitude (Lat/Lng) of the location, or need to settle for a street address that is only close to the real destination. This is not the case at all.

    Data Manager's Move Location provides an interactive means of more accurately identifying where your location really lies on the map. In the case of the Civil War statue that I selected earlier, the street address does not quite accurately represent where the statue is physically located, so I will use the map itself to better locate it.

    The first thing I do is zoom in as closely as possible using the zoom control on the right of the map; this will give me the best chance to identify the true location of the statue. The next thing I do is click the map in the general area of where I know the statue to be. Realize that the best you can hope to do here is improve the placement of your location on the map--you probably won't get it exactly perfect. You would need to know the Lat/Lng for the location to get it perfect.

    My updated location is displayed in Figure 16.

    Figure 16. Updated location

    Now it is your turn! Click one of the locations that you entered and then click the map to move around the icon.

    It might take you a few iterations of clicking the map and seeing where the little blue star moves before you get it just the way you want it. When you have the star in the correct location, simply click UPDATE LOCATION. Note that even though an updated Lat/Lng has now been stored for your location, your street address information will not be updated because you have effectively circumvented this information by directly placing your location on the map.

    What happens if you mess up the location really badly and want to move that little blue star back to where you started? Simply click Re-geocode with MapQuest. This geocodes the location according to the street address information currently stored for this location. In other words, it takes you back to where you started--no harm done.

    The other option on the Move Location page is specifically for those of you who happen to know the exact Lat/Lng of your location. If you click this option, a dialog box is displayed, in which you can enter the Lat/Lng of your location.

    Figure 17. Entering latitude and longitude

    Incremental Batch Files

    Before leaving Data Manager I want to cover one last topic--incremental batch files.

    Not only does Data Manager provide the means for uploading a batch of multiple locations at once, as I demonstrated in the first article in this series, it also allows you to use essentially the same mechanism to add new records, and to change and delete existing records. (If you need to, refer to the first article in this series for a review of how to create a batch file that contains location data for uploading.)

    The only difference between a regular batch file and an "incremental" batch file is that uploading an incremental file does not, in and of itself, remove any locations in the currently selected table. An incremental batch file is very close in format to a normal batch file. The main difference is that an additional field appears on each row of an incremental batch file. For example, consider the following incremental file:

    c,"0000000020","Statue of Dudes on Horses","100 Maryland Ave SW","Washington","District of Columbia","DC","20224","US","","","","","","","2"
    a,"0000000030","Space Shuttle US Enterprise","14390 Air and Space Museum Parkway","Chantilly","Fairfax","VA","20151","US","","","","","","http://www.nasm.si.edu/udvarhazy/","1"
    a,"0000000040","The White House","1600 Pennsylvania Ave","Washington","District of Columbia","DC","20500","US","","","","","","http://www.whitehouse.gov","2"
    

    You'll notice that this file looks very similar to the batch file I used in my first article, except for the additional field at the beginning of each row. Note there are two rows that start with a, and one that starts with c. This additional field at the start of each row is how you tell Data Manager that you want to either (a)dd a new record, or (c)hange an existing record. In addition to adding two new records in this batch file, I have changed the description of the statue to the more playful "Statue of Dudes on Horses"--everything else remains the same.

    CAUTION: An incremental batch file is loaded into Data Manager the same way a regular batch file is loaded, with one important distinction: be sure that the file extension on your incremental file is .acd instead of .txt, because this gives Data Manager a heads-up about the type of file you are about to upload. Other than that, use the Upload File option to upload your incremental file, just as you did when loading a regular batch file in the first article.

    After pushing this incremental file to my table using the Upload File option, and waiting patiently for an e-mail that indicates my upload has been processed, the following results are displayed:

    Figure 18. Updated locations

    Not only are there two new locations, but the description of the second record has been changed based on the new description I added to the incremental file. Quite nifty, isn't it?

    In addition to using an incremental batch file to add new records and change existing records, you can also use the file to delete existing records. To accomplish this you simply create a batch file that contains records with only two fields: d followed by the record ID of the record you want to delete. For instance, if I want to remove the first and third records displayed above, the contents of my incremental batch file will be as follows:

    d,"0000000010"
    d,"0000000030"
    

    Notice that there is no need to include any other information besides d and the record ID.

    I am not going to actually upload this incremental batch file, because I only have four locations in my table as it is, but you should feel free to experiment by uploading an incremental file that deletes records in your table.

    One final note: all of the changes we have made, both interactively and using the incremental batch file, are changes to the staging table only. Be certain to push any changes to production that you want to use in your application; I have done the same.

    Ready to Trek!

    Hopefully you now have a much greater appreciation for Data Manager, and the host of options it provides for getting your custom location data loaded on the MapQuest servers. In the next article we will (finally) dive right in and start work on the PhotoTrek application that will use this location data.

    See you then!

  • Adding Rollover Functionality to Overlays: Part 1

    Over the next couple of posts I'm going to look at the different rollover capabilities that are available for overlays in the MapQuest JavaScript API. To get started I'll post a simple module that can be imported into any application. To demonstrate, I have included links to an application that uses the module. The original Capture the Flag application can be seen here, and with the new functionality, here.

    The module accomplishes two things. First, it uses the altState fields of the overlay to change the alpha value of the overlay, making it more transparent. Second, it accepts a string as an argument, using that as a title which is displayed in a rollover window. The following image is a screenshot of the Capture the Flag application, shown with the mouse hovering over the overlay on the left.

    Capture The Flag Screenshot.

    As you can see from the included code (Listing 2), mouseover and mouseout event handlers are attached to the overlay. When the mouseover event occurs, the rollover window is displayed and the altStateFlag is set to true. The altStateFlag causes any of the altState fields that have been set to be enabled. In the case of this example, that is the altColorAlpha and altFillColorAlpha fields. On mouseout, the altStateFlag is set to false and the rollover window is removed. Note that although I haven't used them in this example, you can similarly set the altColor, altFillColor, and altBorderWidth of the overlay.

    Once the module has been imported into the application, calling the method is very simple. It accepts the current map, the overlay, and a title as arguments. In this example I have called the method using one of three different titles, depending on the color of the overlay.

    Listing 1 - Calling the jrtmq.addRollover method
    
    myBorders.setBorderWidth(3);
    myBorders.setColor(myColors.a);
    myBorders.setFillColor(myColors.b);
    myBorders.setColorAlpha(0.9);
    myBorders.setFillColorAlpha(0.5);
    myBorders.setShapePoints(LLList);
    switch(document.drawMenu.selectColor.value){
      case 'red':
        jrtmq.addRollover(myMap, myBorders, "The Red Zone!!!");
        break;
      case 'green':
        jrtmq.addRollover(myMap, myBorders, "The Green Zone!!!");
        break;
      case 'grey':
        jrtmq.addRollover(myMap, myBorders, "Neutral Zone");
        break;
    }
    
    myMap.addOverlay(myBorders);
      

    You can download the module as a .js file from here. Or simply create your own version of the function below and add it directly to your code.

    Listing 2 - The AddRollover Function
    
    function AddRollover(roMap, roOverlay, roText){
    
      var pricklyPoints = roOverlay.getShapePoints();
      var count = 0;
      var latTotal = 0;
      var longTotal = 0;
      var centerPoint = new MQLatLng();
      var theFlag;
      var icon = new MQMapIcon();
      var newLat;
      var newLong;
    
      // For this method I'm just averaging out the lat and long of the shape points to place
      // the label. There are more complex ways to do this if you're interested in the
      // centroid (weighted center) or some other means of placing the label in relation to the
      // shape. This, however, should work just fine for the current purposes.
    
      for(var i = 0;i<pricklyPoints.getSize();i++){
        count++;
        latTotal = latTotal + pricklyPoints.getAt(i).getLatitude();
        longTotal = longTotal + pricklyPoints.getAt(i).getLongitude();
      }
      newLat = latTotal / count;
      newLong = longTotal / count;
    
      centerPoint.setLatLng(newLat,newLong);
      theFlag = new MQPoi(centerPoint);
    
      // Get rid of the POI icon, as it's the overlay we're associating it with
      // that is important.
      icon.setImage('',0,0);
      icon.setShadow('');
      theFlag.setIcon(icon);
    
      if(!roText){
        theFlag.setInfoTitleHTML('Default Label');
      } else{
        theFlag.setInfoTitleHTML(roText);
      }
    
      // The AltColorAlpha and AltFillColorAlpha properties are available for any MQShapeOverlay.
      // They respond to the AltStateFlag and along with the AltColor and AltFillColor
      // properties allow you to very simply add a rollover response to any overlay.
      roOverlay.setAltColorAlpha(roOverlay.getAltColorAlpha() - 0.2);
      roOverlay.setAltFillColorAlpha(roOverlay.getAltFillColorAlpha() - 0.2);
    
      MQEventManager.addListener(roOverlay, "mouseover", function(){
    
        // While the mouse is hovering over the overlay we want the rollover window
        // to be displayed, and the AltStateFlag to be true, enabling any of the
        // Alt properties that were set for the overlay. In this case, just the alphas.
        theFlag.showRolloverWindow();
        roOverlay.setAltStateFlag(true);
      });
      MQEventManager.addListener(roOverlay, "mouseout", function(){
    
        // Because there is only one rollover window for each map,
        // the method to hide the window is the same, regardless of
        // what object opened the window.
        roMap.getRolloverWindow().hide();
        roOverlay.setAltStateFlag(false);
      });
    
      // Because this method simply borrows the rollover window from a POI,
      // we need to watch for the overlay to be removed so we can also remove
      // the POI.
      MQEventManager.addListener(roOverlay, "removed", function(){
        roMap.removePoi(theFlag);
      });
    
      roMap.addPoi(theFlag);
    }
    	
  • MapQuest Developer Blog is on Alltop

    Alltop is a site that creates category "dashboards" of "all the top" sites on the Internet in both tech and non-tech related categories. They just launched a category for programming information at http://programming.alltop.com. The MapQuest Developer Blog is among the helpful programming resources listed.

    Our thanks to Alltop! Give the site a look.

  • JavaScript API 5.3RC3 Released: Traffic, Remote Collections and More!

    This morning we released an update to the MapQuest JavaScript API. Version 5.3, Release Candidate 3 contains the following new functionality:

    • Drop Shadow setting for the map: We've added a visual drop-shadow graphic to the map, that you can turn on by calling map.setMapShadowState(boolean). This shadow is off by default.
    • Remote Collections: KML and GeoRSS support built into the API! Create a RemoteCollection, tell it the location of the feed and its format, and watch it get automagically sucked onto the map! If you have another format, feel free to extend the feed loading classes to create your own loadable formats.
    • Min/Max Zoom levels on POIs: You can now set minimum and maximum zoom levels on POIs. The POI would then only be visible on the map between the set zoom levels.
      poi.setValue('minZoomLevel', x) poi.setValue('maxZoomLevel', x)
    • Show Traffic Flow on the Map: You can now add traffic flow to your map. This is the first step of adding traffic functionality into the API - more will follow.
    • Show Traffic/Incident POIs on the Map: You can now add traffic incident POIs to your map. This is the second step of adding traffic functionality into the API - more will follow.

    Are you starting to see a pattern here?

    To use this version, change the version parameter in the API request to "v=5.3.0_RC3":
    <script src="http://btilelog.access.mapquest.com/tilelog/ transaction?
    transaction=script&key=YOUR_KEY_HERE&ipr=true&itk=true
    &v=5.3.0_RC3" type="text/javascript"></script>

  • MapNews - A Map Based News Browser

    Part 1 - Concept and Prototype

    "You've got your chocolate in my peanut butter! You've got your peanut butter on my chocolate!" - Reese's Peanut Butter Cups commercial.

    Two guys in the Reese's commercial found out how much fun it is to combine your two favorite things. I, too, have discovered this phenomenon. For me, the combination is a mashup of maps and news. While this mixture might not be quite as good as a Reese's Peanut Butter Cup, it's pretty close in my book.

    I have always enjoyed the newspaper section where you can read about what is going on your state or other states. Following this concept, my idea is to display a map with markers and information windows on places for which news is available. The information window will show headlines and allow click-through to the underlying story.

    The source of news I plan to use is AOL RSS news feeds. The RSS feeds often have a place name associated with a news items. I'll extract and geocode the locations and then populate the map with markers for newsworthy places.

    This is my first time using the MapQuest APIs so I tried a few baby steps to get the development process moving. I created the following static prototype of MapNews:

    This prototype shows details for a particular zip code. I've decided that this isn't quite the right model as this is pulling in news for a selected location. It seems to me that a better model is a push model. The map should be zoomed out to the entire US to show which locations have associated news. You can then click on the locations that appeal to you and view the headlines.

    The other concept shown is selection of news channels (national, business, and sports). These should map directly to specific RSS feeds, so I'll keep that.

    The process for building this prototype was straightforward. I created an account at the MapQuest Technical Resource Center and obtained an API key and downloads of the Javascript API and documentation. I was able to produce this prototype after reading the first couple of pages in the Advantage API Javascript Developer Guide. I, therefore, won't cover those details here.

    I worked with the HTML page on my local file system without moving the page to a web server. This required configuring my account to allow blank referers. After making the account changes, it takes an hour or so for the settings to propagate to the MapQuest servers. Allowing blank referrers should be avoided as it allows anyone to obtain and use your API key. Correcting this is high on my To Do list as I build out MapNews.

    The next step is developing a design to identify the components and architecture I'll use to bring MapNews to life.

  • Map It! - Building a MapQuest Mac OS X Dashboard Widget - Part 2

    That is the exploration that awaits you! Not mapping stars and studying nebula, but charting the unknown possibilities of existence. - Leonard Nimoy

    In Part 1, I provided an overview of the MapQuest Platform, and provided instructions on how to obtain your own developer key. The developer key is required to integrate MapQuest into your application through the JavaScript API.

    About Map It!

    In Part 2 and 3 of building the Map It! Application, I will be creating the Mac OS X Dashboard widget shown in Figure 1.

    Figure 1

    Users enter an address in the search box at the top of the widget, and that point is automatically plotted and zoomed in on the map below. Users can switch between map, satellite, and hybrid views. They can also use the controls on the left to navigate and move around the map.

    I've discussed building Mac OS X Dashboard widgets previously on my AOL Developer Network blog, here and here. For the Map It! Dashboard widget - I used Apple's Dashcode to jumpstart my development. Even though I used Dashcode - the techniques to integrate the MapQuest Platform, I'll review here can be utilized however you develop Dashboard Widgets.

    It's important to understand that Dashboard Widgets are mini Web 2.0 applications - with their programmatic interface coming from JavaScript - so the JavaScript API is almost (I explain why it's almost in Part 3) perfect fit! All of the techniques I review in building Map It! can be used by Web 2.0 developers as well!

    Adding the MapQuest JavaScript Library

    In the main widget HTML page you'll need to include the following SCRIPT tag to import the JavaScript library:

    <script src="http://btilelog.access.mapquest.com/tilelog/transaction?transaction=script&key=**YOUR-KEY**ipr=true&itk=true&v=5.2.0" type="text/javascript"></script>
    

    You need to replace **YOUR-KEY** with the key that was provided in the email when you signed up for the MapQuest Platform, as discussed in Part 1.

    Creating the User Interface

    To place the map on the widget - you'll need to create a DIV to hold the map. In the Map It! application I created the following DIV tag.

    <div id="myMap" style="width: 550px; height: 300px;"></div>
    

    Now that you have a place to display the map, the next step is to get the map displayed in the DIV. This is simply done by create a new MQTileMap object and associating with the DIV, as shown here:

    // Create a MQTileMap object and display it in the myMap DIV
    myMap = new MQTileMap(document.getElementById('myMap'));
    

    The final step is to add the Large Zoom and view controls. These controls allow the user to zoom in and out of the map, and select the type of map that is displayed (road, satellite, or hybrid). This is accomplished with the following code:

    // create a new Large Zoom Control
    var myLZControl = new MQLargeZoomControl(myMap);
    
    // add it to the map at the specified offset from the Top Left corner
     myMap.addControl(myLZControl, new MQMapCornerPlacement(MQMapCorner.TOP_LEFT, new MQSize(1,1)));
    
    // create a new View Control
    var myVControl = new MQViewControl(myMap);
    
    // add it to the map at the specified offset from the Top Right corner
    myMap.addControl(myVControl, new MQMapCornerPlacement(MQMapCorner.TOP_RIGHT, new MQSize(20,20)));
    

    That's all that you need to add a map!

    Referrers

    While Dashboard widgets function just like typical Web 2.0 applications, they have two shortcomings that need to be overcome when working with the MapQuest Platform. First, when including the JavaScript library, your key maps back to the information provided when you signed up for developer access. Since Dashboard widgets run locally from your desktop they will not have a referrer. As shown in Figure 2, you'll need to add the referrer * and enable blank referrers. It's important to understand the risks of enabling blank referrers so read the Warning not carefully. Since we're not running the Dashboard widget from a web server - as all widgets are accessed locally - blank referrers are required. I'll discuss the second shortcoming with Geocoding and Mac OS X Dashboard widgets in the Part 3.

    Figure 2

    Conclusion

    In Part 3 I'll show you how to add Geocoding to the widget to plot a point of interest based on the address entered in the search box. For your reference, here are some references to the MapQuest Platform:

  • Customize Your Trail Maps Using MapQuest and KML

    I've always liked maps, but I've always been disappointed with the content. Sure, knowing road names and where to find a gas station is great. But I'm an outdoors kind of guy, and what I really want to see is cool biking and hiking routes. That's why I'm so excited about Keyhole Markup Language (KML) and the MapQuest Platform; the combination of the two lets users, as opposed to mapmakers, supply map content. Overlaying a map with KML data opens up many possibilities for uploading, sharing, and finding user-generated content. Couple this ability to share data with the vast number of people carrying GPS-enabled devices and you have a perfect storm for sharing off-road routes.

    In this article, I show how to read KML files and create map features by using the MapQuest JavaScript API. The example I'm presenting is a KML file that describes the route for the Appalachian Trail (AT) and the location of shelters hikers use for overnight stays. I've focused on my favorite part of the trail, which is the section between Front Royal, VA, and Harper's Ferry, West Virginia. The source code for the example is listed at the end of this article. The following screen shot shows my custom AT map in action:

    Figure 1. The AT from Front Royal, VA, to Harper's Ferry, WV, showing the location of the trail and overnight shelters

    The trail route is shown in red. The overnight shelters are shown as Point of Interest (POI) stars. Clicking a star opens an information window that lists more details about the shelter:

    Figure 2. Clicking the POI marker opens an information window that lists details about the shelter

    The last screen shot shows the map after zooming in and changing to an aerial image view:

    Figure 3. Aerial view of the David Lesser shelter

    This screen shot shows that the David Lesser shelter is located east of the trail. The screen shot gives just a suggestion of the excellent view from the shelter's porch looking out over the farmlands of Northern Virginia. Just in case you can't tell, I think the David Lesser shelter is the nicest one in this section of the AT.

    In the remainder of this article, I cover some background on KML and the KML files I used to prepare the maps shown here. The following section covers the code and the experiences, some good and some bad, that I had while developing my custom AT map.

    KML

    Wikipedia describes KML as an XML-based schema for expressing geographic annotations of online maps in two or three dimensions. KML is just one of many file formats for interchanging map annotations, which also includes formats for data recorded by GPS-enabled devices. The nice thing about KML is that it is widely supported and has been submitted to the Open Geospatial Consortium for standardization.

    A couple of examples of widespread KML use are 1) searching KML files on AOL if you are interested in a particular topic and 2) aggregation sites that syndicate user-generated content in KML format such as virtualglobetrotting.com, mapufacture.com, and bikely.com. If you have data in a particular format, you can use GPSBabel to convert to and from KML and other data file formats.

    A KML file typically contains elements that describe features, such as camera viewing angles and elevation, that are not relevant for the two-dimensional maps generated with the MapQuest Platform. My approach was to ignore these elements. The code in this article recognizes KML Placemark elements that have a child Point geometry or a LineString geometry. This is best explained by looking an extract from the KML file for the AT data:

    0172 <kml xmlns="http://earth.google.com/kml/2.1">
    0173 <Document>
    0174   <name>Appalachian Trail Centerline</name>
    0175   <description>Exported from at_centerline.shp 3/12/02 downloaded from
    0176     http://www.appalachiantrail.org using City of Portland's Export to
    	   KML 2.3.5
    0177     Go Hokies!
    0178   </description>
    0179   <LookAt>
    0180     <longitude>-78.92943061782194</longitude>
    0181     <latitude>37.87708055555556</latitude>
    0182     <altitude>0</altitude>
    0183     <range>1067501.314867445</range>
    0184     <tilt>32.73615635179154</tilt>
    0185     <heading>1.815747767697585</heading>
    0186   </LookAt>
    0187   <Folder>
    0188     <name>Features</name>
    0189
    0190 <!-- Routes -->
    0191
    0192 <Placemark>
    0193       <name>1778</name>
    0194       <styleUrl>#FEATURES_copy18</styleUrl>
    0195       <LineString>
    0196         <tessellate>1</tessellate>
    0197         <coordinates>
    0198 -78.0494085970235,38.9169067777347,0 -78.0494323854585,38.9169149040082,0 ...
    	 </coordinates>
    0199       </LineString>
    0200     </Placemark>
    
    ...
    
    1004 <!-- Shelters -- >
    1005
    1006     <Placemark >
    1007       <description>Notes:  Sheltered picnic table, fireplace, and privy;
    1008       spring
    1009
    1010 Fee:  No
    1011
    1012 Capacity:  6
    1013
    1014 Maintained By:  Potomac Appalachian Trail Club</description>
    1015       <name>David Lesser Shelter</name>
    1016       <View>
    1017         <longitude>-77.77954</longitude>
    1018         <latitude>39.22718</latitude>
    1019         <range>999.9999999999999</range>
    1020         <tilt>0</tilt>
    1021         <heading>0</heading>
    1022       </View>
    1023       <visibility>1</visibility>
    1024       <styleUrl>#cloned1</styleUrl>
    1025       <Point>
    1026         <coordinates>-77.77954,39.22718,607.9747496823218</coordinates>
    1027       </Point>
    1028       <styleURL>#cloned0</styleURL>
    1029     </Placemark>
    
    ...
    
    1182   </Folder>
    1183 </Document>
    1184 </kml>
    

    Lines 179-186 describe a viewing angle for a three-dimensional viewer. Lines 192-200 are a Placemark that describes a section of the trail route. The example code maps this Placemark to a line overlay on the map. The second Placemark, on lines 1006-1026, describes a shelter and associated three-dimensional location coordinates (on lines 1025-1027). I mapped these elements to a POI and used the longitude and latitude elements of the coordinates.

    There are a few features of KML to keep in mind when writing code to consume KML. The first is that the file can be quite large and KML files are often stored in a zipped file with a .kmz extension. The file for the AT trail route is close to 15 MB. Therefore, the code to traverse KML needs to be as efficient as possible. The other feature of KML worth noting is that container elements such as Folder can be nested. KML document object model (DOM) tree-traversal routines need to be ready for arbitrary nesting of elements.

    The AT trail data I used came in two files: one for shelter locations and one for the center line of the trail route. The trail route file was just too large to handle with client-side JavaScript. To construct a reasonable-size sample, I extracted features lying between Front Royal and Harper's Ferry using the Perl XML::XPath library and consolidated the extracted features into one shorter file. The following listing shows the Perl code I used to extract Placemark elements for my region of interest:

    #!/usr/bin/perl -w
    
    use strict;
    use XML::XPath;
    use XML::XPath::XMLParser;
    
    my $xp = XML::XPath->new(filename => 'doc.kml');
    my $placemarks = $xp->find('/kml/Document/Folder/Placemark');
    foreach my $placemark ($placemarks->get_nodelist) {
      my $coordinates = $xp->find('./LineString/coordinates', $placemark);
      foreach my $c ($coordinates->get_nodelist) {
        my $text_nodes = $xp->find('child::text()', $c);
        my $sv = '';
        foreach my $t ($text_nodes->get_nodelist) {
          $sv .=  $t->string_value;
        }
        $sv =~ s/^\s*//;
        my @coords = split /\s+/, $sv;
        my ($lng, $lat, @rest) = split ',', $coords[0];
    
        #harpersFerry - (39.32, -77.72) - frontRoyal - (38.90, -78.05);
        if( (38.90 <= $lat) and ($lat <= 39.32) and
            (-78.05 <= $lng) and ($lng <= -77.72)) {
          print XML::XPath::XMLParser::as_string($placemark) . "\n";
        }
      }
    }
    

    KML Import JavaScript Code

    I broke the task of reading the KML DOM document into two classes: TreeWalker (lines 28-61) and MapMediator (lines 64-159). The TreeWalker class is responsible for traversing the DOM tree and passing MapMediator interesting KML nodes. The MapMediator class is responsible for creating MapQuest objects on the map for the corresponding KML elements.

    As I mentioned earlier, KML files can be quite large, so I made a couple of optimizations for efficient KML DOM tree traversal. The processing model I use here is client-side JavaScript, so there will be limits to the reasonable file size; however, I aimed to accommodate large files.

    To support large files, the first decision I made was to make only one pass through the KML DOM. This choice is opposed to a pull type of model in which one queries the DOM for interesting elements. (Examples of pull processing might include calls such as document.getElemntByTagName() or JavaScript Prototype Framework methods like $('Placemark Point');.) Each pull query might involve traversing all or part of the tree multiple times, which a one-pass approach avoids.

    To support a one-pass model, I implemented a depth-first, document-order traversal that scans the KML DOM in the same order as I tend to read the XML (top to bottom). The algorithm is as follows: 1) visit the first node (and send the node to MapMediator), 2) push the node's children onto a stack in reverse document order, and 3) pop the stack and repeat from step 1. The main route for the traversal is:

    0050         while(this.stack.size() > 0) {
    0051           var node = this.stack.pop();
    0052           this.handler.handleElement(node);
    0053           if(this.handler.prune(node)) continue;
    0054           if(node.hasChildNodes()) {
    0055             for(var i = node.childNodes.length; i > 0; i--) {
    0056               this.pushIfElement(node.childNodes.item(i - 1));
    0057             }
    0058           }
    0059         }
    

    The second optimization I made was to pass only element nodes to MapMediator (see MapMediator pushIfElement() on Line 37). This approach avoids many method calls for text and attribute nodes. MapMediator can always directly query for these types of nodes when it receives a DOM element.

    The final optimization I made for traversing the tree involves the idea of pruning the tree traversal. When processing elements such as a KML Placemark, I noticed that there are many child elements that are not relevant for a map. It looked to be more efficient for MapMediator to query directly for the relevant children when passed a Placemark element. After TreeWalker allows MapMediator to visit an element, it calls the MapMediator.prune(element) (line 53). If MapMediator returns true, the tree traversal is pruned at that element.

    The time to traverse the 300 Placemarks in the AT shelter file was less than half a second. I wasn't able to traverse the full AT route file in a reasonable amount of time. That's fine, because processing a 15 MB XML file in the browser doesn't make much sense.

    The callback from TreeWalker to MapMediator is handleElement():

    0070       // callback from TreeWalker
    0071       handleElement: function (node) {
    0072         if (node.nodeType == Node.ELEMENT_NODE) {
    0073           if('Placemark'.toLowerCase() == node.nodeName.toLowerCase()) {
    0074             this.placemark(node);
    0075           }
    0076         }
    0077       },
    

    As I mentioned earlier, MapMediator creates MapQuest objects for Placemarks elements only. The code for creating MapQuest objects from KML elements is contained in the method placemark():

    
    0088       // Create map objects for place marks
    0089       placemark: function(node) {
    0090
    0091         // find interesting children of placemark
    0092         var name = this.getChildText(this.getFirstChild(node, 'name'));
    0093         var description = this.getChildText(this.getFirstChild(node,
    		 'description'));
    0094         var point = this.getFirstChild(node, 'Point');
    0095         var lineString = this.getFirstChild(node, 'lineString');
    0096         var rawCoordinates = this.getChildText(
    0097           this.getFirstChild(point ? point : lineString, 'coordinates')
    0098         );
    0099         var coordinates = this.parseCoordinates(rawCoordinates);
    0100
    0101         // Create a MQ POI for KML points and MQ line overlay for KML
    		 LineStrings
    0102         // KML is (lng,lat), MQ is (lat,lng)
    0103         if(point) {
    0104           var poi = new MQPoi(new MQLatLng(coordinates[1], coordinates[0]));
    0105           poi.setInfoTitleHTML(name ? name : '');
    0106           poi.setInfoContentHTML(description ? description : '');
    0107           this.map.addPoi(poi);
    0108         } else if(lineString) {
    0109           var points = new MQLatLngCollection();
    0110           for(var i = 0; i < coordinates.length; i = i + 3) {
    0111             points.add(new MQLatLng(coordinates[i + 1],coordinates[i]));
    0112           }
    0113           var lineOverlay = this.getLineOverlay();
    0114           lineOverlay.setShapePoints(points);
    0115           this.map.addOverlay(lineOverlay);
    0116         }
    0117       },
    

    Lines 92-99 pull the child elements of the Placemark element that will be used when creating the corresponding MapQuest object. For POIs, the name and description are used to populate the information window. In both cases, the coordinates are fetched for locating the MapQuest object. For a lineString element, the coordinates are an array of (longitude, latitude, and altitude) tuples. POI markers are generated for KML points in lines 104-107. Lines 109-115 add a line overlay for the AT route information.

    The balance of the MapMediator class is given over to utility routines. The method getLineOverlay() returns a template MQLineOverlay with styling information and no location information. The method parseCoordinates() converts the text contained within the KML coordinates elements into a flat list of floats; each set of three elements in the list corresponds to a KML location tuple.

    The Long Story

    When I started this project, I envisioned that I would load the KML file using an Asynchronous JavaScript and XML (AJAX) call after the map was created. This means that MapQuest would show the map first, then add in POI markers and line overlays after the map was rendered. I figured this would keep the user happy during a potentially long KML parsing cycle.

    I started with adding the POI markers for the shelters first. I used the Prototype AJAX class to fetch the KML file. After configuring my server's content-type header for KML files to ''application/vnd.google-earth.kml+xml" the AJAX parsing worked well. The main stumbling block I had was that Prototype's DOM traversal routines didn't seem to work if the XML DOM tree was not made part of the document DOM tree. I got past this problem by not using the Prototype routines and instead using the standard DOM API.

    Then, I included code to add the AT route as a series of MapQuest line overlays and found out that my AJAX fetching of the KML wouldn't work anymore. The reason for this problem is tied to MapQuest's use of Dojo for maps with overlays. Map creation happens during a callback from Dojo (see MQInitDojo() on line 165). Dojo is loaded by the MapQuest API, so calling my server for the KML file from Dojo event callbacks was not possible. Without using Dojo event callbacks, I couldn't be sure of when the map was created and initialized.

    One solution might be to fetch and parse the KML and store an intermediate representation in the browser before loading the map. Then, in the map creation callback, use the stored representation to populate the map. Another solution might involve loading Dojo from your server and possibly a proxy to call out for KML files in other domains.

    In the example code, I simply included the KML in a hidden <div> in the HTML document. This solution seemed to work well enough and is a reasonable approach, especially if you have fixed content that can be included via server-side templates. I did observe that some Prototype functions failed to match XML element names. Inspection in Firebug showed that the reported tag names were uppercase for some KML elements. This was strange; in any case, I resorted to String.toLowerCase() for tag name comparison.

    Conclusions

    I've demonstrated how to use the MapQuest JavaScript API to load basic map features from a KML file and create corresponding map objects. However, KML is a rich language and there are many more features that can be rendered on a MapQuest map. There are a few more features in KML for which consumption would be a straightforward extension of what I've presented here.

    I've covered the handling of basic KML elements for point and line annotations. There are several different geometries that could be handled with map overlays, such as linear rings and polygons. KML also has several elements for specifying style information, such as the color or weight of lines, and even the icons used for map elements.

    Even with the minimal amount of KML elements that I've processed, I'm very happy with the maps I was able to create. I look at the AT maps I created and have to say, "Those are my kind of maps." Have fun with the new API and enjoy your own custom map.

    Full Source Code Listing

    0001 <html>
    0002   <head>
    0003     <title>MapQuest KML Import</title>
    0004     <link rel="stylesheet" href="kml.css" type="text/css"/>
    0005     <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>
    0006     <script src='prototype-1.6.0.2.js' type='text/javascript'></script>
    0007     <script language="javascript">
    0008
    0009       function initMap()
    0010       {
    0011         var mapCenter = new MQLatLng(39.11, -77.89);
    0012         var map = new MQTileMap(document.getElementById('mapWindow'), 7,
    		 mapCenter, "map");
    0013
    0014         map.addControl(new MQLargeZoomControl(map));
    0015         var vc = new MQViewControl(map);
    0016         map.addControl(vc, new MQMapCornerPlacement(
    0017           MQMapCorner.BOTTOM_RIGHT, new MQSize(20,20)
    0018         ));
    0019
    0020         try {
    0021           new TreeWalker($('kml'),  new MapMediator(map)).depthFirstWalk();
    0022         } catch (e) {
    0023           alert(e);
    0024         }
    0025       }
    0026
    0027     // Class to traverse KML DOM
    0028     function TreeWalker(startNode, handler){
    0029       this.startNode = startNode;
    0030       this.handler = handler;
    0031     }
    0032
    0033     TreeWalker.prototype = {
    0034       stack: null,
    0035
    0036       // process only element nodes
    0037       pushIfElement: function(node) {
    0038         if(node.nodeType == Node.ELEMENT_NODE) this.stack.push(node);
    0039       },
    0040
    0041       // Depth first, document order traversal of DOM
    0042       depthFirstWalk: function() {
    0043         this.stack = [];
    0044         this.handler.handleElement(this.startNode);
    0045         if(this.startNode.hasChildNodes()) {
    0046           for(var ci = this.startNode.childNodes.length; ci > 0; ci--) {
    0047             this.pushIfElement(this.startNode.childNodes.item(ci - 1));
    0048           }
    0049         }
    0050         while(this.stack.size() > 0) {
    0051           var node = this.stack.pop();
    0052           this.handler.handleElement(node);
    0053           if(this.handler.prune(node)) continue;
    0054           if(node.hasChildNodes()) {
    0055             for(var i = node.childNodes.length; i > 0; i--) {
    0056               this.pushIfElement(node.childNodes.item(i - 1));
    0057             }
    0058           }
    0059         }
    0060       }
    0061     }
    0062
    0063     // Class to create MQ objects from KML elements
    0064     function MapMediator(map) {
    0065       this.map = map;
    0066     }
    0067
    0068     MapMediator.prototype = {
    0069
    0070       // callback from TreeWalker
    0071       handleElement: function (node) {
    0072         if (node.nodeType == Node.ELEMENT_NODE) {
    0073           if('Placemark'.toLowerCase() == node.nodeName.toLowerCase()) {
    0074             this.placemark(node);
    0075           }
    0076         }
    0077       },
    0078
    0079       // stop tree walk at placemarks
    0080       prune: function(node) {
    0081         var shouldPrune = false;
    0082         if('Placemark' == node.nodeName) {
    0083           shouldPrune = true;
    0084         }
    0085         return shouldPrune;
    0086       },
    0087
    0088       // Create map objects for place marks
    0089       placemark: function(node) {
    0090
    0091         // find interesting children of placemark
    0092         var name = this.getChildText(this.getFirstChild(node, 'name'));
    0093         var description = this.getChildText(this.getFirstChild(node,
    		 'description'));
    0094         var point = this.getFirstChild(node, 'Point');
    0095         var lineString = this.getFirstChild(node, 'lineString');
    0096         var rawCoordinates = this.getChildText(
    0097           this.getFirstChild(point ? point : lineString, 'coordinates')
    0098         );
    0099         var coordinates = this.parseCoordinates(rawCoordinates);
    0100
    0101         // Create a MQ POI for KML points and MQ line overlay for KML
    			LineStrings
    0102         // KML is (lng,lat), MQ is (lat,lng)
    0103         if(point) {
    0104           var poi = new MQPoi(new MQLatLng(coordinates[1], coordinates[0]));
    0105           poi.setInfoTitleHTML(name ? name : '');
    0106           poi.setInfoContentHTML(description ? description : '');
    0107           this.map.addPoi(poi);
    0108         } else if(lineString) {
    0109           var points = new MQLatLngCollection();
    0110           for(var i = 0; i < coordinates.length; i = i + 3) {
    0111             points.add(new MQLatLng(coordinates[i + 1],coordinates[i]));
    0112           }
    0113           var lineOverlay = this.getLineOverlay();
    0114           lineOverlay.setShapePoints(points);
    0115           this.map.addOverlay(lineOverlay);
    0116         }
    0117       },
    0118
    0119       // convert KML coordinate string to a flat array of floats.
    0120       parseCoordinates: function(coordinateString) {
    0121         var coordinates = [];
    0122         var tuples =
    0123           coordinateString.replace(/^\s*/, '').replace(/\s*$/,
    		 '').split(/\s+/);
    0124         for(var i = 0; i < tuples.length; i++) {
    0125           var strs = tuples[i].split(',');
    0126           for(var j = 0; j < 3; j++) {
    0127             if(j < strs.length) {
    0128               coordinates.push(parseFloat(strs[j]));
    0129             } else {
    0130               // elevation is optional in KML
    0131               coordinates.push(undefined);
    0132             }
    0133           }
    0134         }
    0135         return coordinates;
    0136       },
    0137
    0138       // return a line overlay without points
    0139       getLineOverlay: function() {
    0140           var lineOverlay = new MQLineOverlay();
    0141           lineOverlay.setColor('#FF0000');
    0142           lineOverlay.setColorAlpha(1.0);
    0143           lineOverlay.setBorderWidth(2);
    0144           return lineOverlay;
    0145       },
    0146
    0147       // fetch text nodes contained by node
    0148       getChildText: function (node) {
    0149           return node
    0150             ? Element.cleanWhitespace(node).firstChild.nodeValue
    0151             : undefined;
    0152       },
    0153
    0154       // find a child element by tag name
    0155       getFirstChild: function(node, tagName) {
    0156         var found = node.getElementsByTagName(tagName).item(0);
    0157         return found;
    0158       }
    0159     }
    0160
    0161     </script>
    0162   </head>
    0163   <body>
    0164     <script>
    0165       MQInitDojo(initMap);
    0166     </script>
    0167     <h1>MapQuest KML Import</h1>
    0168     <hr>
    0169     <div id="mapWindow" style=""></div>
    0170     <hr>
    0171     <div id="kml">
    0172 <kml xmlns="http://earth.google.com/kml/2.1">
    0173 <Document>
    0174   <name>Appalachian Trail Centerline</name>
    0175   <description>Exported from at_centerline.shp 3/12/02 downloaded from
    0176     http://www.appalachiantrail.org using City of Portland's Export to
    		 KML 2.3.5
    0177     Go Hokies!
    0178   </description>
    0179   <LookAt>
    0180     <longitude>-78.92943061782194</longitude>
    0181     <latitude>37.87708055555556</latitude>
    0182     <altitude>0</altitude>
    0183     <range>1067501.314867445</range>
    0184     <tilt>32.73615635179154</tilt>
    0185     <heading>1.815747767697585</heading>
    0186   </LookAt>
    0187   <Folder>
    0188     <name>Features</name>
    0189
    0190 <!-- Routes -->
    0191
    0192 <Placemark>
    0193       <name>1778</name>
    0194       <styleUrl>#FEATURES_copy18</styleUrl>
    0195       <LineString>
    0196         <tessellate>1</tessellate>
    0197         <coordinates>
    0198 -78.0494085970235,38.9169067777347,0 -78.0494323854585,38.9169149040082,0 ...
    	 </coordinates>
    0199       </LineString>
    0200     </Placemark>
    
    ...
    
    1004 <!-- Shelters -- >
    1005
    1006     <Placemark >
    1007       <description>Notes:  Sheltered picnic table, fireplace, and privy;
    1008  spri
    1009
    1010 Fee:  No
    1011
    1012 Capacity:  6
    1013
    1014 Maintained By:  Potomac Appalachian Trail Club</description>
    1015       <name>David Lesser Shelter</name>
    1016       <View>
    1017         <longitude>-77.77954</longitude>
    1018         <latitude>39.22718</latitude>
    1019         <range>999.9999999999999</range>
    1020         <tilt>0</tilt>
    1021         <heading>0</heading>
    1022       </View>
    1023       <visibility>1</visibility>
    1024       <styleUrl>#cloned1</styleUrl>
    1025       <Point>
    1026         <coordinates>-77.77954,39.22718,607.9747496823218</coordinates>
    1027       </Point>
    1028       <styleURL>#cloned0</styleURL>
    1029     </Placemark>
    
    ...
    
    1182   </Folder>
    1183 </Document>
    1184 </kml>
    1185     <div>
    1186   </body>
    1187 </html>
    
  • More Overlay Events

    Custom events are also a great way to 'clean up' the look of your maps. In most of my previous MapQuest posts, I've been only loading a single overlay or polyline at a time. Chances are, you'll encounter the need to load more than one at a time - but also don't want to clutter up the look of the map by having them all visible at the same time.

    Here's an example where I've loaded two different polylines from different GeoRSS/GML files. Instead of having both automatically displayed on the map, I'm going to use a mousever event on a POI to display the polyline. Two do so, I create two POIs has a legend to each polyline. On a 'mouseover' event I load and display the appropriate file. On a 'mouseout' event the overlay is set to not be visible.

    <html>
    <head>
    <title>Overlay Events</title>
    <script src="http://btilelog.access.mapquest.com/tilelog/transaction?
      transaction=script&key=YOUR_API-KEY
      &ipr=true&mp;itk=true&v=5.2.0" type="text/javascript"></script>
    <script language="javascript">
    MQInitDojo(initMap);
    var myOverlayColl = new MQOverlayCollection();
    var myXMLDoc;
    function openXMLFile1(){
        myXMLDoc = document.implementation.createDocument("","",null);
        myXMLDoc.load("./foo1.xml");
        myXMLDoc.onload = loadGeoRSS;
    }
    function openXMLFile2(){
        myXMLDoc = document.implementation.createDocument("","",null);
        myXMLDoc.load("./foo2.xml");
        myXMLDoc.onload = loadGeoRSS;
    }
    function loadGeoRSS() {
        myOL = new MQLineOverlay();
        for (i = 0; i < myXMLDoc.getElementsByTagName("entry").length; i++) {
            var c = myXMLDoc.getElementsByTagNameNS("http://www.opengis.net/gml",
             "posList")[i].textContent;
            var tmp = c.split(/ /);
            var myShapePts = new MQLatLngCollection();
            for (j = 0; j > tmp.length; j++) {
                myShapePts.add(new MQLatLng(tmp[j], tmp[j+1]));
                j++;
            }
            myOL.setShapePoints(myShapePts);
            myOverlayColl.add(myOL);
            myMap.replaceOverlays(myOverlayColl);
    	myOL.setColor("#800000");
            myOL.setBorderWidth(1);
    	myOL.setColorAlpha(1.0);
        }
    }
    function loadData() {
        myPoint1 = new MQPoi(new MQLatLng(33.305389, -116.879258));
        myPoint1.setInfoTitleHTML("Palomar Mountain South Grade");
        myMap.addPoi(myPoint1);
        MQEventManager.addListener(myPoint1,'mouseover', openXMLFile1);
        MQEventManager.addListener(myPoint1,'mouseout', clear);
    
        myPoint2 = new MQPoi(new MQLatLng(33.288569, -116.805051));
        myPoint2.setInfoTitleHTML("Palomar Mountain East Grade");
        myMap.addPoi(myPoint2);
        MQEventManager.addListener(myPoint2,'mouseover', openXMLFile2);
        MQEventManager.addListener(myPoint2,'mouseout', clear);
    }
    function clear() {
        myOL.setVisible(false);
    }
    function initMap() {
       myMap = new MQTileMap(document.getElementById('mapDiv'),
         8,new MQLatLng(33.272866, -116.831869));
       myMap.addControl(new MQLargeZoomControl(myMap));
       loadData();
    }
    </script>
    </head>
    <body>
    <div id="mapDiv" style="width:384px; height:384px; border:2px solid"></div>
    </body>
    </html>
    

    Here's a screenshot of the results:

    More resources:

    * Map data courtesy SundayMorningRides.com

  • JavaScript API Updated to 5.2.1

    This morning we released an update to our JavaScript API. Version 5.2.1 uses DOJO 1.0.2, which fixes a bug in the original DOJO 0.9.0 GFX library that could cause line overlays to "jump" at certain zoom levels. If you do not use overlays in the 5.2 version of the JavaScript API, you do not need to make any changes.

    To update, simply change the version parameter to "v=5.2.1":
    <script src="http://btilelog.access.mapquest.com/tilelog/ transaction?
    transaction=script&key=YOUR_KEY_HERE&ipr=true&itk=true& v=5.2.1" type="text/javascript"></script>

  • South By Southwest 2008 Recap

    Thank you Austin!

    We just got back from a great couple of days at SXSW Interactive where we announced our new MapQuest Platform: Free Edition.

    It was great to talk with developers and that people were excited to see us doing our part to rock Austin, TX. We also got to make a lot of new friends at the show and other events around town. We showed off a bunch of demos in the booth and in case you missed your flight or otherwise wanted to check them out further, you can currently find them here: MapQuest Platform: SXSW Demos. You can download the code samples to help you get started on your own applications from that page as well.

    SXSW Demo Screenshot

    We also would like to thank everyone at SXSW Interactive, all of the organizers and sponsors of the SXSW Geeks Love Bowling Event and of course everyone who came by to talk to us. We appreciate all the feedback, support and can't wait to see you all next year.

    One last thing...

    Remember that our Platform is much better than our bowling: