Javascript Concepts

Google Maps API Tutorial

Javascript Concepts: Asynchronous I/O

In most computer languages, when you tell the program to read or write some data, the processing pauses until that action completes. The results of the read operation are available for use by the next statement in the program.

Most read and write operations in Javascript don’t work like that. Javascript makes a request to the I/O system for the data to be read or written and then continues to execute the following statements without waiting for the action to complete. When the action is completed, a corresponding completion event will be triggered.

If you want to be informed of the I/O completion, e.g. if you want to process the data that’s been read, you have to supply a callback function which will be called when the I/O completes. Or, in some cases, listen for an event that indicates that the operation is complete.

This makes sense when you consider that I/O operations which involve exchanging packets of data with Internet servers in distant parts of the world may take thousands of times longer to complete than I/O operations on your local peripherals. If the browser waited for one image to load before requesting the next, then it would take an awful lot longer for all the images to be displayed.

Images

Most of the time when you load an image, you’re not interested in knowing when the I/O has completed. In those cases when you do care, you can add an “onload” attribute to the <img> specifying the action to be performed when the image fetch is completed.

Alert

One example of an operation which is not asynchronous is “alert”. when you call alert(), all processing is paused until the user clicks the alert window’s “OK” button.

GDownloadUrl, GClientGeocoder and GDirections

GDownloadUrl, GClientGeocoder and GDirections calls are always asynchronous. There’s nothing you can do to make them synchronous.

The results of the I/O are not available immediately after the GDownloadUrl() call, but only within its callback function.

The results of a GDirections call are only available wne its “load” event has been triggered.

GXmlHttp

The method of using GXmlHttp() described in the documentation uses asynchronous processing.

If you really want to, you can use GXmlHttp() in synchronous mode. This is achieved by setting the third parameter of request.send() to false

     var request = GXmlHttp.create();
      request.open("GET", "example.xml", false);
      request.send(null);
      var xmlDoc = request.responseXML;

When used in this way, the “request.send()” will cause the processing to pause until the file has been fetched.

This works in standards-compliant browsers which don’t support ActiveX, and in MSIE6. There’s no guarantee that it will work in any other browsers that support ActiveX.

[The way the code works is that if the browser supports ActiveXObject, then GXmlHttp() calls ActiveXObject(“Microsoft.XMLHTTP”), if not, then if the browser supports window.XMLHttpRequest, then GXmlHttp() calls that. MSIE7 supports both technologies. The API is currently coded to use ActiveX if both are available. I’ve not tested synchronous GXmlHttp in MSIE7.]

onload functions

Another common situation where an asynchronous operation happens is when you use an onload function, either by writing <body onload=”load()”> in the HTML, or equivalently by writing window.onload=load;in the Javascript.

Code that’s placed outside any such function is executed as soon as the browser reads it, then the browser fetches any asyncronous resources that the page requires, in particular the images. After all the image fetches complete, the browser calls the onload function.

Advertisements

Compatibility

Google Maps API Tutorial

APIv2 supports the old v1 documented commands (except openInfoWindowXslt) as well as the new v2 syntax.

If you already have a working v1 map that doesn’t use any undocumented features, then you can just change the version number when you load the API code and it may well work the same.

One significant source of incompatibility is that you must perform a map.setCenter() before adding any overlays.

Third Party Extensions

Google Maps API Tutorial

EPolys v2

Version 2 of the EPoly extension runs much faster by storing intermediate information about the polys, rather than recalculating everything each time it gets called.

The downside of this is that it will not notice any changes that are made to the polys.

If the geometry of your polys might possibly change, e.g. by using enableEditing(), insertVertex() or deleteVertex(), then use version 1 of the EPoly extension.

Version 2 also uses more memory than version 1, to store the intermediate information.

The version 2 interface is identical to that of version 1 except that you copy the epoly2.js code into your own webspace and load it like:

    <script src="epoly2.js" type="text/javascript"> </script>

This v2 example is identical to the v1 example except that it uses version 2 of the EPoly extension, and as you can see it runs considerably faster.

Third Party Extensions

Google Maps API Tutorial

Using the EWindow extension

The EWindow extension provides some of the functionality of custom info windows.

Here’s an example with a single EWindow that behaves something like the Google info window.

Here’s a similar example but now the EWindow has a close icon.

Here’s an example with multiple info windows that are permanently open.

Here’s a rather messy example with tabbed EWindows.

The basic procedure for using EWindows is:

  1. Download the ewindow.zip file which contains all the components, unzip it an place the contents on your web site.
  2. Call the CSS file like this <link rel=”stylesheet” type=”text/css” href=”ewindow.css”>
  3. Load the Javascript code like this <script type=”text/javascript” src=”ewindow.js”></script>
  4. Create and addOverlay() one or more EWindows, e.g. ewindow = new EWindow(map, E_STYLE_1); map.addOverlay(ewindow);
  5. Open the EWindow with ewindow.openOnMarker(marker,html) orewindow.openOnMap(point, html, offset)

EWindow Constructor

The parameters for new EWindow() are:

map The map on which the EWindow is to appear.
EStyle Information about the style of the EWindow
A few EStyles are provided, E_STYLE_1, E_STYLE_2, etc. or you can design your own.

Opening an EWindow

There are two methods provided for opening an EWindow

ewindow.openOnMap

This opens the EWindow on the map that you specified in the EWindow constructor.

point A GLatLng() or GPoint() specifying the geographical location
html A string containing simple HTML.
EWindows won’t handle all the complicated HTML that a Google info window can cope with, and won’t auto wrap the text. You’ll need to use <br> wherever you want the text to break
offset (optional) A GPoint() specifying a pixel offset.

ewindow.openOnMarker

This opens the EWindow on the map that you specified in the EWindow constructor, with a location derived from the specified marker.

This call uses the infoWindowAnchor parameter of the icon that is being used by the marker.

marker The marker on which the EWindow is to be opened.
html A string containing simple HTML.
EWindows won’t handle all the complicated HTML that a Google info window can cope with, and won’t auto wrap the text. You’ll need to use <br> wherever you want the text to break

Multiple EWindows

You can have as many EWindows as you like on the same map. Use the EWindow constsuctor to construct them ewindow2 = new EWindow(map, E_STYLE_1); map.addOverlay(ewindow2);

Closing an EWindow

Use ewindow.hide()

There’s no close icon in an EWindow, so you might consider arranging for them to close when the user clicks on the map

      // ========== Close the EWindow if theres a map click ==========
      GEvent.addListener(map, "click", function(marker,point) {
        if (point) {
          ewindow.hide();
        }
      });

map.removeOverlay(ewindow) is provided because it’s a required part of the Custom Overlay interface, but I don’t recommend using it.

You can use ewindow.show() to unhide an EWindow.

EWindow.copy()

ewindow.copy() is provided because it’s a required part of the Custom Overlay interface, but the copy will not be visible uless you use .openOnMap() or .openOnMarker() on the copy. So, for example, the EWindow will not be visible in blowups.

Making your own EStyles

If you don’t like the EStyles that are provided, you can make your own.

Create a suitable image for the stem. To keep things reasonably simple, the anchor point is always the bottom left corner of the stem image.

Then use the EStyle constructor like this
myEStyle = new EStyle(stemImage, stemSize, boxClass, boxOffset);

The parameters are:

stemImage A string containing the URL of the image file
stemSize A GSize() containing the pixel size of the stemImage
boxClass A string containing a class name to be used for the CSS style
boxOffset A GPoint containing the pixel offset of the bottom of the box from the bottom of the stem image
Typically, for stems that touch the bottom of the box, the x value will be a small negative number, and the y value will be the height of the image minus the width of the box border from the CSS.
For stems that touch the left of the box (like E_STYLE_6) the x value will typically be the width of the image minus the border, and the y value will be a small positive number.

If you create some nice EStyles that other people could use, send me a copy and I’ll include them in the EWindow distribution.

More advanced stuff

Google Maps API Tutorial

GLayers

API v2.130 introduces GLayers, which can be used for displaying the Wikipedia and Panoramio layers to your map.

Google themselves don’t (yet) supply a control for allowing users to switch layers on and off, so I wrote this example

IDs and LMCs

The documented way to specify a particular layer is by using its ID. The official list of IDs is atspreadsheets.google.com/pub?key=p9pdwsai2hDN-cAocTLhnag

In addition to the official list, there are also “com.panoramio.popular”, which omits the Panoramio elements that have the small icons, and there’s “com.youtube.all” which displays the YouTube layer, and there are a few other Wikipedia languages. I don’t intend to test all possible Wikipedia languages: If your favourite language is supported by Wikipedia but isn’t in the official GLayers list, try it anyway.

There’s also an undocumented way to specify a particular layer, by using its LMC. E.g.map.addOverlay(new GLayer(“lmc:panoramio/0”)) is the same as map.addOverlay(new GLayer(“com.panoramio.popular”)).

You can only use IDs that the particular release of the main API code knows about, because the main API code needs to know how to translate the ID into an LMC. In v2.130, the known IDs are “com.panoramio.all”, “com.panoramio.popular” and “org.wikipedia.*”.

When you use an LMC, the main API code doesn’t need to translate it for you, so you can use any LMC for which there is a service.

At the moment, the only LMC that I know about that has service but no ID is “lmc:panoramio/1”, which displays the unpopular Panoramio entries and omits the popular ones.

Viewing the source of maps.google.com reveals the existence of “lmc:youtube”, but the API has no service for it at the moment.

hide() and show()

GLayers support .hide(), .show() and .isHidden().

GLayers don’t support .supportsHide().

More advanced stuff

Google Maps API Tutorial

Using pseudo-HTML data files for maxContent

If you’re using maxContent, or anything else, to display large quantities of HTML data about each marker, then you’ve got to store that data somewhere. If you’ve got lots of markers, things can get messy.

You could store the data in one large XML file or plain text file, but that becomes a pain to create and maintain if you’ve got large amounts of HTML data for each marker.

You could store the maxContent for each marker in a separate HTML file, which you load on demand. The only problem with that is that if you’ve got lots of markers, you end up with lots of files. If you’re using something like googlepages, you have to upload each file, one by one, and there’s a limit of 500 files per googlepages site.

What I came up with is storing the data in one rather unconventional HTML file. The file contains the maxContent HTML for all the markers, using <hr> as a separator. It doesn’t have <head>, <body> or <html> tags or a Doctype, because the maxContent gets injected into a div.innerHTML within an existing page. However, it’s still close enough to acceptable HTML for you to be able to load it into a browser and check the format and content of all the sections at once.

I’ve also put other data about the markers in the same file, separated with “|” characters, but you could just as well store the other data in a separate XML file with the records arranged in the same order.

The data is read in a similar manner to that used when reading a plain text data file, except that the data is split into sections using “<hr>” as the separator, rather than “\n”.

More advanced stuff

Google Maps API Tutorial

Modularized Overlays and Controls

Version information:

  • Info Windows: Google moved the info window back into the main code from v2.135 to v2.183, so this technique is not necessary for those versions.
    Google moved the info window back into an exernal module in v2.184, so this technique is again necessary from v2.184 onwards.
  • GOverviewMapControl: The undocumented .getOverviewMap() does not work since v2.135. There is now no way to obtain a reference to the overview map oject.

Google have been moving some code out of the main API code and into external modules. This reduces the memory required to run the API, and the time taken to load the main code. The external modules are only loaded when you make the first call to code that they contain.

The loading of an external module is performed asynchronously. This can be a problem if you were intending to directly modify the contents of something created by that module. Modifying the overlay using API calls is OK. I guess API calls to a module that hasn’t finished loading are pipelined.

If you attempt to reference DOM elements of modularized overlays or controls, then you’ll find that those elements don’t exist for a few hundred milliseconds.

GOverviewMapControl

In v2.93, the code for the map controls was moved into a separate module.

In previous versions of the API you could write

      var overlayControl = new GOverviewMapControl();
      map.addControl(overlayControl);
      var overmap = overlayControl.getOverviewMap();
      var overmapdiv = document.getElementById('map_overview');

From v2.93, those undocumented features no longer work because the GOverviewMapControl doesn’t actually exist until a few hundred milliseconds later, after the code that handles it has been loaded. You have to wait for the module to be loaded before those features become accessible. Or, alternatively, you could stop using those undocumented features.

The internal event that is triggered when the code module has been loaded doesn’t appear to be accessible, and waiting for a fixed time period carries the risk that it might fail for a user with slower Internet connectivity or when the Google server is busy.

What you could do is wait for a short while, and check to see if the feature is active, and wait again if it isn’t. Like this:

      var overlayControl = new GOverviewMapControl();
      map.addControl(overlayControl);
      setTimeout("checkOverview()",100);
   ...
      function checkOverview() {
        overmap = overlayControl.getOverviewMap();
        if (overmap) {
          ...
        } else {
        setTimeout("checkOverview()",100);
      }
    }

This example logs the availability of .getOverviewMap() and “map_overview”.

This example performs the same tweaks as described here for the earlier versions of the API.

Info Window

In v2.123, the code for the info window was moved into a separate module.

In previous versions of the API you could write

     marker.openInfoWindowHtml('<div id="info"> ... </div>');

and immediately access document.getElementById(“info”).

You might want to do that if you’re displaying a mini-map or a StreetView panorama inside the info window.

From v2.123, that doesn’t work the first time you open the info window, because the content div doesn’t exist until a few hundred milliseconds later, after the code that handles it has been loaded.

If you’re opening the info window immediately when the page launches, then you’d have to wait for the module to load, just like the GOverviewMapControl above.

If, however, you only open the info window when the user clicks on a marker, a neater solution is to force the module to be loaded as the page opens, with the expectation that the module will be available by the time that the user clicks on a marker. This can be done like this:

     var map = new GMap2(document.getElementById("map"));
     map.getInfoWindow().show();

The .show() method doesn’t display anything, since the info window isn’t set up. Other commands that don’t display anything are implemented in main.js, and don’t cause the module to be loaded.

More advanced stuff

Google Maps API Tutorial

Storing User Input

A common requirement is to allow users to input data onto your map which will then become available to other users.

You can’t write files directly from Javascript, so you have to send the data back to a server script and arrange for the server to store the data in a database on your server.

Javascript can send the data to the server using GDownloadUrl.

If there are always going to be less than 512 bytes to send, then you can use the normal format of GDownloadUrl, which sends a GET request to the server:

  GDownloadUrl(
   "myserver.php?lat=1.12&lng=-23.45&details=This is my house",
   function(doc){}
  );

If there may be more that 512 bytes of data, then you’ll need to use the undocumented POST form of GDownloadUrl

  GDownloadUrl(
   "myserver.php",
   function(doc){},
   "lat=1.12&lng=-23.45&details=This is my house"
  );

Here’s a simple example

Some considerations

Javascript code launched from HTML (e.g. from the SUBMIT button of a form) executes in global context, so it can only access local variables. In the example, I store a global reference to the last input marker that had its info window opened, “lastmarker”. Since the API only supports one info window being open at once, this will be the marker associated with the SUBMIT button click.

I’ve made the input markers draggable so that the user can adjust the position.

I’ve used a different icon for the input markers and the normal markers. I reckon it gets confusing otherwise.

I’ve set the {draggableCursor} to “default”. This makes it easier for the user to position the click accurately.

Don’t allow untrusted users to enter HTML content into your info windows.
In this example, I’ve used the text node version of marker.openInfoWindow() rather than marker.openInfoWindowHtml(), so thay if anyone attempts to do something nasty, like embed a malware file, all that happens is that http://malware.file gets displayed in the text.
You might want to do something more sophisticated, like removing anything between < and >.

Googlepages doesn’t support server scripts, so my example doesn’t actually send the data to a server. In your real page you’d remove the “//” from the beginning of the GDownloadUrl() command.

This is just a simple example. In a real page you might want to allow users to edit or delete markers that they had previously created. You might even want to allow them to perform a limited amount of markup in the text. That’s not going to be particularly easy.

More advanced stuff

Google Maps API Tutorial

Geocoding Low Quality Data

The technique for geocoding multiple addresses shown here is OK if your original address data is of reasonable quality.

I recently attempted to geocode a file of locations that had been collected in my family history system, and found that that mechanism wasn’t appropriate. Much of the data was collected from hand written documents, some of which are over 200 years old, so there are transcription errors. Some of the original information was obtained from illiterate individuals and uses strange spelling. Some of the streets no longer exist. Some of the towns have changed their names, e.g. “Layton with Warbreck” is now “Blackpool”. Some of the county boundaries have changed, e.g. the geocoder can’t find “Bowness, Westmorland” because it’s now in Cumbria.

I also seem to have hit several towns for which the geocoder has wildly inaccurate information. I have ancestors who came from Lytham, Charnock Richard, and North Meols.

My data was so bad that any sort of fully automated process would be useless. Every location needed to be looked at to see if the result is reasonable. Since I don’t actually know where the locations should be, I couldn’t drag a pointer to the correct locations. All I could do was to try modifying the address until the geocoder returned something sensible.

I ended up using two maps, one at street level and one zoomed out far enough so that I could see that the street was in the right town. I found that more convenient than zooming in and out with a single map.

Below the maps, I placed a control area in which the .getLocation() results could be displayed and selected, and the address text could be modified.

I display the results in XML format so that I can copy and paste the data into the final XML file. If your webhost supports server side scripting, you could post the data directly to your server to be stored in your online database.

I ended up with this

More advanced stuff

Google Maps API Tutorial

Context Menu

You may have noticed that maps.google.com now has a context menu that can be invoked with a right click.

It is possible to create context menus for your own API maps by using the “singlerightclick” event.

Here’s an Example

There’s nothing in the API to help you construct the context menu itself. You have to write your own code to do that.

What I basically do in that example, is to create a hidden div that contains the menus and has onclicks that each call a global function. When the user right-clicks the map, the “singlerightclick” event is triggered, and it returns a GPoint() indicating the pixel position of the click relative to the map container.

When the code detects such an event, I:

  • Store a copy of the pixel position in a global variable, in case any of my global functions need it later.
  • Check if the position is close to the right or bottom edge and adjust the context menu position accordingly.
  • Create a GControlPosition() for the position of the context menu.
  • Apply() the GControlPosition to the context menu div.
  • Make the context menu visible

Whenever there’s a click on one of the menu options, I perform the requested event, and then close the context menu.

Whenever there’s a click on the map or one of its clickable overlays, I close the context menu.

If one of the functions needs to know where on the map the right-click occurred, I read the pixel location from the global variable and convert it to a GLatLng with map.fromContainerPixelToLatLng().

The strange looking divs inside the links are there to make the whole width of the menu clickable.
Without them, only the actual text would be clickable.

You can obviously get your context menus to contain whatever options you want. You could even generate the .innerHTML of the context div dynamically when the right click occurs, with different options depending on the context.

Bits I’ve not figured out yet

  • The maps.google.com context menus close when the mouse exits the map container outwards, but not when it exits the map container by hovering over the context menu.

More advanced stuff

Google Maps API Tutorial

“Did You Mean?”

The maps.google.com page will ask a “Did you mean?” question in response to an address search under certain circumstances. You might possibly want to offer the same functionality within the API.

Note: The API geocoder is quite different from the maps.google.com geocoder. Situations which cause the conditions which provoke a “Did you mean?” in one geocoder will not usually cause those conditions in the other geocoder.

Multiple Hits

One condition which provokes the question is when the geocoder returns more than one Placemark in response to the query.

To detect this situation with the API geocoder, simply test whether result.Placemark.length is greater than 1.
If it is greater than 1, then display a list of clickable options, using the Placemark.address values that are returned. Store the corresponding Placemark.coordinates so that they can be used to plot the marker when the user selects one of the alternatives.

Here’s an example

The API geocoder doesn’t often return multiple results. Bispham Road, Bispham, UK is one example address which currently produces three results in the API geocoder (because there really are three Bispham Roads in Bispham).

Potential Pitfalls

  1. Don’t store the Placemark.address and try to geocode that when the user selects it.
    Even though the geocoder returns that address, it doesn’t mean that it can geocode it if you pass it back.

Significantly Different Address

The geocoder may occasionally return an address which is sufficiently different from the requested address to be worth asking the “Did you mean?” question.

For example, if you search for Bespham Road, Cleveleys, FY2, the API geocoder will return a single result for Bispham Rd, Blackpool, FY2 0, United Kingdom, which you might consider to be sufficiently different to be worth querying.

This circumstance is much harder to test for. The returned address will often look very different from the query just because the geocoder converts the address into a standard format. In this case, the fact that “Gilford” has been replaced by the zip code “03249”; and the fact that “,USA” has been added; should not be considered to be “significant” differences in the address.

I reckon that it’s impractical to compare anything other than the house number and street. To go any further would require some fairly sophisticated AI to handle all the possible valid variations that might be used, and you’d almost need to write your own geocoder to check if “Gilford” actually is zip code “03249”.

My strategy for matching the street is to

  • Write a little function that converts words like “street”, “lane”, “north” etc. into standard versions “st”, “la”, “n” etc.
  • Split the reply at the first comma to obtain the part that specifies the street.
  • Convert the query and the reply to the same case, e.g. .toLowerCase().
  • Remove any apostrophes, so that “St Fred’s Rd” matches “St Freds Rd”.
  • Replace any other punctuation with spaces, since the geocoder ignores punctuation except that it separates words.
  • Replace multiple spaces with a single space, so that we now have a string of words separated by single spaces.
  • Split the strings into arrays of words.
  • Convert each word to the “standard” version.
  • Compare corresponding words from the query and the reply

Here’s an example

I’ve not done an awful lot of testing with this.

More advanced stuff

Google Maps API Tutorial

Geocoding multiple addresses

If you’ve got lots of addresses to geocode, then you can’t just write a loop that calls the getAddress() function in the previous example several times. There are several problems:

  • Geocoding takes quite a bit of computer power. Let’s try to avoid needlessly wasting Google server resources.
  • If you send geocode requests to quickly, some of the requests will fail with code 620 “G_GEO_TOO_MANY_QUERIES”.
  • Geocoding your locations each time will make your page take longer to open. A geocode request can sometimes take as long as a second to be processed.
  • There are awkward pitfalls for the unwary coder when there’s more than one geocode request in flight at the same time.
  • Geocoders aren’t too smart. If the Google geocoder can’t find a good match for your address it will sometimes make guesses that a human would consider to be really silly. The geocoding database changes fairly rapidly, and it’s possible that an address that it guesses right today might go wrong next week. If you geocode once and check the results then you know that you’ve got the right place.

If your webhost supports server-side scripting, then I recommend finding a non-Google batch geocoder and using that. If not, you can do something like this example, calling the Google geocoder as each reply comes back and displaying the results in a format that you can copy and paste into an XML file.

The goeocoder quota is now set at 15,000 geocode calls per IP address per day. The old 1.725 second speed limit per API key no longer applies, but there’s a mysterious “limit to the maximum rate” mentionedhere and here. At the time of writing, it seems that a 100ms delay between geocode requests works well for long runs. Shorter delays case more 620 errors and may take longer to complete the retries.

If you use .getLatLng() for geocoding multiple addresses, then you won’t be able to recognise the 620 errors. I recommend using .getLocations() so that you can monitor the 620 errors. If you decide to use .getlatLng(), then I suggest using a considerably longer delay.

In my example, I start with a 100ms delay and increase the length of the delay each time I retry an address due to receiving a 620 error.

In my example, the list of target addresses is hard coded, but you could read the list from one XML file, pass forward all the attributes to your getAddress() function and include all the details in your output.

If you can run server-side scripting on your webserver, then you could send each result to your server to be stored in your database, but in that case you’d probably find it easier to use a non-Google batch geocoder that you could call directly from your server.

More advanced stuff

Google Maps API Tutorial

Custom Tooltips

It’s possible to add custom tooltips to your markers.

This technique was invented by “evilc”.

Here’s an example

The code is more complicated than using standard tooltips, but by creating our own tooltips we can:

  • Pop up the tooltip instantly when the mouse moves over the marker, instead of there being a delay.
  • Keep the tooltip visible until the mouse moves off the marker.
  • Pop up the marker tooltip when the mouse moves over the corresponding sidebar entry.
  • Use our own styles.

The technique involves creating a <div> that contains the tooltip which is initially hidden.

When the mouseover event occurs, the pixel position of the marker relative to the South West corner of the visible map is calculated, the tooltip <div> is positioned relative to that corner and made visible.

There’s no reason why the tooltip should only contain a simple text label. You can use images and tables etc. Don’t try using links or forms, because the tooltip disappears when the mouse moves over the tooltip, because it would no longer be over the marker.

Bugs

  1. The tooltip appears in the wrong place when the International Date Line is within the visible map. This is caused by a problem with map.getBounds(). It returns the normalized values of the locations, but we really need the unnormalized values. I don’t believe that there’s anything that can be done about that until Google provide a mechanism for obtaining the unnormalized location of the map corners.
  2. The tooltip gets squashed up if it is created close to the East edge of the map. The behaviour is worse in Firefox than IE, particularly if overflow:hidden is not used. The behaviour at the other edges is OK.

Potential Pitfalls

  1. Make sure that you create the map before performing the appendChild(tooltip). If you append the tooltip div first, then the tooltip gets displayed underneath the map tiles and you can’t see it.
  2. Don’t put links or forms into the tooltip div. The user won’t be able to click on them because the tooltip disappears when the mouse moves off the marker.
  3. Do make sure that the tooltip has a defined CSS style, which should at least include a “background-color” setting. Without it, the tooltip background would be invisible.

The Basics

Google Maps API Tutorial

Fitting the map to the data

The map.getBoundsZoomLevel() method allows us to calculate a zoom level setting that fits a set of markers.

Here’s an example

It is necessary tp perform a map.setCenter() call before starting to add markers. At this point we don’t know the real parameters for the call, so just use any values.

   map.setCenter(new GLatLng(0,0),0);

Create an empty GLatLngBounds() object.

   var bounds = new GLatLngBounds();

Each time a point is read, extend the bounds to include that point.

   bounds.extend(latlng);

When all the points have been processed, the zoom level can be set to fit the points.

   map.setZoom(map.getBoundsZoomLevel(bounds));

The centre can be obtained by using the bounds.getCenter() method

   map.setCenter(bounds.getCenter());

Potential Pitfalls

You can’t mix v1 compatibility mode and v2 native syntax in this code. You do need to use a GLatLngBounds(), not a GBounds(), and you can only use a GLatLng in its .extend() method, not a GPoint.

The Basics

Google Maps API Tutorial

Data in text files

If you happen to have your data stored in some application that doesn’t export XML files, you might prefer to output it as a plain text file.

It’s also possible to have an script running on your server which returns plain text data containing a different selection from the data depending on query information derived from what your users input. I’m not going to cover the server side of things in this tutorial.

Here’s a simple example

You can see the file that I used here

The code defines a function “process_it()” which processes the retrieved data.

The entire data file is passed to process_it() as a single string, which we can then split into lines using newline (\n) as a separator. Each line can then be split into parts using some separator. I’m using “|” as the separator since it’s unlikely to occur in the data.

Care needs to be taken to avoid trying to split empty lines. With some text editors it’s not possible to avoid a final newline character at the end of the file, and split(“\n”) will consider there to be an empty line after the final newline.

Once all the data has been parsed, we can create the markers and the sidebar as in the previous examples.

Note that the code to insert the assembled sidebar information into its <div> must be inside the process_it() function.

Unlike XML, plain text files can contain < and >, and there’s no problem with quote character confusion when using things like links or images. This may well make plain text more suitable for map data in many situations.

There’s no problem with MIME types either. This code works even if the server sets the MIME type to (text/xml) or (text/html) instead of (text/plain), but it’s very unlikely that any server wouldn’t use (text/plain) for a file with a .txt extension.

Potential Pitfalls

  1. Be aware that Javascript i/o is asynchronous (so that the browser can get on with doing other things like fetching images if the i/o request takes a while to complete).
    If you’re used to programming languages that wait for i/o to complete, you might tend to put code that uses the data read from the file after the “GDownloadUrl()” statement. That would be wrong because code placed there would get executed immediately rather than waiting for the data to arrive. Any code that acts on the retrieved data should be placed inside the process_it() function.
  2. All data is considered to be strings of characters. You need to convert your latitude and longitude from strings to floating point numbers by using “parseFloat()”.
    In some circumstances, it might seem that you can get away without doing that, but then things can go horribly wrong later if the Google code tries to perform arithmetic on values that are not numbers.
  3. Don’t choose a separator character that occurs in your data.