Drawing 3D Objects and Buildings on Google Maps

February 11, 2013 | HTML/CSS, JavaScript | 3 Comments

As a web developer for a school, I’ve always enjoyed the challenge of creating a good campus map. The school I work for has a growing online seminary program to train pastors around the world, but helping people get around the main campus in Dallas is a still an integral part of any school.

TL;DR: Final result: DTS “3D” campus map

campus-map-sidebyside

Back Story: The Old Map

Several years ago, I wanted to make an interactive 3D building map, so I learned Papervision 3D a powerful Flash-based 3D engine (papervision map). I really liked it at the time but now that Flash is largely out of the picture, it was time to replace the map. I’ve wanted to port the old map to Three.js a JavaScript based 3D engine, but I found that conversion wasn’t as easy as I thought (three.js experiment).

Flash based map

To Google Maps

Since I also want to give first-class support to mobile devices, I decided to switch gears to Google Maps API since it runs really well on phones and tablets now. The problem was finding a way to display the map in an interesting and clear way.

Attempt #1: Flat polygons

My team and I drew out the floor line  of several buildings using Google’s Polygon tool with editable:true turned on, then we created a little loop to draw them all at once. It looks great, but the problem is that it’s not really clear that we’re showing buildings and other than color, it’s hard to tell what’s a parking lot.

var 
	mapCenter = [32.794488, -96.780372],
	mapOptions = {
		zoom: dts.maps.current.campus.zoom,
		center: new google.maps.LatLng(mapCenter[0], mapCenter[1]),
		mapTypeId: google.maps.MapTypeId.SATELLITE			

	},
	map = new google.maps.Map(document.getElementById("map"), mapOptions),
	buildingCoordinates = [
		[32.7948702128665,-96.78071821155265],
		[32.79478789915277,-96.780713852963],
		[32.794745606448785,-96.7807638091059],
		[32.79465594788115,-96.78065450908855],
		[32.794983444026556,-96.78026383449742],
		[32.79507153522898,-96.78037564908573],
		[32.79502490803293,-96.78043113728472],
		[32.79502635107838,-96.78054211368271]	
	],
	polygonCoords = [];

for (var j=0; j<buildingCoordinates.length; j++) {
	polygonCoords.push(new google.maps.LatLng(
		buildingCoordinates[j][0], 
		buildingCoordinates[j][1]
	));	
}       

var polygon = new google.maps.Polygon({
  paths: polygonCoords,
  strokeColor: '#ee1111',
  strokeOpacity: 0.6,
  strokeWeight: 1,
  fillColor: '#eeeeee',
  fillOpacity: 0.7
});		

polygon.setMap(map);

campus-map-flat

Attempt #2: Roof

The next step was trying draw a floating roof simply by copying the the coordinates and adding a little bit to the latitude to make it seem like it was “up in the air.” The result doesn’t really make sense, but it starts to give some building like feeling:

campus-map-roofs

Attempt #3: Drawing a single “Wall”

My next thought was to remove the floor and then draw to draw a single wall on the South side to make it look like a wrap around wall. To do this, I looked through the coordinates and found the western and eastern edge and then tried to draw along it. It worked in many places, but in complex buildings it looked a little strange since there is only one “south” wall.

campus-map-single-wall

Attempt #4: Drawing individual Walls

To fix the problem of complex buildings, I decided to draw a polygon for each wall. This involves taking each floor coordinate and making a pair with the next one and then stretching it upward. Here’s what my new function looks like

function drawExcrudedShape(map, coordinates, height, strokeColor, strokeOpacity, strokeWeight, fillColor, fillOpacity) {

	var pairs = [],
		polygons = [];

	// build line pairs for each wall
	for (var i=0; i<coordinates.length; i++) {

		var point = coordinates[i],
			otherIndex = (i == coordinates.length-1) ? 0 : i+1,
			otherPoint = coordinates[otherIndex];

		pairs.push([point, otherPoint]);
	}

	// draw excrusions
	for (var i=0; i<pairs.length; i++) {

		var first = pairs[i][0],
			second = pairs[i][1],
			wallCoordinates =  [
				new google.maps.LatLng(first[0],first[1]),
				new google.maps.LatLng(first[0]+height,first[1]),
				new google.maps.LatLng(second[0]+height,second[1]),
				new google.maps.LatLng(second[0],second[1])									
			],
			polygon = new google.maps.Polygon({
				paths: wallCoordinates,
				strokeColor: strokeColor,
				strokeOpacity: strokeOpacity, 
				strokeWeight: strokeWeight,
				fillColor: fillColor,
				fillOpacity: fillOpacity
				zIndex: zIndexBase+i
			});

		polygon.setMap(map);

		polygons.push(polygon);
	}		

	return polygons;
}

Here is the result:

campus-map-multi-wall

This looks much better, but now we have two problems. First, some walls incorrectly overlap since I haven’t explicitly told Google the correct order to draw them in z-index problem. Second, if you were to rotate the map 180 degrees (see below), the buildings would be upside-down. This is because I’m not checking which wall is the southern most or the direction of the map.

campus-map-upsidedown

Attempt #5: Re-Ordering the Walls

So in my final attempt, I’ve taken the pairs above and ordered them based on the Google’s heading (map.getHeading()). This allows me to figure out which way is “up” and correctly layer the walls so that they look like real 3D objects. Here’s the final function and map result:

function drawExcrudedShape(map, coordinates, height, zIndexBase, heading, strokeColor, strokeOpacity, strokeWeight, fillColor, fillOpacity) {

	
	var pairs = [],
		polygons = [];
		
	// build line pairs
	for (var i=0; i<coordinates.length; i++) {
	
		var point = coordinates[i],
			otherIndex = (i == coordinates.length-1) ? 0 : i+1,
			otherPoint = coordinates[otherIndex];
	
		pairs.push([point, otherPoint]);
	}
	
	// sort the pairs based on which one has the "lowest" point based on the heading
	pairs.sort(function(a, b) {
		var aLowest = 0,
			bLowest = 0;
			
		switch (heading) {
			case 0:
				aLowest = Math.min(a[0][0], a[1][0]);
				bLowest = Math.min(b[0][0], b[1][0]);	
				
				
				if (aLowest < bLowest) {
					return 1;
				} else if (aLowest > bLowest) {
					return -1;
				} else {
					return 0;
				}						
			case 90:
				aLowest = Math.min(a[0][1], a[1][1]);
				bLowest = Math.min(b[0][1], b[1][1]);	
				
				if (aLowest < bLowest) {
					return 1;
				} else if (aLowest > bLowest) {
					return -1;
				} else {
					return 0;
				}						
	
			case 180:
				aLowest = Math.max(a[0][0], a[1][0]);
				bLowest = Math.max(b[0][0], b[1][0]);	
				
				
				if (aLowest > bLowest) {
					return 1;
				} else if (aLowest < bLowest) {
					return -1;
				} else {
					return 0;
				}	
			
			case 270:
				aLowest = Math.max(a[0][1], a[1][1]);
				bLowest = Math.max(b[0][1], b[1][1]);	
				
				if (aLowest > bLowest) {
					return 1;
				} else if (aLowest < bLowest) {
					return -1;
				} else {
					return 0;
				}	
		
		} 
	});
			
	// draw excrusions
	for (var i=0; i<pairs.length; i++) {
		
		var first = pairs[i][0],
			second = pairs[i][1],
			wallCoordinates = null;
			
		switch (heading) {
			case 0:
				wallCoordinates = [
					new google.maps.LatLng(first[0],first[1]),
					new google.maps.LatLng(first[0]+height,first[1]),
					new google.maps.LatLng(second[0]+height,second[1]),
					new google.maps.LatLng(second[0],second[1])									
				];
				break;
				
			case 90:
				wallCoordinates = [
					new google.maps.LatLng(first[0],first[1]),
					new google.maps.LatLng(first[0],first[1]+height),
					new google.maps.LatLng(second[0],second[1]+height),
					new google.maps.LatLng(second[0],second[1])									
				];
				break;
				
			case 180:
				wallCoordinates = [
					new google.maps.LatLng(first[0],first[1]),
					new google.maps.LatLng(first[0]-height,first[1]),
					new google.maps.LatLng(second[0]-height,second[1]),
					new google.maps.LatLng(second[0],second[1])									
				];
				break;
				
			case 270:
				wallCoordinates = [
					new google.maps.LatLng(first[0],first[1]),
					new google.maps.LatLng(first[0],first[1]-height),
					new google.maps.LatLng(second[0],second[1]-height),
					new google.maps.LatLng(second[0],second[1])									
				];
				break;
		}				
			
		var polygon = new google.maps.Polygon({
			paths: wallCoordinates,
			strokeColor: strokeColor,
			strokeOpacity: strokeOpacity, 
			strokeWeight: strokeWeight,
			fillColor: fillColor,
			fillOpacity: fillOpacity, 
			zIndex: zIndexBase+i
		});
		
		polygon.setMap(map);
		
		polygons.push(polygon);
	}		
	
	return polygons;
}

Final Map

Here is the final result. We’ve changed the parking lots to just have a colored border to help people know where to park and the full map has some interactivity on the buildings, lots, and departments. Go give it a try!

campus-map-final

Thanks to Google Maps Mania for the kind words here and here.

3 Responses to “Drawing 3D Objects and Buildings on Google Maps”

  1. Dirk says:

    Wow. That is impressive my friend. Good work

  2. Mano Marks says:

    This is really cool. What’s the license on your code?

  3. These Maude Mustard Flats embody everything I love: bow-tie detailing, cute patterns,
    vegan leather, and not to mention, it’s honey yellow.
    While Lv harness or even bandage is definitely aswell the best permanently allocation in order to your ex.
    Daphne’s Hollywood (Herve Leger) because of its close to the body line “bandage” Herve Leger
    Bandage design and famous.

    Feel free to surf to my blog: boutique dresses wholesale

Leave a Reply

Hi, I'm John Dyer. In my day job, I build websites and create online seminary software for a seminary in Dallas. I also like to release open source tools including a pretty popular HTML5 video player and build tools that help people find best bible commentaries and do bible study. And just for fun, I also wrote a book on the theology of technology and media.

Fork me on GitHub

Social Widgets powered by AB-WebLog.com.