Thanks to Pedro’s post, I was finally able to create a zoomable image viewer using GDAL and Leaflet.

First, you must make sure that your image adhere’s to the tile size chart:

Zoom Level    Pixel size
0             256
1             512
2             1024
3             2048
4             4096
5             8192
6             16384
7             32768
8             65536

Then make sure that the larger side of your image (width or height) is sized to the exact pixel size.  To do so, run the following command:

gdal_translate -of JPEG -outsize 32768 3407  coast3.jpg coast3-resized.jpg

In this example, zoom level 7 was chosen, so the pixel size is 32768.  You will need to calculate what the pixel height is based on the ratio of the image dimensions.  Since the original image is 40400 x 4200, the adjusted dimensions are 32768 x 3407.

When you run the above command, you should get a response like so:

Input file size is 40400, 4200
0...10...20...30...40...50...60...70...80...90...100 - done.

Now it is time to tile the image and create the image viewer.  We will use the command gdal2tile.py to accomplish this.

gdal2tiles.py -p raster -z 0-7 -w all coast3-resized.jpg coast3

This creates a “coast3” folder with all the tiles inside.  While gdal creates some default web viewers, I have opted to create my own using leaflet.  Here is the code for the viewer:

<html>
<head>
	<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css" /> 
	<style>
		html, body, #map { width:100%; height:100%; margin:0; padding:0; }
	</style>
</head>
<body>
	<div id="map"></div>

	<script src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>

	<script>
		var mapMinZoom = 0;
		var mapMaxZoom = 7;

		var map = L.map('map', {
			maxZoom: mapMaxZoom,
			minZoom: mapMinZoom,
			crs: L.CRS.Simple
		}).setView([-180, 90], 0);

		var mapBounds = new L.LatLngBounds(
			map.unproject([0, 32768],mapMaxZoom),
			map.unproject([32768, 30015],mapMaxZoom));

		map.fitBounds(mapBounds);

		L.tileLayer('{z}/{x}/{y}.png', {
			minZoom: 0,
			maxZoom: 7,
			attribution: 'UCLA/NiigataUniversity',
			tms: true,
			noWrap: true   
		}).addTo(map);
	</script>
</body>
</html>

There are some numbers that need to be adjusted.  The mapMinZoom and mapMaxZoom need to be adjusted depending on what parameters were chosen to tile the image.  Furthermore, the mapBounds numbers need to be calculated.  To do so, open the folder with the tiles (coast3 in this example), and examine the tilemapresource.xml file that was automatically generated by gdal.  It should look like this:

<?xml version="1.0" encoding="utf-8"?>
    <TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">
      <Title>coast3-resized.jpg</Title>
      <Abstract></Abstract>
      <SRS></SRS>
      <BoundingBox minx="0.00000000000000" miny="-3407.00000000000000" maxx="32768.00000000000000" maxy="0.00000000000000"/>
      <Origin x="0.00000000000000" y="-3407.00000000000000"/>
      <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>
      <TileSets profile="raster">
        <TileSet href="0" units-per-pixel="128.00000000000000" order="0"/>
        <TileSet href="1" units-per-pixel="64.00000000000000" order="1"/>
        <TileSet href="2" units-per-pixel="32.00000000000000" order="2"/>
        <TileSet href="3" units-per-pixel="16.00000000000000" order="3"/>
        <TileSet href="4" units-per-pixel="8.00000000000000" order="4"/>
        <TileSet href="5" units-per-pixel="4.00000000000000" order="5"/>
        <TileSet href="6" units-per-pixel="2.00000000000000" order="6"/>
        <TileSet href="7" units-per-pixel="1.00000000000000" order="7"/>
      </TileSets>
    </TileMap>

Observe the numbers in the BoundingBox parameter.  These are needed in order to calculate the mapBounds parameters in leaflet.  Here is the rough formula:

map.unproject([minx,maxx],mapMaxZoom),
map.unproject([maxx,maxx+miny],mapMaxZoom)

And here is the results: