HTML5’s Canvas! Let’s draw the world!

Following on from my Making a speedometer using HTML5′s Canvas and Making a thermometer using HTML5’s Canvas posts I decided to attempt another project.  In this post I’ll discuss the steps I followed while attempting to draw the world .  In addition to this I also implemented  HTML5’s new geographic location feature to obtain the latitude and longitude of the connecting client.  This location is then plotted on the map to produce the following:

Map canvas geolocation sample

The HTML Source

The following code is stripped to the minimum in order to draw the map above:
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>Canvas Map Plotting</title>
		<script src='map.js'></script>
	</head>
	<body onload='draw();'>
		<div>
			<canvas id="map" width="1580" height="790"></canvas>
		</div>
	</body>
</html>

The HTML is fairly straight forward. The items of interest are the canvas, the ‘onload’ event for the body and the script tag. For those who are not familiar with HTML, let me explain how the canvas turns into the map of the world. When any HTML page loads the browser will run the JavaScript code attached to the onload event of the body tag e.g.
         <body onload='draw();'>
The browser will load any JavaScript code included in a script tag prior to displaying the page to the user. Therefore, the content of my script “map.js” will be loaded and thus the function called ‘draw’ will be available. If you were to look inside the aforementioned script you will be able to see the code e.g.

function draw()
{
	// Main entry point got the map canvas example

	var canvas = document.getElementById('map');

	// Canvas supported?
	if(canvas.getContext)
	{
		var ctx = canvas.getContext('2d');

		// Draw the background
		drawBackground(ctx);

		// Draw the map background
		drawMapBackground(ctx)

		// Draw the map background
		drawGraticule(ctx);

		// Draw the land
		drawLandMass(ctx);

		// One-shot position request. (f supported)
		if (navigator.geolocation)
			navigator.geolocation.getCurrentPosition(plotPosition);

	}
	else
	{
		alert("Canvas not supported!");
	}
}
The content of the function is quite readable and the code is ordered in logic steps. The first step is to draw the background. The background is a simple rectangle filled with the colour black. The background will eventually include the map and a guide for printing the related latitude and longitude values. The function responsible for drawing the background is called ‘drawBackground’. It contains the following:
function drawBackground(ctx)
{
	// Black background
	ctx.fillStyle = "rgb(0,0,0)";

	// Draw rectangle for the background
	ctx.fillRect(iCANVAS_START_X_POS, iCANVAS_START_Y_POS, (iCANVAS_START_X_POS + iCANVAS_WIDTH), iCANVAS_START_Y_POS + iCANVAS_HEIGHT);

	ctx.stroke();
}
The function above sets the fill colour to black and then draws a rectangle. The parameters passed to the fillRect are defined in the JavaScript and exposed globally to the various functions.

 

The next step required is to draw the map background. The step is fulfilled by the function ‘drawMapBackground’. In order to orientate us, if at this point one was to draw the background and map the canvas would have the following finish:

Canvas background and map

This hopefully explains why there is a need for a base background and map background. It naturally provides a nice border area to print the latitude and longitude values. The ‘drawMapBackground’ function contains the following code:
function drawMapBackground(ctx)
{
	// Ocean blue colour!
	ctx.fillStyle = "rgb(10, 133, 255)";

	// Draw rectangle for the map
	ctx.fillRect(iMAP_START_X_POS, iMAP_START_Y_POS, iMAP_WIDTH, iMAP_HEIGHT);

}
Structurally this is a carbon copy of the background function with different parameters. In this case the fill colour is ocean blue and the rectangle is plotted using the MAP constants instead of the CANVAS constants.

 

The next steps is to draw the graticule. Graticule is the term used to describe the grid used in the geographic coordinate system. Thus, the function draws the grid lines as follows:
function drawGraticule(ctx)
{
	// Set distance between lines
	var iDEGREES_BETWEEN_LAT_GRID_LINES = 10;
	var iDEGREES_BETWEEN_LON_GRID_LINES = 10;

	// Style
	ctx.lineWidth = 0.2;
	ctx.strokeStyle = "rgba(0, 0, 0, 0.1)";
	ctx.fillStyle = 'rgb(255,255,255)';

	// Font styling
	ctx.font = 'italic 10px sans-serif';
	ctx.textBaseline = 'top';

	drawLatitudeLines(ctx, iDEGREES_BETWEEN_LAT_GRID_LINES);
	drawLongitudeLines(ctx, iDEGREES_BETWEEN_LON_GRID_LINES);
}
The function above sets the line style and font and calls the following:
function drawLongitudeLines(ctx, iDEGREES_BETWEEN_GRID_LINES)
{

	var iNORTH_LATITUDE = 90;
	var iSOUTH_LATITUDE = -90;

	var iDegreesScreenY = 0;

	// Iterate around the latitude axis at the given interval
	for( iLineOfLatitude = iNORTH_LATITUDE; iLineOfLatitude >= iSOUTH_LATITUDE; iLineOfLatitude -= iDEGREES_BETWEEN_GRID_LINES)
	{
		// Convert the latitude value and move the pen to the start of the line
		iDegreesScreenY = degreesOfLatitudeToScreenY(iLineOfLatitude);
		ctx.moveTo(iMAP_START_X_POS, iDegreesScreenY);

		// Plot the line
		ctx.lineTo(iMAP_START_X_POS + iMAP_WIDTH, iDegreesScreenY);

		// Put the label on the line
		ctx.fillText(iLineOfLatitude, iCANVAS_START_X_POS + 5, iDegreesScreenY - 5);

		ctx.stroke();

	}
}

function drawLatitudeLines(ctx, iDEGREES_BETWEEN_GRID_LINES)
{
	var iMIN_LONGITUDE = -180;
	var iMAX_LONGITUDE = 180;

	var iDegreesScreenY = 0;

	// Iterate around the longitude axis at the given intercal
	for( iLineOfLongitude = iMIN_LONGITUDE; iLineOfLongitude 	{
		// Convert the longitude value and move the pen to the start of the line
		iDegreesScreenX = degreesOfLongitudeToScreenX(iLineOfLongitude);
		ctx.moveTo(iDegreesScreenX, iMAP_START_Y_POS);

		// Plot the line
		ctx.lineTo(iDegreesScreenX, iMAP_START_Y_POS + iMAP_HEIGHT);

		// Put the label on the line
		ctx.fillText(iLineOfLongitude, iDegreesScreenX - 10, iCANVAS_START_Y_POS + 10);

		ctx.stroke();
	}
}
The two functions are self explanatory: one draws each line along the lines of latitude and the other performs the same for the line of longitude. They achieve this by iterating through the the bounds of the latitude or longitude drawing a line every 10 degrees. This has the following result:

Map with graticule

The last step is to draw the land. This is relatively simple. Each continent is made up of a set of shapes. Each shapes represents an island. Therefore, if you have a list of geographical points that make up the outline of each area of land, we can use the beginPath feature of the canvas implementation to plot it. The data for the points is available from various sources. I obtained a copy of the data from the GNUPlot project here. Unfortunately, the data they provide is formatted using the a CSV format (I think) witch blank lines denoting the end of a shape. In order for this to be used in JavaScript I have converted the data to JavaScript Object Notation (JSON). A extract of the converted data structure follows:

function getMapData()
{
	// The map data!!

	var json =
	{
		"shapes" : [
[
		{
			"lat" : "48.24",
			"lon" : "-92.32"
		},
		{
			"lat" : "48.92",
			"lon" : "-88.13"
		},
		{
			"lat" : "46.27",
			"lon" : "-83.11"
		},
		{
			"lat" : "44.76",
			"lon" : "-81.66"
		}
],
[
		{
			"lat" : "51.76",
			"lon" : "-177.55"
		},
		{
			"lat" : "51.63",
			"lon" : "-177.41"
		}
]
...
The full version of data is not included above. But, I have included this full dataset in the project zip file discussed below. The function which uses this data to draw the land is called ‘drawLandMass’. It contains the following:
function drawLandMass(ctx)
{
	var landMass = getMapData();

	var iFirstScreenX = 0;
	var iFirstScreenY = 0;
	bFirst = false;

	// A lighter shade of green
	ctx.fillStyle = 'rgb(0,204,0)';

	// Iterate around the shapes and draw
	for( iShapeCounter = 0; iShapeCounter < landMass.shapes.length; iShapeCounter++)
	{
		var shape = landMass.shapes[iShapeCounter];

		ctx.beginPath();

		// Draw each point with the shape
		for( iPointCouner = 0; iPointCouner < shape.length; iPointCouner++)
		{
			var iLon = shape[iPointCouner]['lat'];
			var iLat = shape[iPointCouner]['lon'];

			// Before plotting convert the lat/Lon to screen coordinates
			ctx.lineTo(degreesOfLongitudeToScreenX(iLat),
				degreesOfLatitudeToScreenY(iLon));
		}

		// Fill the path green
		ctx.fill();
		ctx.stroke();

	}
}
The ‘drawLandMass’ function first configures the fill colour of the canvas context to green. It then starts an outer loop which iterates around the shapes within the aforementioned JSON data. Each shape included has at least one data point; thus, the inner loops iterates around the points that define the boundary of the current shape. These data point relate to a specific latitude and longitude point. Using the path function the inner and outer loop combine together to draw and fill each shape. As a result of which the land mass is drawn onto the map.

Drawing a map with a canvas

That concludes the steps involved to draw the world. I decided to add one more feature. HTML5 implements a geographic location feature. This feature allows us to request the latitude and longitude associated with the IP address of the connected client. If the latitude and longitude can be ascertained these can then be used to draw a point on the map. The code to request the geographical location is:
		// One-shot position request. (f supported)
		if (navigator.geolocation)
			navigator.geolocation.getCurrentPosition(plotPosition);
The JavaScript above first checks whether the browser supports the geographical location feature. If supported the browser requests the current location. The parameter passed into the function is called a callback function. The callback function will be executed when the result of the location request has been fulfilled by the browser. The callback function contains the following:
function plotPosition(position)
{
	// Grab a handle to the canvas
	var canvas = document.getElementById('map');

	// Canvas supported?
	if(canvas.getContext)
	{
		// Grab the context
		var ctx = canvas.getContext('2d');

		ctx.beginPath();

		// Draw a arc that represent the geo-location of the request
		ctx.arc(
			degreesOfLongitudeToScreenX(position.coords.longitude),
			degreesOfLatitudeToScreenY(position.coords.latitude),
			5, 0, 2 * Math.PI, false);

		// Point style
		ctx.fillStyle = 'rgb(255,255,0)';
		ctx.fill();
		ctx.lineWidth = 3;
		ctx.strokeStyle = "black";

		ctx.stroke();
	}
}
The code ascertains a handle to the canvas element. Using the canvas it then draws an arc. The centre of the arc is positioned at the geographical position obtained by the postition request. The result of which is:

Geographic location with HTML5

That concludes this post. A working sample can be viewed here here. The source code for this project can be downloaded here

6 thoughts on “HTML5’s Canvas! Let’s draw the world!

  1. Hello just wanted to give you a quick heads up. The words in your post seem to be running off the screen in Chrome.
    I’m not sure if this is a formatting issue or something to do with internet browser compatibility but I figured I’d post
    to let you know. The design look great though! Hope you
    get the issue solved soon. Kudos

    1. That’s interesting as I used Chrome and it looks fine on my PC. I use the free wordpress.org Blog account and the theme is one provided (not something I’ve created). It should work. Can you let me know what device you are using? e.g. Android device, PC or Mac?

Leave a comment