Campus Shuttle

David J. Malan
Harvard University
http://cs.harvard.edu/malan
malan@harvard.edu

For more problem sets like this one, visit cs50.tv.

Summary Campus Shuttle challenges students to implement a web-based driving game that mashes together the Google Earth and Google Maps APIs. Once implemented, students can drive around their school's campus virtually. The game's objective is to pick up classmates who appear as 2D avatars within Google Earth and drop them off at their respective dormitories.
Topics HTML, CSS, JavaScript. Loops, conditions, variables, functions. Arrays, objects. GIS.
Audience CS1 or higher.
Difficulty Relatively easy, though difficulty could be increased by providing less distribution code.
Strengths Challenges range from easy to hard. Reveals applicability of arrays and objects to actual problems. Introduces students to third-party APIs. Incredibly fun to drive around one's own campus virtually. Assignment can be customized for any school (that's been photographed and mapped by Google).
Weaknesses Requires Internet access (for Google Earth and Google Maps APIs). Requires installation of (free) Google Earth browser plugin.
Dependencies Familiarity with arrays and objects. Exposure to HTML and CSS. Exposure to (but not necessarily experience with) JavaScript.

Talk

Demos

Screenshot

screenshot

Instructions for Teachers

Instructions for Students

adapted for SIGCSE from pset8.pdf

Your mission for this problem set is to implement your own shuttle service that picks up passengers all over campus and drops them off at their houses. Your shuttle is already equipped with an engine, a steering wheel, and 35 seats. Shall we go for a spin? Here's how to drive with your keyboard:

Move Forward: W
Move Backward: S
Turn Left: left arrow
Turn Right: right arrow
Slide Left: A
Slide Right: D
Look Downward: down arrow
Look Upward: up arrow

Note that your mouse is not used for driving in this 3D world. In fact, don't even click on it, else you'll take away "focus" from our (and, soon, your) JavaScript code, the result of which is that the shuttle will no longer respond to your keystrokes. If that does happen, simply click the 2D map or anything above it (within the same window) to give focus back to the code; the shuttle should then respond to your keystrokes again.

Anyhow, go for a ride! As you approach buildings, you may find that they get prettier and prettier as more satellite imagery is automatically downloaded. See if you can make your way to your own house. (Try not to take any shortcuts through buildings.) Along the way, you'll likely see some familiar faces. Those will soon be your passengers!

In fact, go ahead and click a few times the minus (-) sign in the top-left corner of the application's 2D map. You should see more and more red markers as you zoom out, each of which represents a passenger waiting for pickup. If you hover over each marker with your mouse, you'll see each passenger's name and origin. The blue bus, of course, represents you! You're welcome to click and drag the 2D map to see more of campus; as soon as you start driving again, the map will be re-centered around you.

Above the 2D map is a list of your 35 seats, each of which is currently empty. Above that, in the application's top-right corner, are two buttons: Pick Up and Drop Off. Neither works yet, but both will before long!

If you happen to get lost, just reload the page, and you'll be returned to a default location. Your soon-to-be passengers will also be pseudorandomly repositioned throughout campus.


Alright, let's take a stroll through this assignment's distribution code. Open up index.html first and read over the lines of HTML within. Notice first how this file references several others in its head, namely services.css, math3d.js, buildings.js, passengers.js, shuttle.js, and service.js, as well as the URL of Google's JavaScript API. Notice next how the body tag has a few event handlers, each of whose implementations lives, as we'll soon see, in service.js. And notice how each of the div elements is uniquely identified with an id attribute so that we can stylize it with CSS or access it via JavaScript.

At the moment, the HTML within two of those div elements (#announcements and #seats) is meant to be temporary. Eventually, there'll be some announcements, and there will be passengers in seats!

Next open up service.css, which stylizes that HTML. You don't need to understand all of the CSS within, but do know in general that div#foo, refers to the div element whose id is foo.

Now take a peek at buildings.js. Declared in that file is BUILDINGS, which is an array of objects, each of which represents a building on campus. Associated with each building is a name and address, and a pair of coordinates that represents an edge of that building. We could have declared this array in service.js, but because it's so big, we decided to isolate it in its own file.

Similarly do passengers have their own file. In passengers.js is PASSENGERS, another array of objects, each of which this time represents a passenger on campus. Associated with each passenger is a username (i.e., a unique identifier), a name, and a house. Incidentally, each of those usernames maps to a similarly named JPEG in passengers. Note, though, that some houses comprise multiple buildings, and so even though Mather House appears in both passengers.js and buildings.js, Quincy House appears only in passengers.js. In buildings.js, meanwhile, are objects for Quincy House New Residence Hall, Quincy House Library, and (nice and confusingly) Mather Hall, Quincy House. And so you will find in houses.js a third (and final!) data structure, this one an object whose keys are houses' names and whose values are objects representing houses' coordinates. And so you can access the best house's coordinates with something like:

var lat = HOUSES["Mather House"].lat;
var lng = HOUSES["Mather House"].lng;

You may assume that the coordinates in houses.js are the official coordinates for passengers' destinations. In fact, we've already planted yellow markers at those very coordinates on the 2D map to help out the driver. We've not bothered planting placemarks at those coordinates on the 3D Earth, since they're not as easy to spot.

Now take a peek at shuttle.js. This file's a bit fancy, inasmuch as it effectively implements a data structure called Shuttle. You don't need to understand this file's entirety, but know that inside of a Shuttle are two properties: position, which encapsulates a shuttle's position (including its longitude and latitude), and states, which encapsulates a shuttle's various states.

Okay, now turn your attention to service.js, where you'll do most of your work, though you're welcome to modify any of this problem set's files, except for math3d.js (which is just a library), buildings.js, houses.js, and passengers.js. Atop service.js are a bunch of constants and globals. Just below those lines are two calls to google.load, which loads the two APIs on which this application relies:

You won't need to read through the entirety of that documentation; odds are you'll explore it as needed. But do read the first page or so of each to get a sense of what each API does.

Implemented in service.js are all of those event handlers you first saw in index.html. Scroll down to the implementation of load first. It's this function that embeds the 2D map and 3D Earth map in your browser. Next scroll back up to the implementation of initCB; this function gets called as soon as the Google Earth Plugin has loaded, and so it's in this function that we finish initializing the app. (If something goes wrong, it's failureCB that's instead called.) Notice, in particular, that initCB "instantiates" an object, shuttle, of type Shuttle.

Next take a look at the function called keystroke. It's this function that handles your keystrokes. In response to your keystrokes, this function changes the state of the shuttle simply by updating the appropriate property in shuttle.states with a value of true or false.

Now take a peek at the function called populate. It's this function that plants friendly faces all over campus. Each face is implemented as a "placemark" on the 3D earth and as a "marker" on the 2D map.

Finally, take a look at the function called chart. That function renders a seating chart (in the div whose id is chart) as an ordered HTML list (0-indexed for your debugging convenience) for the shuttle's 35 seats. Notice how it iterates over shuttle.seats, an array that's initialized to be of size SEATS (i.e., 35) in shuttle.js. It looks like null represents an empty seat!


Alright, it's time start picking passengers up. Recall from index.html that, when the button labeled Pick Up is clicked, the function called pickup in service.js is called. Implement pick-ups as follows.

If the button is clicked when the shuttle is within 15.0 meters of a passenger and at least one seat is empty, that passenger should be given a seat on the shuttle and removed from both the 2D map and 3D earth. (If multiple passengers are within 15.0 meters, you should pick up as many of them as there are empty seats.) If the shuttle is not within 15.0 meters of any passenger, make an announcement to that effect. If the shuttle is within range of some passenger but no seats are free, make an announcement to that effect (and don't pick up the passenger). Any such announcements should be cleared (or replaced with some default text) as soon as the shuttle starts moving.

Not sure how to proceed? That's part of the challenge! Odds are you'll encounter similar uncertainty when you dive into your final project. Whenever in doubt, peruse the APIs' documentation, and turn to Google (the search engine) for tips. But we'll give you some hints to get you started on pick-ups.

To calculate the shuttle's distance, d, from some point (lat, lng), you'll probably want something like:

var d = shuttle.distance(lat, lng);

To remove a placemark, p, from the 3D Earth, you'll probably want something like:

var features = earth.getFeatures();
features.removeChild(p);

To remove a marker, m, from the 2D map, you'll probably want something like:

m.setMap(null);

Unfortunately, populate, at the moment, doesn't remember the placemarks or markers that it plants, so you may need to tweak populate as well, per its TODO. Perhaps you could store those placemarks and markers in some global array(s) and/or object(s) so that you can remove them from the 3D Earth and 2D map, respectively, later?

And how to give a picked-up passenger a seat? Odds are you'll want to update shuttle.seats, but be sure not to add extra seats. And odds are you'll want to update div#seats anytime that array is updated by calling chart. But, at the moment, chart only knows how to display empty seats; be sure to replace its TODO with some code that displays picked-up passengers names and houses, so that you know who and where to drop off.

As for making announcements, recall that code like the below will get the job done:

document.getElementById("announcements").innerHTML = "hello, world";

Alright, it's now time to implement drop-offs! Recall from buildings.js that each passenger lives in a house. And recall from index.html that, when the button labeled Drop Off is clicked, the function called dropoff in service.js is called. Implement drop-offs as follows.

If the button is clicked when the shuttle is within 30.0 meters of an on-board passenger's house, that passenger should be dropped of by emptying their seat. (If there are multiple passengers on board that live in that house, all should be dropped off in this manner.) If the shuttle is not within 30.0 meters of any passenger's house, make an announcement to that effect. Any such announcement should be cleared (or replaced with some default text) as soon as the shuttle starts moving.

No need to re-plant a placemark or marker for dropped-off passengers; assume they're going to head straight inside. As for passengers' who're still on board after a drop-off, be sure not to make them change seats just because some other seat is now empty.


Nice work so far! Suffice it to say this shuttle service is starting to feel like a game. It's time to allow you some creativity. Implement any one (1) of the features below.

Although you are only required to implement one of the features above, you are welcome to impress us (and your friends) with more just for fun! And you are welcome to alter your user interface's aesthetics, including your 2D map's icons.


This is CS50.

Copyright © 2013, David J. Malan, Harvard University

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.

Creative Commons License


Extra info about this assignment: