GPS tracking using SVG - Part 2

a paper for the 2002 SVG Open Developers Conference.

Continued from part 1

Where we left off:

We solved some interesting issues with regard to coordinate projection, and built an example, that loads in coordinates from an xml file, and plots a vehicle's movement over a map:

The SVG file:
example2.svg (Opens in a new window)

The color-coded source-code:
example2.html

So that's great, our application does exactly what we want it to.
There's just one thing though - the map isn't too great.
If this is to be of any use we'll need higher resolution maps, but without needing a massive download or it will slow the thing down too much.

Taking it to the next level:

That's literally what we'll do - add new levels of detail, and tie them in with SVG's zoomlevels.
The way this works, is that we load higher resolution maps, covering a smaller area, as we zoom in, like this:

Fig.1 to 5 - Increasing detail as we zoom-in:

Level 0 (not zoomed)
Increasing zoom levels

Level 1
Increasing zoom levels

Level 2
Increasing zoom levels

Level 3
Increasing zoom levels

Level 4
Increasing zoom levels

There are two ways of loading the higher resolution maps,

  1. Load the map-segments into a grid, usually using bounding-box measurements in feet. As we move close to the grid-border, the next segment is loaded. The sections are cached on the server, or locally on HD/CD.
  2. Load the maps centered on our vehicle-icon. As we move towards the edge of the map-segment, a new map is loaded, again centred on our vehicle-icon. The maps are created dynamically on the server, depending on the Longitude/Latitude we use in the map's URL-string.

In this case I am going to use the second method - although both have their merits, depending on the situation.
Let's put together a to-do list again:

  1. We need a map-server that returns centered maps on Longitude/Latitude requests.
  2. Catch our SVG's onzoom event, and determine what zoomlevel we're at.
  3. Add an <image> to our SVG, to load the map into.
  4. Make a loadMap() function that loads the map into the image, and positions it correctly.
  5. Call this function when the user zooms in or out, to show the correct map resolution.
  6. Add some of the final touches, like panning the window.

Ok, let's see about Step 1:

Setting up a map-server can range from uploading a few tiled images, to setting up full-blown web-services, that will require the necessary licenses to be paid and servers to be set-up.
So for the scope of this paper, and purely for educational purposes I'll use an existing, publicly available map server.
We even have a choice:

There are probably many others too, but I'll use mappoint for this exercise.
To start off we need to know what parameters we can pass in the map-server's URL, here's a URL breakdown:

URL:http://msrvmaps.mappoint.net/isapi/MSMap.dll
ID:(probably used for internal access-control)?ID=3XNsF.
Lat/Lon:&C=44.96,-93.22
Country code - could also be EUR0809 for instance.&L=USA0409
Unknown - needs to be set to "1"&CV=1
Zoom level - 1 is very close, 22000 is the whole world (cool!)&A=285
Image size(in pixels)&S=1000,1000
Offset (Used for panning, not needed in this case)&O=-0,0

Try it, Minneapolis up close:

http://msrvmaps.mappoint.net/isapi/MSMap.dll?ID=3XNsF.&C=44.96,-93.22&L=USA0409&CV=1&A=1&S=500,500&O=-0,0

or viewed from outer-space:

http://msrvmaps.mappoint.net/isapi/MSMap.dll?ID=3XNsF.&C=44.96,-93.22&L=USA0409&CV=1&A=22000&S=500,500&O=-0,0

(Open in new windows)

Disclaimer: Like I said, we use this service for personal,educational, non-commercial use only.
If you are interested in commercial use contact the companies involved.

OK, now for Step 2:

Catching the onzoom event, and finding out what the current zoomlevel is, isn't that hard:

<svg onzoom="catchZoom()">
<rect x="80px" y="10px" width="40px" height="40px" /> 
<script>
	function catchZoom(){
		if (!window.svgDocument)svgDocument = evt.target.ownerDocument;
		alert("currentScale = "+svgDocument.rootElement.currentScale)
	}
</script>
</svg> 

Open the example, and zoom in or out - an alert will show the currentScale. This ranges from 0.0625 to 16, 1 being original size.

Example 3:

example3.svg (Opens in a new window)

At this point we have to drop Batik support, as Batik doesn't have the onzoom event implemented yet.
It has however been mentioned as a feature request, so it's likely to be added soon.

In Step 3 we add an image to our SVG:

We'll use this image to load our dynamic maps.
I also add a line in init() to get a handle on the image, so we can alter it's "xlink:href" property later.
To keep things simple I'll just use one image for now. Normally we'd use two, to avoid flashing while a map is loading.
for the moment we load a placekeeper image - pk.jpg.
width="200px" height="200px" are temporary values.

<svg onload="init()">
	<image id="detail" width="200px" height="200px" xlink:href="images/pk.jpg" />
	<script type="text/ecmascript">
		function init(){
			if (!window.svgDocument)svgDocument = evt.target.ownerDocument;
			mapLoader=svgDocument.getElementById("detail")	
		}
	</script>
</svg> 

In Step 4 we add a "loadMap()" function:

This loads the map into the image, and positions it correctly.

function loadMap(lonDist,latDist,LONGITUDE,LATITUDE){
	vehicle.lastxfeet=lonDist
	vehicle.lastyfeet=latDist
	//We set some default values. These should always have the same proportion to each other,
	//so we can divide all three, or multiply all three by the same amount.
	//Multiplying times 2 loads large map (large area), dividing by two loads small maps (modem users)
	var startSize=120000
	var startZoom=60
	var startRes=350 
	
	//We use the currentScale to get a usable multiplication coefficient.
	c=(svgDocument.rootElement.currentScale/2)+0.8
	
	var res=Math.round(startRes+((startRes/8)*c))
	var zoom=Math.round(startZoom/c)
	//As we have to pass integers in the URL, we check what the rest was, to adjust the display size accordingly:
	var zoomRest=(zoom/(startZoom/c))
	//Load the map passing the coords and size
	mapLoader.setAttribute("xlink:href","http://msrvmaps.mappoint.net/isapi/MSMap.dll?ID=3XNsF.&C="+parseFloat(LATITUDE)+","+parseFloat(LONGITUDE)+"&L=USA0409&CV=1&A="+zoom+"&S="+res+","+res+"&O=-0,0");
	
	//Calculate what size to display the map at + set size:
	var width=((startSize/c)*zoomRest)+res*2
	var height=((startSize/c)*zoomRest)+res*2
	mapLoader.setAttribute("width",width)
	mapLoader.setAttribute("height",height)
	vehicle.mapLoaderSize=width
	//Calculate what location to display the map at + move:
	var xVal=parseInt(lonDist)-(width/2)
	var yVal=parseInt(latDist)-(height/2)
	var cont="translate("+xVal+" "+yVal+")"
	mapLoader.setAttribute("transform", cont)
	//Set the viewers size:
	vehicle.setSize(c)
}

In Step 5 we call "loadMap()" when the user zooms in or out:

This allows us to show the correct map resolution depending on how close we're zoomed in.
The onzoom event calls "catchZoom()". This basically tests the zoom-level, and loads a new image appropriate to that zoom-level.

In Step 6 we add a few finishing touches:

We keep track of the distance covered from the initial loading coordinate, so we can load the next map segment when our vehicle approaches the map's edge.
We use feet, instead of Lon/Lat for this, as the feet scale 1:1 to pixels in the SVG. So if we know our map's image is 500px wide, then if our vehicle travels more than 200 feet from the initial position, we load the next map (by setting border=true)- again centred on the vehicles position, and reset the lastxVal and lastyVal variables.

Another thing to fix is that we want our browser's viewport to follow the vehicle, so the vehicle remains visible as it moves.
The way I did this works well, despite it's simplicity:


	var difx=(this.xfeet[i-1]-this.xfeet[i])
	var dify=(this.yfeet[i-1]-this.yfeet[i])
	var cof=(200000/Math.min(window.innerHeight,window.innerWidth))/d.currentScale
	d.currentTranslate.y+=(dify/cof)
	d.currentTranslate.x+=(difx/cof)
	

In line 1 and 2 we subtract the previous position from the current one, to find out what amount to pan the viewport by.
In line three we find the ratio our map is being displayed at, as when the map is zoomed, 1px in size is no longer 1px on the screen.
In the remaining lines we pan the window by the equivalent of the amount the vehicle moved.

Let's have a look what we have so far:

In part 1 we solved some issues with regard to coordinate projection, and built an example, that loads in coordinates from an xml file, and plots a vehicle's movement over a map.
In part 2 we added the high-definition maps that load more detailed areas, as the viewer zooms in:

The SVG file:
example4.svg (Opens in a new window)

The color-coded source-code:
example4.html

Well, that's about it for this paper - I hope you found it interesting, if you have any comments or questions don't hesitate to email me.


Cheers,
Richard Bennett.
mail@richardinfo.com

Valid XHTML 1.0!