mygeoland.com is a minimalist location search for Australia. By offloading everything
to the power of Virtual Earth it provides a simple, fast and fun way to find a street,
suburb or place in Australia. In this article I explain how simple it really is
as we build the site from the ground up with a few div tags and a handful of JavaScript.
I will then touch on some really simply ways we can optimise such a site using free
third party tools and server options.

Go check it out: mygeoland.com
Simply double click the state, type the street name and extension e.g. ‘smith st’
and press enter.
Because you centred the map on the state, the search box automatically populates
the state and country. So what have we done to put this together? Isn’t it just
a stock Virtual earth control with a textbox. Well guess what? Pretty much it is,
with a few tricks to make it work well and no crap put in.
Stage 1 – stock map control
This is the basic html page that displays a road map over east coast Australia.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript"
src="http://dev.virtualearth.net/mapcontrol/v4/mapcontrol.js"></script>
<script type="text/javascript">
var map = null;
function GetMap()
{
map = new VEMap('myMap');
map.LoadMap(new VELatLong(-29, 152),5,'r',false);
map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
}
</script>
</head>
<body onload="GetMap();">
<div id="myMap" style="position:relative; width:400px; height:400px;"></div>
</body>
</html>
View Page.
Stage 2 – full screen and dispose
We set the map div to 100% height and 100% width but we also need to hook the body
onresize to tell the VE control we have resized to a specific size, I utilise some
cross-browser javascript to calculate the new size.
<body onload="GetMap();" onresize="ResizeMap();" onunload="UnloadMap();">
<div id="myMap" style="width:100%;height:100%;position:relative;"></div>
</body>
While we are here we also want to hook the body onunload event and dispose of the
map object.
function ResizeMap() //resize
{
if (map != null)
{
if( typeof( window.innerWidth ) == 'number' )
{
//Non-IE
map.Resize(window.innerWidth,window.innerHeight);
}
else if( document.documentElement && ( document.documentElement.clientWidth
|| document.documentElement.clientHeight ) )
{
//IE 6+ in 'standards compliant mode'
map.Resize(document.documentElement.clientWidth
,document.documentElement.clientHeight);
}
}
}
function UnloadMap() //dispose map etc
{
if (map != null)
{
map.Dispose();
}
}
View Page.
Stage 3 – custom search box and disambiguation box
For the search box I add a simply div to the page containing a textbox and a go
hyperlink with some css styles. For the disambiguation box I add another div, again
with some css styles.
<div id="mySearch" style="position:absolute;top:12px;left:100px;font: bold 10px arial;">
<input tabindex="0" id="SearchInput" style="width:280px;font:bold 14px arial;
filter:Alpha(Opacity=80);opacity:0.8;" type="text" onkeydown="Keypress(event);"
autocomplete="off" size="20" /> <a
href="javascript:Search(document.getElementById('SearchInput').value);"
>GO</a></div>
<div id="myDisambiguation" style="display:none;position:absolute;width:400px;top:34px;
left:100px;z-index:1000;background-color:White;font:bold 14px arial;
border: solid 1px black;"></div>
We have to add the following JavaScript to wire it up, we accept a key press of
enter to search, put a pin at the right location of the result – DO NOT USE map.GetCenter()
- and produce the html for the disambiguation box.
function Keypress(e) //keypress
{
if (e.keyCode == 13) {
Search(document.getElementById('SearchInput').value);
}
}
function Search(s) //search
{
DisambiguationClose();
if (s != '')
{
map.Find(null,s,1,SearchResults);
}
}
function SearchResults(results) //search returned
{
map.Clear();
map.AddPushpin(new VEPushpin("sPin", results[0].LatLong));
}
function Disambiguation(e) //Disambiguation Callback
{
var r="<div style='float:left'>Multiple results.
Choose or refine search.</div><div
style='float:right;cursor:pointer;'
onclick='DisambiguationClose();'>X</div><br />";
for (x=0; x<e.length; x++)
{
r+="<a onclick='Search(\""+e[x].ID+"\");' href='#'>"+e[x].ID+"</a><br>";
}
document.getElementById('myDisambiguation').innerHTML=r;
document.getElementById('myDisambiguation').style.display = "block";
}
function DisambiguationClose() //close DisambiguationCallback
{
document.getElementById("myDisambiguation").style.display = "none";
}
View Page.
Stage 4 – support for controls in 3D mode
In order for our search box and disambiguation box to appear in 3D mode we have
to generate a “shim”. The shim is an iframe that allows the control to be placed
on top of the 3D layer.
We add some generic add and remove functions and then call the function when the
map changes to 3D mode. This is achieved by hooking the “oninitmode” event.
function GetMap()
{
map = new VEMap('myMap');
map.LoadMap(new VELatLong(-29, 152),5,'r',false);
map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
map.DisambiguationCallback = Disambiguation;
map.ShowDisambiguationDialog(false);
map.AttachEvent('oninitmode', ModeChange);
map.AddControl(document.getElementById("mySearch"));
map.AddControl(document.getElementById("myDisambiguation"));
AddShim(document.getElementById("mySearch"),"mySearchShim");
}
function AddShim(el,sid) //add iframe shim
{
if (map.GetMapMode() == VEMapMode.Mode3D)
{
var s = document.createElement("iframe");
s.id = sid;
s.frameBorder = "0";
s.style.position = "absolute";
s.style.zIndex = "1";
s.style.top = el.offsetTop;
s.style.left = el.offsetLeft;
s.width = el.offsetWidth;
s.height = el.offsetHeight;
s.scrolling="no";
s.className="Shim";
el.shimElement = s;
el.parentNode.insertBefore(s, el);
}
}
function RemoveShim(sid) //remove iframe shim
{
var msh = document.getElementById(sid);
if (msh!=null) msh.parentNode.removeChild(msh);
msh = null;
}
function ModeChange() //Change from 2d to 3d etc
{
if (map.GetMapMode() == VEMapMode.Mode3D)
{
AddShim(document.getElementById("mySearch"),"mySearchShim");
if (document.getElementById("myDisambiguation").style.display == "block")
{
AddShim(document.getElementById("myDisambiguation"),"myDisambiguationShim");
}
}else
{
RemoveShim("mySearchShim");
RemoveShim("myDisambiguationShim");
}
}
View Page.
Stage 5 – some hard coded reverse geocoding
To make the site auto-populate the state and country for Australia we hook the onfocus
event for the text box and work out the centre location of the map with a set of
if..else statements.
function GetLocation() //GetLocation
{
//don't change if the Disambiguation box is up.
if (document.getElementById('myDisambiguation').style.display == "none")
{
var mc = map.GetCenter();
var mla = mc.Latitude;
var mlo = mc.Longitude;
var s = "";
if (mla < -8.67 && mla > -45.34 && mlo > 110.92 && mlo < 158.20) //AUS
{
if (mlo < 129) //WA
{
if (s.indexOf("WA") == -1)
s = s + ", WA";
}else if (mlo < 138 && mla > -26) //NT
{
if (s.indexOf("NT") == -1)
s = s + ", NT";
}else if (mlo < 141 && mla < -26) //SA
{
if (s.indexOf("SA") == -1)
s = s + ", SA" ;
}else if (mla > -28.23) //QLD
{
if (s.indexOf("QLD") == -1)
s = s + ", QLD" ;
}else if (mla > -36.8) //NSW
{
if (mla < -35.16 && mla > -35.93 && mlo > 148.8 && mlo < 149.21) //ACT
{
if (s.indexOf("ACT") == -1)
s = s + ", ACT";
}else //NSW
{
if (s.indexOf("NSW") == -1)
s = s + ", NSW";
}
}else if (mla > -40) //VIC
{
if (s.indexOf("VIC") == -1)
s = s + ", VIC";
}else //TAS
{
if (s.indexOf("TAS") == -1)
s = s + ", TAS";
}
//add AUS suffix
if (s.indexOf("AUS") == -1)
s = s + ", AUS";
}
document.getElementById("SearchInput").value = s;
SetFocus();
}
}
function SetFocus() //setfocus
{
var elm = document.getElementById("SearchInput");
if (elm.setSelectionRange) {
elm.focus();
elm.setSelectionRange(0, 0);
}
else if (elm.createTextRange) {
var r = elm.createTextRange();
r.collapse(true);
r.moveEnd('character', 0);
r.moveStart('character', 0);
r.select();
}
}
function SetDelayedFocus() //delayed focus
{
if (map.GetMapMode() != VEMapMode.Mode3D)
{
setTimeout("SetFocus()",100);
}
}
View Page.
Clearly this technique is error prone around the boundaries and doesn’t scale but
it is a simple demo and is very fast. To make this more accurate and support the
entire world you make an AJAX call to reverse geocode the position using one of
the many services available such as:
http://www.geonames.org/export/#ws
Stage 6 – hide the VE message boxes
To suppress some of the VE message boxes we add some css to override the visibility
of the divs created. These are bound to change in the future so use at your own
peril.
<style type="text/css">#a_vemessagepanel, #threeDNotification {visibility:hidden;}</style>
View Page.
Stage 7 – Firefox 2 support
At the time of writing VE4 didn’t support Firefox 2.0. This simple snippet of js
will get around that.
function GetMap()
{
// START FIX FOR FF2.0
var ffv = 0;
var ffn = "Firefox/"
var ffp = navigator.userAgent.indexOf(ffn);
if (ffp != -1) ffv = parseFloat(navigator.userAgent.substring(ffp + ffn.length));
// If we're using Firefox 1.5 or above override
// the Virtual Earth drawing functions to use SVG
if (ffv >= 1.5) {
Msn.Drawing.Graphic.CreateGraphic=function(f,b)
{ return new Msn.Drawing.SVGGraphic(f,b) }
}
// END FIX FOR FF2.0
map = new VEMap('myMap');
map.LoadMap(new VELatLong(-29, 152),5,'r',false);
map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
map.DisambiguationCallback = Disambiguation;
map.ShowDisambiguationDialog(false);
map.AttachEvent('oninitmode', ModeChange);
map.AttachEvent('onclick', SetDelayedFocus);
map.AddControl(document.getElementById("mySearch"));
map.AddControl(document.getElementById("myDisambiguation"));
AddShim(document.getElementById("mySearch"),"mySearchShim");
SetFocus();
}
View Page.
Stage 8 – preloading message for VE JavaScript
The aim here is to make a really fast site. Later we will optimise your code but
we will always face the overhead of the VE JavaScript. Rather than having to wait
for the JavaScript to load before the page is shown what we do here is remove the
link and dynamically load the JavaScript after the page is loaded. This way the
site will load very quickly and show the loading and usage instructions.
A word of caution: I was unable to successfully dynamically include the proper “http://dev.virtualearth.net/mapcontrol/v4/mapcontrol.js”
file. Instead I added the direct “http://local.live.com/veapi.asjx”. What this means
it the site could break as the versions changes. Keep an eye on the VE wiki for
updates or make a suggestion.
var map = null;
var le = 0;
function GetMap()
{
var head = document.getElementsByTagName("head")[0];
var s = document.createElement('script');
s.id = 'VEScript';
s.type = 'text/javascript';
s.src = "http://local.live.com/veapi.asjx";
head.appendChild(s);
DelayGetMap();
}
function DelayGetMap()
{
try {
// START FIX FOR FF2.0
var ffv = 0;
var ffn = "Firefox/"
var ffp = navigator.userAgent.indexOf(ffn);
if (ffp != -1) ffv = parseFloat(navigator.userAgent.substring(ffp + ffn.length));
// If we're using Firefox 1.5 or above override
// the Virtual Earth drawing functions to use SVG
if (ffv >= 1.5) {
Msn.Drawing.Graphic.CreateGraphic=function(f,b)
{ return new Msn.Drawing.SVGGraphic(f,b) }
}
// END FIX FOR FF2.0
map = new VEMap('myMap');
document.getElementById("myLoading").style.display = "none";
document.getElementById("myMap").style.display = "block";
document.getElementById("mySearch").style.display = "block";
map.LoadMap(new VELatLong(-29, 152),5,'r',false);
map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
map.DisambiguationCallback = Disambiguation;
map.ShowDisambiguationDialog(false);
map.AttachEvent('oninitmode', ModeChange);
map.AttachEvent('onclick', SetDelayedFocus);
map.AddControl(document.getElementById("mySearch"));
map.AddControl(document.getElementById("myDisambiguation"));
AddShim(document.getElementById("mySearch"),"mySearchShim");
SetFocus();
}catch(err)
{
if (le < 30)
{
le++;
setTimeout("DelayGetMap()",1000);
}else
{
alert("Site is busy.");
}
}
}
View Page.
Stage 9 – optimisation
We have no server side code! What we have is a static file the user downloads. Once
they have this file all the work is done by Virtual Earth, her farm of servers and
the clients broswser. So if we can reduce what the user needs to download from our
site we can improve their experience. Unless you have some serious web farm infrastructure
I bet we are the weakest link here.
Put into a separate js file
By moving the JavaScript into its own file it will be downloaded separately from
the html file and cached.
Replace variable names.
Although our code was nice and readable the browser doesn’t care. By changing the
variables and ID to several letters we vastly reduce the number of characters in
the code.
Run through optimiser to remove comments and whitespace
I used: http://www.xtreeme.com/javascript-optimizer
To remove my comments and whitespace. We now have a JavaScript file that is 4.1KB
compared to 10KB, that’s 2.4 times smaller!
Stage 10 – IIS6 settings
If you have access to your IIS settings there a couple of things that can make a
big difference to serving static files.
Firstly, turn on IIS compression. This using gzip to compress the textual files.
For our JavaScript and html page it makes a huge difference. Since the files are
static the compression only occurs once. IIS serves less data and the client gets
it faster. Html page is now 0.88KB and the JavaScript file is 1.59KB. If the browser
supports it the user will download only 2.47KB the first time (Plus the VE JavaScript)
and then only 880 Bytes on future requests!
Secondly enable kernel mode caching. See here for more information: http://www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/webapp/iis/iis6perf.mspx
Put simply this cache is accessed very early on in the processing logic of IIS.
It means your server can serve the page with less effort and therefore can serve
more pages/sec with little cpu usage.
Conclusion.
Here is the final product: mygeoland.com
In this article I hope you picked up a few tips on how to put together a simple
VE page and get it serving up quick. Your homework is to go a build a local street
search site for your country!
Virtual Earth is free for non-commercial use for sites serving less than 100,000
“transaction” per day.
So keep it local and find a decent host for your 2.47KB website! Then start adding
some really cool stuff like webcams, traffic reports, weather, favourite places
etc.