Drawing 3D Objects and Buildings on Google Maps

February 11, 2013 | HTML/CSS, JavaScript | 10 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.

10 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

  4. forex says:

    We’re a group of volunteers and starting a new scheme in our community.

    Your website provided us with valuable information to work
    on. You’ve performed a formidable task and our whole community will probably be
    grateful to you.

  5. All natural, completely safe, along with appetite suppressing are just a couple of
    perks you might find after you work with the
    HCG for fat loss. Just because HCG drops cost more does not mean that they are better
    than the more reasonably priced product that can be as much as half the cost.
    Real HCG diet plans can add elasticity to your skin.

    Feel free to visit my blog post; weight loss liquid hcg

  6. 5 to frontt layout fսll and stuck double pike іn crosstumbling.
    ” People the world over go onto a new diet and find out after a few days nothing is happening, they check and find they are the same weight as when they started. With nothing else around but the weights and machines, there is nothing there to distract you, and it becomes much easier to focus on the training session.

  7. Fantastic beat ! I wish to apprentice at the same time as you amend your
    web site, how could i subscribe for a blog website?

    The account helped me a appropriate deal. I were a little bit familiar of this your broadcast provided shiny clear concept

  8. Un discours de mariage rédigé en bon français,
    foncez!

  9. Why viewers still use to read news papers when in this technological world everything is
    existing on net?

  10. Legal o seu site, continue assim! Tem iPhone?
    A tela quebrou? Se você for de Salvador, procure a iPhoneSSA, troca de tela de iPhone
    em Salvador com o melhor preço.

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.