More advanced stuff

Google Maps API Tutorial

GoogleBar Options

The GoogleBar allows you to achieve similar functionality to what you would get by using the Google AJAX Search API to perform a Local Search, but provides a few advantages:

  1. If you just need the basic functionality, all you need is one extra command in your code: map.enableGoogleBar().
  2. You can use it without having agreed to the Google AJAX Search API Terms of Use, which seem to not allow you to modify the results.

The Maps API GoogleBar options provide facilities which you can use to:

  • Modify or replace the text, images, or other content of the Google Search Results
  • Changing the order in which the Google Search Results appear
  • Intermix Search Results from sources other than Google
  • Intermix other content such that it appears to be part of the Google Search Results
  • Modify, replace or otherwise disable the functioning of links

All of which would be prohibited if you were using the Google AJAX Search API.

Options

The possible entries in the googleBarOptions are:

property type purpose
showOnLoad bool If true, the Google Bar will be expanded when the map has loaded.
linkTarget string Use one of these string variables to control the link target pane G_GOOGLEBAR_LINK_TARGET_BLANK, G_GOOGLEBAR_LINK_TARGET_PARENT, G_GOOGLEBAR_LINK_TARGET_SELF, G_GOOGLEBAR_LINK_TARGET_TOP
resultList string Use one of these string variables to control result list suppression G_GOOGLEBAR_RESULT_LIST_INLINE, G_GOOGLEBAR_RESULT_LIST_SUPPRESS
suppressInitialResultSelection bool If true, the info window is not opened on the first result.
suppressZoomToBounds bool If true, the map does not zoom and pan to fit the results.
onIdleCallback function Function be be called when the GoogleBar finishes searching
onSearchCompleteCallback function Function to be called when the search is complete, before the reults are plotted
onMarkersSetCallback function Function to be called after the markers have been placed on the map.
onGenerateMarkerHtmlCallback function Function to be called when the info window is about to be opened on one of the results. Must return the (modified) html element object to be used for the info window contents.

The first six options are fairly straight forward, but the callback functions need a bit of explanation:

onIdleCallback

This function is called when the GoogleBar is opened for the first time, and called again whenever the previous results are cleared. It is not called if a second search is executed without the user explicitly clearing the previous results.

No parameters are passed.

onSearchCompleteCallback

This function is called when the search is complete, before the results are plotted.

There is one parameter, a reference to a GlocalSearch object, see Official Class reference

Of particular interest is the .results[] array, which is an array of GlocalResult() objects, see Official Class reference

You can read that information, but also, because it’s a reference, you can modify the contents of the .result[] array. The .html will have already been calculated, so changing things like the .results[i].city won’t affect what gets displayed in the info window.

If you want to change the info window contents at this point, then you need to change the .results[i].html.innerHTML.

onMarkersSetCallback

This function is called when the API has created markers corresponding to the search results.

There is one parameter, an array of anonymous Objects.
Each Object in the array has two properties, .result is a GlocalResult object and .marker is a GMarker.

onGenerateMarkerHtmlCallback

This function is called whenever an info window is about to be opened.

The parameters are:

  1. The GMarker
  2. The HTMLElement which is intended to become the info window contents
  3. The GlocalResult

The function must return a HTMLElement which will become the actual info window contents.

Simple result filtering example

This example uses onSearchCompleteCallback to filter the search results, reducing the number of results to no more than two.

Simple info window modification

This example uses onGenerateMarkerHtmlCallback to read information from the GMarker and from the GlocalResult to modify the contents of the info window at the time that the info window is opened.

Advanced example: add ELabels

This advanced example uses all the GoogleBar Options. It displays an Elabel alongside each marker, and in order to do that it uses the callback functions like this:

onIdleCallback uses map.clearOverlays() to remove the old ELabels

onSearchCompleteCallback uses map.clearOverlays() to remove the old ELabels

onMarkersSetCallback reads the location of each GMarker and the titleNoFormatting of each GlocalResult and uses that information to create an ELabel.

onGenerateMarkerHtmlCallback hides the ELabel corresponding to the marker on which the info window is opening.

Advertisements

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

Custom Cursors

It is possible to use your own custom cursors for the map and for GDraggableObjects.

Here’s a simple example

The cursor is implemented as part of the operating system code, not in the browser itself, and the implementation varies considerably depending on the operating system and the browser.

MSIE

MSIE allows you to specify a list of CUR and ANI files and standard cursor keywords.
E.g. ‘url(mycursor.ani), url(mycursor.cur), pointer’
The “hot spot” must be specified in the CUR or ANI file itself, you can’t specify it in the cursor list.

If, for any reason, one cursor in the list cannot be used in the current environment, then the next one in the list will be used. However, if you attempt to use standard CSS3 syntax to specify the “hot spot” of any of the cursors, the whole list is ignored.

Gecko based browsers

Recent versions of Gecko-based browsers (Firefox, Mozilla, Netscape, Flick, etc.) allow you to specify a list of CUR, PNG, GIF, JPG, XBM and BMP files but require that the last element in the list be a standard cursor keyword.
They also allow the “hot spot” to be specified in the cursor list using standard CSS3 syntax.
E.g. ‘url(mycursor.png) 16 16, pointer’
If you don’t specify a “hot spot” in the list, then for XBM and CUR files the “hot spot” specified in the file will be used, and for other file types the tip left pixel (0,0) will be used.

Animated cursors are not currently supported in Gecko-based browsers. If you specify an animated GIF, then the first frame will be used.

If, for any reason, one cursor in the list cannot be used in the current environment, then the next one in the list will be used. However, if the last entry in the list is not a standard cursor keyword, then the whole list is ignored.

Webkit based browsers

I can’t find a list of rules for Safari or Chrome, but anything that works in Firefox seems to work.

Presto based browsers

I can’t find a list of rules for Opera, and can’t get anything to work. Any attempt at using a custom cursor list causes the whole list to be ignored. The standard cursor keywords work on their own, but not if there’s a “url()” in the list.

Windows versions

Translucent cursors are not supported in Windows versions prior to XP.

Cursors larger than 32*32 pixels are not supported in Windows versions prior to XP.

Mac Os X

Custom cursors don’t currently work on Mac Os X in Gecko based browsers.

Creating CUR files

Normal image processing applications won’t output CUR files. You’ll need a separate dedicated CUR file editor.

I happened to use ArtCursors from Aha-Soft www.aha-soft.com/artcursors. It costs $39.99 and comes with a 30 day trial period. There are several similar looking shareware programs at fairly similar prices.

Alternatively, you could try downloading ready-made cursors from one of the many “free cursor” sites. I recommend deactivating Javascript and other active content before you go searching for such sites. The active content on some of those sites is extremely annoying.

Custom cursors outside the map

You can use custom cursors on any HTML element by using the same type of cursor list in a CSS “cursor:” style setting.

Standard cursor keywords

Standard cursors are “auto”, “crosshair”, “default”, “help”, “move”, “pointer”, “text”, “wait”, and in some environments “hand”, “progress”, “not-allowed”, “no-drop”, “vertical-text” and “all-scroll”.

Changing cursors dynamically

You can change the cursors of a GDraggableObject at any time with .setDraggableCursor() and .setDraggingCursor(). With API versions up to v2.92 you can only set the map cursors with the {draggableCursor} and {draggingCursor} options at the time that you create the map.

It is possible to change the cursors of a map dynamically by using the .getDragObject() Method.

   map.getDragObject().setDraggableCursor("crosshair");

The map cursors are actually owned by the GDraggableObject that is used to control the dragging of the map and its contents.

More advanced stuff

Google Maps API Tutorial

Custom direction details

GDirections.load() doesn’t give you much control over the layout of the directions information. If you want to have the directions displayed slightly differently, you have to read through the GRoutes and GSteps and build up the whole thing yourself.

Here’s a page where I’ve already done that to produce a display that looks almost exactly like that produced by GDirections, but since it’s all done in exposed Javascript, you can take a copy and customise the code to make it look the way that you want it to.

The parameters of my “customPanel()” function are:

  1. The map object.
  2. A string containing the name of the global variable that points to your map.
  3. The GDirections() object.
  4. The div into which the directions are to be placed.

Potential Pitfalls

  1. Don’t forget to use {getSteps:true} otherwise there won’t be any GRoute or GStep information to process
  2. The GDirections “load” event seems to happen just before it centres the map. If you are relying on GDirections to perform the initial centering of the map, then you have to set a short timeout after the “load” before the direction details are processed.

More advanced stuff

Google Maps API Tutorial

Snap point to street

It is now possible (in countries that support directions) to snap points to the nearest street that’s in the Google database.

This could be useful if you want to record a specific path by drawing a polyline that follows the streets.

The effect is achieved by requesting directions from the clicked point to itself. When the directions information returns, it will include a polyline with a single vertex. This polyline “follows” the street grid, and that vertex is the nearest point that lies on the street grid. So instead of plotting the point exactly where the user clicked, we can plot the point at the returned street vertex.

Here’s a simple example. Click on the map and the marker will be added to the nearest known street.

In this example, the markers are draggable and snap to a street when the drag ends.

In this example, polylines following the streets are added between the snapped points.

In this example, the two previous features are combined. Polylines are redrawn when a marker is dragged.

Potential Pitfalls

  1. Some of the very small streets shown on the map are not in the street garid database.
    E.g. the small streets to the East of the A583 roundabout in the first example are paths inside a caravan park, points will not snap to those paths because they’re not in the street database.
  2. Don’t try to snap all the points in your database dynamically, there are issues with running multiple GDirections calls at the same time. Snap your points, one by one, offline and store the snapped locations in your database.

More advanced stuff

Google Maps API Tutorial

Get Directions on your own map

It is now possible to obtain directions on your own map.

Here’s an example.

There are initially two links: “To here” and “From here”. Clicking these links opens a corresponding form.

The initial version of the info window, one with the “To here” form and one with the “From here” form. When the “To here” or “From here” link is clicked, the corresponding html string is loaded into the info window.

When the “Get Directions” button is clicked, the form invokes the “getDirections()” function which makes a GDirections() call.

The details of using GDirections() is well described in the Official Documentation, so I’ll not repeat the information here.

From v2.124 you can specify walking directions and avoid highways by using the options {travelMode:G_TRAVEL_MODE_WALKING} and {avoidHighways:true}.

Potential Pitfalls

  1. GDirections() doesn’t currently support queries in the form “43.82589,-79.1004(My House)”.
    It does support the format “My House@43.82589,-79.1004”.
  2. For legal reasons, walking directions don’t work if you omit the second parameter when you create the GDirections() object. The API needs somewhere to put the disclaimer.

More advanced stuff

Google Maps API Tutorial

Marker Categories

If you have a map that shows several categories of markers, you may want to hide and show all the markers of one category at once, and remove the corresponding sidebar entries.

Here’s an example that does that.

The code makes use fo several of the techniques that have been described previously, which I’m not going to describe again.

The distinctive features of this code are:

Hiding and showing markers

The “hide(category)” and “show(category)” functions read through the array of markers and hide() or show() all the markers which match the specified category.

Dynamic sidebar

Rather than adding the sidebar entries as each marker is created, the “makeSidebar()” function is executed after all the markers already exist, by reading the gmarkers[] array. It omits entries for which gmarkers[i].isHidden().

The “makeSidebar()” function is called whenever the marker display changes.

Marker properties

Extra Properties are added to the marker Objects, to store the category and sidebar name information. This allows the “makeSidebar()”, “hide(category)” and “show(category)” functions to obtain all the information that they need from the marker Objects, rather than having to use some external list.

Checkbox settings

I can’t seen to get <input type=”checkbox” checked > to do exactly what I want, so I’ve added a line to the “hide()” and “show()” functions to ensure that the checkbox is checked and cleared.

In order for this to work, I’ve set the “id” of the checkbox that manages the “theatre” category to “theatrebox”, etc., so that the Javascript can easily obtain the name of the checkbox if it knows the category.

Associative array of GIcons

I’ve used an associative array to hold the GIcons, (as explained in the Associative arrays and Custom Icons tutorial). I’ve used the same names for the icons and categories.

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

Setting the z-index of markers

It is possible to set the z-index of markers.

You might want to do this if your map tends to be rather cluttered with overlapping markers, and you want to highlight a particular marker, but normally find it to be partially obscured behind less significant ones.

To use the facility, you need to create a little function which will return an integer to be used as the z-index. Markers with higher z-index values are drawn on top of those with lower z-index values.

You can calculate the value from scratch, or you might modify the usual value, which can be obtained fromGOverlay.getZIndex(marker.getPoint().lat()).

The Function is passed two Parameters. The first Parameter is a reference to the marker and the second one always seems to be undefined at present.

Attach your function to the marker by using the GMarkerOption {zIndexProcess:myFunction}

You can’t change the order of the markers afterwards. The Function only gets evaluated when the marker is addOverlay()ed.

I don’t think you can pass a Parameter directly to the zIndexProcess Function, but what you can so is to add a Property to the marker before you addOverlay() it. The function can read that Property and use it in the calculation. For example, if you want important markers to be placed on top, then you can add a marker.importance property to each marker, and return higher z-index values for markers with higher importance.

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

Geocoding with error handling

The mechanics of geocoding without error handling are well explained in the official documentation but the examples shown there do not include error handling.

Geocoding requests can fail for a variety of reasons, so it’s a good idea to check for successful completion before attempting to plot markers using the returned information.

It’s also helpful to display the failure reason to your users, where possible, otherwise your users might waste their time doing things like trying to guess the correct spelling of “Sauchiehall Street, Glasgow, UK”, if the problem isn’t the spelling but the fact that you’ve used up youir geocoding quota already.

What you can do is to set up an array like this

 var reasons=[];

 reasons[G_GEO_SUCCESS]            = "Success";

 reasons[G_GEO_MISSING_ADDRESS]    = "Missing Address: The address was either missing or had
                                      no value.";

 reasons[G_GEO_UNKNOWN_ADDRESS]    = "Unknown Address:  No corresponding geographic location
                                      could be found for the specified address.";

 reasons[G_GEO_UNAVAILABLE_ADDRESS]= "Unavailable Address:  The geocode for the given address
                                      cannot be returned due to legal or contractual reasons.";

 reasons[G_GEO_BAD_KEY]            = "Bad Key: The API key is either invalid or does not match
                                      the domain for which it was given";

 reasons[G_GEO_TOO_MANY_QUERIES]   = "Too Many Queries: The daily geocoding quota for this site
                                      has been exceeded.";

 reasons[G_GEO_SERVER_ERROR]       = "Server error: The geocoding request could not be
                                      successfully processed.";

and display the text associated with the reply.Status.code.

When using geocoder.getLocations(), accessing the status code is straight forward, see this example

When using geocoder.getLatLng(), the status code is not returned. You could follow a failed getLatLng() with a getLocations() request and examine its status code. There is a possibility that the status code might be different, in particular, if there was a server timeout on the first attempt, the second attempt might even succeed.

I recommend avoiding geocoder.getLatLng(). It might seem to make the coding easier, but the lack of error handling makes things more difficult in the long run.

Fitting the zoom

The geocoder now returns an ExtendedData section which gives details of a Lat/Lon box that can be used to zoom the map appropriately. Here’s an example.

In that example, if you search for “France” you get zoom level 6. If you search for “123 Avenue des Champs-Elysees, Paris, France” you get zoom level 16.

Note that the Lat/Lng box isn’t a perfect fit for the target location. It’s just there to provide sensible zooming.

Potential Pitfalls

  1. Be aware that geocoder requests are asyncrhonous. The request is sent to the geocoder and processing continues immediately without waiting for the reply to come back.Make sure that all the code that operates on the results is placed inside the event handler function.
  2. Don’t send lots of requests at once. There’s a rate limit on the number of geocode requests that you can send per second.

More advanced stuff

Google Maps API Tutorial

Toned map – New method

You can now change the colour tone of a map by overlaying partially opaque GPolygons over the map.

Things go slightly askew if you try to use a single polygon for the whole 360 degrees of longitude. Four polygons that span 90 degrees each works.

Things can also go wrong when a polygon crosses the International Date Line, but you can have one polygon that ends at 180 degrees and the next polygon starts at 180.000001.

Here’s an example

Restriction

Polygons don’t wrap round the Earth. If you zoom out so that more than one copy of the Earth is visible, then only one copy of the Earth will be toned.

Draggable Markers and GTileLayerOverlays

Draggable markers

We added draggable markers as a hidden feature in revision 2.46, but today they are making their official debut as part of the Maps API. To make a marker draggable, set the marker option draggable to true when you declare it: var marker = new GMarker(center, {draggable: true});.

When you drag the marker, a little X will appear to mark the spot where the marker will fall. After you let go of the marker, it bounces into place (to let you know how much fun it’s having). If you prefer non-bouncy markers, set the marker option bouncy to false.

Try dragging this marker:

GTileLayerOverlays

The Maps API has GTileLayers (which you can use to build GMapTypes) and GOverlays (which you can show on top of a Google Map). Today we’re introducing GTileLayerOverlays, which lets you add tile layers without needing to introduce new map types. You can also add & remove them on the fly, unlike map types.

For example, to create a desaturated map, you can overlay it with a white tile overlay at 50% opacity:

 var tilelayer = new GTileLayer(new GCopyrightCollection(), 0, 17); tilelayer.getTileUrl = function(tile, zoom) { // A more interesting tile server would do something with the numbers // tile.x, tile.y and zoom. return "http://kml.lover.googlepages.com/white_map_tile.gif"; }; tilelayer.getOpacity = function() {return 0.5;} map.addOverlay(new GTileLayerOverlay(tilelayer)); 

If you’d like to start testing these new features right away, don’t forget to add the v=2.x parameter to your<script> src, so you get the latest revision instead of the default v2.60.

API v1 Current: 1.31
API v1 Default: 1.31
API v2 Current: 2.61
API v2 Default: 2.60

More advanced stuff

Google Maps API Tutorial

Limiting the range

If you want to limit the range of zoom levels that the user is allowed to use on your map, you can overwrite the .getMinimumResolution() and .getMaximumResolution() methods of all the map types.

If you want to limit the range of movement that the user is allowed to use on your map, you can add listeners for the “move” event, and countermand any operations that go outside the permitted region.

Things happen so quickly that there’s no time for anything out of bounds to get plotted, so it all looks reasonably smooth.

Here’s an example

What I’ve done for the position is to specify an allowable region in GLatLngBounds() format, and require that the centre point lie within that region.

Potential Pitfalls

  1. Make sure that your permitted region is larger than the visible region at the maximum permitted zoom level.
  2. Allow enough room around the edge so that the automatic movements caused by opening infowindows partially off screen are not restricted.
  3. Don’t call map.setCenter() unless you are actually changing the values. E.g. if you omit the “if” statement around “map.centerAtLatLng(new GPoint(X,Y));”, then that operation itself would trigger a “move” event, and the code would go into a loop.
  4. Don’t try to use the .lat.hi. .lat.lo, .lng.hi and .lng.lo properties of the GLatLngBounds() object.
    The values are in radians, not degrees, so they can’t be used for recentering the view.

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.

More advanced stuff

Google Maps API Tutorial

Standard Tooltips

It’s possible to add tooltips to your markers.

Here’s an example

The preferred syntax for GMarker() allows a set of options to be specified. One of these options is “title”, which specifies the tooltip text.

E.g. new GMarker(point, {title:"My House"});

These types of tooltip are plain text. You can’t apply any formatting to them. If you want to change the style, position or duration you’ll need to use custom tooltips.

Potential Pitfalls

  1. Don’t try to mix this syntax with the old GMarker() syntaxThis won’t work: new GMarker(point, myIcon, {title:”My House”}). When you use the new syntax, the options must be the second parameter, like this new GMarker(point, {icon:myIcon, title:”My House”}).

More advanced stuff

Google Maps API Tutorial

Dual Maps

It’s possible to have a pair of maps that track each other’s movements by using “move” or “moveend” listeners one each other and updating the position of the other map to match the one that moved. For example, a small zoomed-out map can be used as a dynamic thumbnail to help you see what the main map is zoomed in on.

The only problem is that updating the position of the second map cases “move” and “moveend” events, and we don’t want to attempt to process those events, otherwise the code goes into a loop and gets stuck.

All we need to do is to add a pair of extra variables that remember whether the maps are being moved by our own code, and only update the other map when this is not the case, i.e. when the map is being moved by the user.

In this example there’s a crosshair on the small map. This is just a marker which gets recentred every time either map moves.

Credit Thanks to Esa Ojala for posing the original question which led me to develop this technique.

The Basics

Google Maps API Tutorial

Google Chrome Considerations

Pros

Speed
Cons

Currently unsupported
3D Map Type
Async behaviour
Maps in new tabs
Poor developer tools
Polygon winding rule
Polygon fill order
Speed
Speed

Chrome is an awful lot faster at client side processing. In particular, it compiles Javascript code rather than interpreting it.
[But it’s possible that you ain’t seen nothing yet. TraceMonkey is rumoured to run Javascript faster.]

This doesn’t mean that every map page will run faster. If the critical factor is the amount of time it takes to fetch resources from a server then there’s not a lot that Chrome can do to improve the speed.

What’s acceptable is very subjective, and depends on the hardware and Internet connection speed as well as the browser. On my computer, I reckon that the number of displayed markers and polylines that make the browser unacceptably sluggish are:
MSIE: 100 markers or 10 polylines.
Firefox: 300 markers or 100 polylines.
Chrome: 2000 markers or 1000 polylines
Currently Unsupported

Google Chrome is not included in the current list of supported browsers.
Most of the Google Maps API functionality works under Google Chrome, and GBrowserIsCompatible() accepts it, but if you happen to stumble upon something that doesn’t work under Chrome, then there may not be a fix for it.

3D Map Type

G_SATELLITE_3D_MAP currently doesn’t work under Chrome. The Google Earth plugin is not recognised.
Async Behaviour

Chrome has more asynchronous behaviour than other browsers. In other browsers, the display only gets refreshed when Javascript pauses, but Chrome is designed to run Javascript and refresh the display at the same time. This might possibly be a problem if you depend on things happening in a predictable order.
One particular problem that you might encounter is that it’s possible for the Javascript to start executing before CSS files have been read. If your map container has its size specified in an external CSS file, Chrome may well execute the “new GMAp2()” command before the map container size is known. A moment later, Chrome processes the CSS information and changes the size of the container, but the GMap2 object has been created with the wrong size.

Maps in new tabs

There’s an occasional problem that results in a page that’s launched into a new tab to be centred incorrectly.
It seems to occur intermittently with the API, but it occurs consistently with maps.google.com. For example If you shift-click this link in Chrome http://maps.google.com/?q=FY2 and wait a few seconds for the page to be rendered in the closed tab, then the marker gets placed in the correct location but the map is centred on Moscow instead of Blackpool.

Poor developer tools

At present there are no useful extensions for Google Chrome equivalent to things like Firebug, Venkman, HTMLValidator and WebDeveloper in Firefox.
I recommend developing your code in Firefox and testing it in Chrome, MSIE, etc afterwards.

Polygon winding rule

When you create an encoded polygon with holes, the holes may not appear when your map is viewed with Chrome.
This is because the API uses the CANVAS graphics technology to render polylines and polygons, and CANVAS has a different winding rule.

In the other three graphics technologies, a region of a polygon is filled if its winding number is odd, and left empty if the winding number is even. You can consider the “winding number” to be the number of times that the path winds round the region in a clockwise direction. So, if you have a clockwise outer boundary and a clockwise inner boundary, the inner region has winding number 2, and is rendered as a hole.

In CANVAS, a region of a polygon is filled if its winding number is non-zero, and left empty only if the winding number is zero. So, if you have a clockwise outer boundary and a clockwise inner boundary, the inner region has winding number 2, and is not rendered as a hole. To create a hole you need to have one path be clockwise and the other path be anticlockwise for the centre to be renedered as a hole.

Here’s an example

Similarly, if you have a polygon with a path that crosses itself, the interior will appear different in Chrome.

Here’s an example

The different winding rule is a basic feature of the graphic technology, and there’s nothing that the API can do about it.

Polygon fill order

The fill of a polygon appears to be performed above the polyline that forms its boundary, whereas with the other graphics technologies the boundary is placed on top of the fill. This can make the result look quite different.
Consider this example and this one
Speed

From time to time, Chrome will decide to update itself. Without asking.
During the update process, the computer becomes so sluggish that it’s effectively useless for doing anything else until Chrome has finished its update.

Chrome also seems to occasionally perform extensive housekeeping tasks. At least I guess that’s what it’s doing. Every so often it will grab 100% of the CPU for about 10 minutes without any Internet I/O. The only way I’ve found to do any other work on the computer is to close Chrome, then, when I don’t want to use the computer for a while, restart Chrome and let it do its thing.

The Basics

Google Maps API Tutorial

Using a percentage height for the map div

If you try to use style=”width:100%;height:100%” on your map div, you get a map div that has zero height. That’s because the div tries to be a percentage of the size of the <body>, but by default the <body> has an indeterminate height.

There are ways to determine the height of the screen and use that number of pixels as the height of the map div, but a simple alternative is to change the <body> so that its height is 100% of the page. We can do this by applying style=”height:100%” to both the <body> and the <html>. (We have to do it to both, otherwise the <body> tries to be 100% of the height of the document, and the default for that is an indeterminate height.)

The <body> also has a margin by default. If you want to switch that off, add “margin:0” to the <body> style, to push the body right out to the edge of the screen.

In this example I’ve set the map div height to 96% to leave room for the back link.