David Janes' Code Weblog

November 7, 2008

How to use the Google Maps API

demo,html / javascript,maps,tips · David Janes · 6:32 am ·

The Google Maps API is the great granddaddy of mapping API. Originally, the GMap API was an internal private Google API until reverse engineered by Adrian Holovaty to create the first great mapping mashup, Chicago Crime (now folded into Every Block). The API is well documented, well understood and easy-to-use but suffers from the look getting a little long in the tooth and the API key design imposing a higher administrative burden than the competing packages.

The way we set up the events in Google Maps is a little different than we did in MapQuest, Yahoo Maps and Visual Earth. GMaps uses a two phase creation scheme, using one call to create the map object and a second call to actually display it somewhere. I like this and it would have got around the “jumping map” issue we saw in Virtual Earth. Because we want to capture the load event – to get the initial state of the map – we have to put it between these two calls otherwise we’ll miss the load event (it would be nice if Google just called your “onload” callback if the map was already loaded).

My least favorite part of the GMap API is that you need a per-domain API key. This is the most restrictive licensing of any of the mapping packages. Amongst the problems this will cause you:

  • if you’re working in development, staging, deployment environments you’ll have to drive your map from dynamic HTML, as you’ll need to switch in the correct key
  • http://user.example.com, it will mean that all your users will have to individually sign up for API keys — e.g. uptake will be slow. update: see the comments

The documentation for the GMap API is by far the best of any of the mapping package. Everything is cleanly laid out and there’s no question how you get from concepts, to examples, to the API spec.

Here’s our example map application and the source below. You’ll have to get your own API key and substitute it in if you want to start using this example. Geocoding is almost as easy as in the Yahoo API, with the caveat that you really want to set the baseCountryCode, as it won’t make a guess for the country on your behalf. Unlike all the other applications, the GMap API will not take strings where it expects numbers, hence all the parseFloats in the example below.

<html>
<head><script
    type="text/javascript"
    src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=YOURKEY"></script>
<style type="text/css">
#id_map {
    height: 75%;
    width: 100%;
}
</style>
</head>
<body>
<div id="id_map"></div>
<p>
Lat: <input type="text" id="id_lat" onchange="map_js.onupdate_ll()" />
Lon: <input type="text" id="id_lon" onchange="map_js.onupdate_ll()" />
Zoom: <input type="text" id="id_zoom" onchange="map_js.onupdate_zoom()" />
</p>
<script type="text/javascript">
map_js = {
    e_lat : null,
    e_lon : null,
    e_map : null,
    g_map : null,
    g_geocoder : null,

    create : function() {
        map_js.e_map = document.getElementById("id_map");
        map_js.e_lat = document.getElementById("id_lat");
        map_js.e_lon = document.getElementById("id_lon");
        map_js.e_zoom = document.getElementById("id_zoom");

        map_js.g_map = new GMap2(map_js.e_map);
        map_js.g_geocoder = new GClientGeocoder();
        map_js.g_geocoder.setBaseCountryCode("ca")

        GEvent.addListener(map_js.g_map, "load", map_js.onposition);
        GEvent.addListener(map_js.g_map, "moveend", map_js.onposition);
        GEvent.addListener(map_js.g_map, "zoomend", map_js.onposition);

        map_js.g_map.addControl(new GSmallMapControl());
        map_js.g_map.addControl(new GMapTypeControl());

        map_js.g_geocoder.getLatLng("Toronto, ON", map_js.ongeocoded);
        // map_js.g_map.setCenter(new GLatLng(43.648565, -79.385329), 13);
    },

    ongeocoded : function(point) {
        if (!point) {
            alert("not found");
        } else {
            map_js.g_map.setCenter(point, 13);
        }
    },

    onposition : function() {
        var c = map_js.g_map.getCenter();
        map_js.e_lat.value = c.lat();
        map_js.e_lon.value = c.lng();

        var z = map_js.g_map.getZoom();
        map_js.e_zoom.value = z;
    },

    onupdate_ll : function() {
        map_js.g_map.panTo(
            new GLatLng(parseFloat(map_js.e_lat.value), parseFloat(map_js.e_lon.value)));
    },

    onupdate_zoom : function() {
        map_js.g_map.setZoom(parseInt(map_js.e_zoom.value));
    },

    end : 0
};
map_js.create();
</script>

November 5, 2008

How to use the Microsoft Virtual Earth API

demo,html / javascript,maps,tips · David Janes · 1:40 pm ·

Next up in our tour of Javascript mapping APIs is Microsoft’s Virtual Earth. VE is a full-featured mapping API with a visually appealing look, but with a number of gotchas what watchfors which I’ll list here.

VE is rather picky about the CSS and styles it sees (not nearly so much as MapQuest though!). In particular:

  • make sure you add a position: relative or position: absolute to the CSS definitions for the map’s DIV, otherwise it will go crazy on at least Firefox. I added position: relative and it worked fine
  • contrary to the documentation, VE wants to see the style rules directly on the element rather than inherited from CSS, otherwise it seems to default to the 600px by 400px default. Fortunately, once you’ve done this it does understand width: 100% and similar.

Unlike MapQuest and Yahoo Maps, VE comes with a rather comprehensive map control installed. The downside of this is that if you want a simpler interface you’re going to have to code it yourself. In particular, there’s an option for a “Bird’s Eye” view of the map you’re looking at. Once you click on this, concepts like the map having a latitude and longitude go out the window (for reasons I don’t grasp). You can see in the example below that we actually test to see if Lat/Lon is available before attempting to display it. Also note that the documentation for VEMap.GetCenter is wrong — a null is not returned; instead Latitude and Longitude are null.

Microsoft really needs to adopt a more modern view of Javascript. One obvious thing would be to start using namespaces to isolate code — use VE.Map rather than VEMap. The VEMap constructor doesn’t accept a (DOM) element as an argument, you have to pass the ID. Another ugliness is the use of lengthy optional argument lists, such as this atrocity:

VEMap.Find(what, where, findType, shapeLayer, startIndex,
  numberOfResults, showResults, createResults, useDefaultDisambiguation,
  setBestMapView, callback);

This was understandable in the Win32 C-code days but not so much when passing a dictionary would accomplish much of the same in a simpler manner.

VE includes a rather straightforward geocoding call – despite what the documentation above implies. Just do:

map_js.ve_map.Find(null, "Toronto, ON");

Unfortunately, this has a two shortcomings:

  • you cannot “Geocode at setup time” like you can in Yahoo Maps, so you have to display one location, then jump to the one you really want
  • the Geocoder changes you’re zoom level, whether you want it to or not. From a mapping point-of-view I could see how this makes sense (e.g. show all of Toronto), but it would be nice if it was optional

We’ve got the geocoder in the example below commented out, but you may find it useful. Just remember that it’s asynchronous, so the actual map jump will not happen until after your current JS terminates.

Finally, to end on high note, VE doesn’t appear to need a developer or application key meaning you can just take this example to modify and run as you please. Here’s our example map application and the source below:

<html>
<head>
<script charset="UTF-8" type="text/javascript"
  src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&mkt=en-us"></script>
<style type="text/css">
#id_map{
position: relative;
}
</style>
</head>
<body>
<div id="id_map" style="width: 100%; height: 75%;">&nbsp;</div>
<br />
<p>
Lat: <input type="text" id="id_lat" onchange="map_js.onupdate_ll()" />
Lon: <input type="text" id="id_lon" onchange="map_js.onupdate_ll()" />
Zoom: <input type="text" id="id_zoom" onchange="map_js.onupdate_zoom()" />

</p>
<script type="text/javascript">
map_js = {
	e_lat : null,
	e_lon : null,
	e_map : null,
	ve_map : null,

	create : function() {
		map_js.e_lat = document.getElementById("id_lat");
		map_js.e_lon = document.getElementById("id_lon");
		map_js.e_zoom = document.getElementById("id_zoom");

		map_js.ve_map = new VEMap("id_map");
		map_js.ve_map.LoadMap(new VELatLong(43.648565, -79.385329), 13,
		  VEMapStyle.Road, false, VEMapMode.Mode2D, true, 1);
		// map_js.ve_map.Find(null, "Toronto, ON");

		map_js.ve_map.AttachEvent("onendzoom", map_js.onposition);
		map_js.ve_map.AttachEvent("onendpan", map_js.onposition);

		map_js.onposition();
	},

	onposition : function() {
		var c = map_js.ve_map.GetCenter();
		if (!c || !c.Latitude) {
			return;
		}

		map_js.e_lat.value = c.Latitude;
		map_js.e_lon.value = c.Longitude;

		var z = map_js.ve_map.GetZoomLevel();
		map_js.e_zoom.value = z;
	},

	onupdate_ll : function() {
		map_js.ve_map.PanToLatLong(new VELatLong(map_js.e_lat.value, map_js.e_lon.value));
	},

	onupdate_zoom : function() {
		map_js.ve_map.SetZoomLevel(map_js.e_zoom.value);
	},

	end : 0
};
map_js.create();
</script>
</body>
</html>

Tip – how to get your browser's User Agent

demo,html / javascript,ideas,tips · David Janes · 10:16 am ·

Every browser normally sends a User-Agent header in its HTTP request. You can use this header to determine what browser and version a website is using, i.e. Internet Explorer, Firefox, whether it’s a mobile browser, an iPhone, etc..

So, what’s your browser’s User-Agent? Just type this in the address bar:

javascript:alert(navigator.userAgent)

Source

November 4, 2008

How to use the MapQuest API

demo,html / javascript,maps,tips · David Janes · 8:02 am ·

This post should be subtitled “MapQuest, I’m disappointed in you”. I have to say I was looking for a lot more out of this toolkit, because they’re a late entrant to the modern AJAX-y map interface so they should have known what will work and what would; because they’re still (I think) the #1 online mapping company;  because I like cheering for scrappy underdog even if they’re really giant behemoths; and finally because I saw Kevin Survance, CTO of MapQuest speak at the Ajax Experience in Boston in 2007 and thought his presentation rocked.

Alas, there’s a number of shortcomings you should know about before you throw away your Google Maps (Yahoo Maps, Live Maps) application:

  • the maps don’t look that great – there’s a lot pixelation due to light or non-existent anti-aliasing
  • the map size has to be set explicitly as a style tag using pixels, not percentages! No CSS for you!
  • the Geocoding feature requires a local proxy – while I understand the technical appeal of this, all the other mapping packages have got around this, why can’t MapQuest? As a result, our example app below does not demonstrate geocoding, it just hard-codes the lat/lon
  • I wasn’t that impressed with the documentation / “hello world”-type examples. One issue here is that MapQuest may be targeting desktop applications and so browser JS is suffering for attention

Now, there seems to be a fairly complete API so there may be further virtues to the MapQuest API that I’m missing, so take that as a caveat. On the other hand, there’s a level of equivalency in map dimensions between all the other mapping packages that MapQuest doesn’t have; I’ll quantify this after I complete this survey of map APIs.

I’ve created an example map application which displays a map, the current lat/lon and zoom level, and lets the map position to be adjusted by editing those same values. If you’d like to get started with the MapQuest API yourself:

  • register with MapQuest to get developer keys, etc.. Note that the examples here is using the default MapQuest key and seem to work fine for testing; I just wouldn’t use that for production purposes
  • copy the code below to “map.html” in favorite hosting environment — it should even work off your disk
  • add your developer key, if you got one, in the appropriate place
  • run, enjoy, modify

Further reading:

Source (note the script src has been split for formatting purposes):

<html>
<head>
<script src="http://btilelog.access.mapquest.com/tilelog/transaction?transaction=script&
key=mjtd%7Clu6t2h07n1%2C2x%3Do5-lw7l9&itk=true&v=5.3.s&ipkg=controls1,traffic&ipr=false"
    type="text/javascript"></script>
</head>
<body>
<div id="id_map" style="width: 1024px; height: 400px;"></div>
<p>
Lat: <input type="text" id="id_lat" onchange="map_js.onupdate_ll()" />
Lon: <input type="text" id="id_lon" onchange="map_js.onupdate_ll()" />
Zoom: <input type="text" id="id_zoom" onchange="map_js.onupdate_zoom()" />
</p>
<script type="text/javascript">
map_js = {
    e_lat : null,
    e_lon : null,
    e_map : null,
    mq_map : null,

    create : function() {
        map_js.e_map = document.getElementById('id_map');
        map_js.e_lat = document.getElementById("id_lat");
        map_js.e_lon = document.getElementById("id_lon");
        map_js.e_zoom = document.getElementById("id_zoom");

        map_js.mq_map = new MQA.TileMap(map_js.e_map, 9,
             new MQA.LatLng(43.648565, -79.385329), "map");
        map_js.mq_map.addControl(new MQA.ZoomControl());
        map_js.mq_map.addControl(new MQA.ViewControl());

        MQA.EventManager.addListener(map_js.mq_map, "zoomend", map_js.onposition);
        MQA.EventManager.addListener(map_js.mq_map, "dragend", map_js.onposition);

        map_js.onposition();
    },

    onposition : function() {
        var c = map_js.mq_map.getCenter();
        map_js.e_lat.value = c.getLatitude();
        map_js.e_lon.value = c.getLongitude();

        var z = map_js.mq_map.getZoomLevel();
        map_js.e_zoom.value = z;
    },

    onupdate_ll : function() {
        map_js.mq_map.panToLatLng(new MQA.LatLng(map_js.e_lat.value, map_js.e_lon.value));
    },

    onupdate_zoom : function() {
        map_js.mq_map.setZoomLevel(map_js.e_zoom.value);
    },

    end : 0
};
map_js.create();
</script>
</body>

November 2, 2008

How to use the Yahoo Maps Service AJAX API

demo,html / javascript,maps,tips · David Janes · 8:02 am ·

Yahoo is a nice alternative service to Google Maps for displaying maps on your website or service. Amongst its benefits:

  • it’s super easy to set up, as we’ll show below
  • it requires an “application key”, but once you have this you can run on any domain. GMaps requires a per-domain key, which means that if your deploying to multiple domains — for example, if you have a http://user.example.com type site — you’re suddenly going to find yourself with an intractable problem
  • it plays moderately well with YUI conceptually, though it doesn’t seem to use the same code base. UPDATE: you have to do hacks to work with YUI. Sigh.

Note that Yahoo provides several APIs for maps, the AJAX API we’re using here, an API for Flash/Actionscript and a REST API for getting map images. There all different, so make sure you’re not reading the wrong docs for what you’re doing.

I’ve created an example map application which displays a map, the current lat/lon and zoom level, and lets the map position to be adjusted by editing those same values. If you’d like to get started with the Yahoo AJAX maps API:

  • get an application key – do not reuse mine or anyone else’s please
  • copy the code below to “map.html” in favorite hosting environment — it should even work off your disk
  • add your application key in the appropriate place
  • run, enjoy, modify

Further reading:

Source:

<html>
<head>
<script type="text/javascript"
src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=YOURAPPID"></script>
<style type="text/css">
#map{
height: 75%;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<p>
Lat: <input type="text" id="id_lat" onchange="map_js.onupdate_ll()" />
Lon: <input type="text" id="id_lon" onchange="map_js.onupdate_ll()" />
Zoom: <input type="text" id="id_zoom" onchange="map_js.onupdate_zoom()" />
</p>
<script type="text/javascript">
map_js = {
    e_lat : null,
    e_lon : null,
    e_map : null,
    y_map : null,

    create : function() {
        map_js.e_map = document.getElementById('map');
        map_js.e_lat = document.getElementById("id_lat");
        map_js.e_lon = document.getElementById("id_lon");
        map_js.e_zoom = document.getElementById("id_zoom");

        map_js.y_map = new YMap(map_js.e_map);
        map_js.y_map.addTypeControl();
        map_js.y_map.addZoomLong();
        map_js.y_map.addPanControl();
        map_js.y_map.setMapType(YAHOO_MAP_REG);

        YEvent.Capture(map_js.y_map, EventsList.endMapDraw, map_js.onposition);
        YEvent.Capture(map_js.y_map, EventsList.changeZoom, map_js.onposition);
        YEvent.Capture(map_js.y_map, EventsList.endPan, map_js.onposition);

        map_js.y_map.drawZoomAndCenter("Toronto, ON", 5);
    },

    onposition : function() {
        var c = map_js.y_map.getCenterLatLon();
        map_js.e_lat.value = c.Lat;
        map_js.e_lon.value = c.Lon;

        var z = map_js.y_map.getZoomLevel();
        map_js.e_zoom.value = z;
    },

    onupdate_ll : function() {
        map_js.y_map.panToLatLon(new YGeoPoint(map_js.e_lat.value, map_js.e_lon.value));
    },

    onupdate_zoom : function() {
        map_js.y_map.setZoomLevel(map_js.e_zoom.value);
    },

    end : 0
};
map_js.create();
</script>
</body>
</html>

October 31, 2008

How to detect internal link jumps

html / javascript,tips · David Janes · 5:48 pm ·

If you play with our post GenX – first public demonstration, you’ll see that the an internal link marker “External“. Clicking on any of these links will bring you a highlighted section of the document, such as this. You’ll also see this on Wikipedia, for example here.

The way to do this is quite simple with CSS3 using the ‘:target’ pseudo-class:

div.gencard:target {
    background-color: rgb(228, 241, 228);
}

But let’s say you don’t have access to CSS3 or you need to highlight a parent element (this is actually how I started this investigation). CSS doesn’t provide a way to select a parent element that has a child with a certain set of properties.

Given that we can’t use CSS, the next tool we look to is JS. One’s initial take for this would be quite simple:

  • create a document load event
  • in that event, look at window.location.hash
  • if substr(1) refers to an existing element, highlight whatever it is you need

But what happens if you click on an “internal link”, e.g. one like <a href=”#id_internal”>? Well, after a little research it turns out you’re out of luck – no DOM provides such an event. Alas, it turns out – as far as I’ve been able to see – the solution is write a little JS timer than monitors the window.location for changes, and if it has, update your highlight appropriately.

Further reading:

October 28, 2008

More style updates

administrivia,html / javascript,semantic web · David Janes · 6:36 am ·

I’ve added hAtom to this weblog’s template: you can see a parsed version here. I’ve also updated the comments to be prettier.

Next, to figure out what this gravatar stuff is and to expand the blogroll.

October 26, 2008

Tip – fixing broken menus over form on IE6 and IE7

html / javascript · David Janes · 7:17 pm ·

If you use pop up menus on your site, you may find that they don’t work very well on IE6 and sometimes on IE7 also. In particular, FORM fields (INPUT and SELECT) show above the menus and sometimes the text of BUTTONs appear on top of the menu.

This post describes how to fix this. The technique described as the same as this one here, but you may find this a little easier to implement. Honestly, your best solution is not to code menus yourself but find a menus package that handles this for you. However, you may find yourself in a situation like I where there is no choice: you have to retrofit existing code.

This is how you do it:

Add the following to your CSS – this styles an IFRAME that we’re going to drop into a menu. Initially I though the z-index was not needed, but it turns out you want to make sure the IFRAME is below the menu because otherwise the IFRAME will capture mouse and keyboard events – i.e. your menu will not work! It doesn’t seem to matter what the z-index of the IFRAME relative to the background form is though.

<style type="text/css">
.menu_iframe {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 0px;
    height: 0px;
    filter: alpha(opacity = 0);
    z-index: -1;

}
<style>

Then add the following to the element that is your menu popup, at the very top:

<!--[if lte IE 7]><iframe class="menu_iframe"></iframe><![endif]-->

This will add on IE6/7 only (due to the magic Internet Explorer conditions) an IFRAME to the menu popup. Other browers will not add this IFRAME and will continue to work as per normal. Update: see the note at the bottom if you are doing this on a HTTPS secure page.

Add this code to your JavaScript:

ie_done = {}

function miframe(e_menu) {
    if (!e_menu) return;
    if (ie_done[e_menu.id]) return;
    ie_done[e_menu.id] = 1;

    var e_iframes = e_menu.getElementsByTagName("iframe");
    if (e_iframes.length == 0) return;

    e_iframes[0].style.width = e_menu.offsetWidth;
    e_iframes[0].style.height = e_menu.offsetHeight;
}

This function will – the first time it is called per-menu – resize the first IFRAME found in the menu to be the same size of the menu. Note the dependency on having an ID tag on the menu popup!

When you’re popping up your menu, call miframe with the menu element and this will make the IFRAME the same size of the menu. The IFRAME magically blocks out the form but also allows the menu to show through (play with the opacity parameter for fun: opaque is 100).

But it still doesn’t work!?

This is where it gets, well, weirder. It turns out that if you have anything marked up as position: relative, well, all the above doesn’t work. So make sure you get rid of all of those and find a different way to do fine tuning.

BUTTON tags never seem to work either – the text often filters through onto the menu. Alas, they’ve gone into the HTML junk heap for me for now until IE6 gets obsoleted. Try using a style A tag instead and suck up the ugliness.

Update: HTTPS pages

If you’re using HTTPS, the method above will have to be adjusted because you’ll start getting a “The page contains secure and non-secure items” message. My solution was to add a src="/empty.htm" tag but it occurs to me now that using src="about:blank" may work also but I haven’t tested it.

October 25, 2008

New style for this weblog

administrivia,html / javascript · David Janes · 2:56 pm ·

I’ve put together a basic WordPress theme for this site, to make it similar to everything else. More work will be forthcoming, including add hAtom to all appropriate places.

« Newer Posts

Powered by WordPress

Switch to our mobile site