Now, we are ready to use this information to create our own map. The best source of freely available street mapping data is the OpenStreetMap (OSM) project: http://www.openstreetmap.org.
OSM also has a publicly available REST API called StaticMapLite to create static map images: http://staticmap.openstreetmap.de.
The OSM StaticMapLite API provides you with a GET API based on Google's static map API to create simple map images with a limited number of point markers and lines. A GET API, as opposed to REST, allows you to append name/value parameter pairs after a question mark on the URL. A REST API makes the parameters part of the URL path. We'll use the API to create our own NextBus API map on demand with a red pushpin icon for the bus location.
In the next example, we have condensed the previous script down to a compact function named nextbus()
. The nextbus()
function accepts an agency, route, command, and epoch as arguments. The command defaults to vehicleLocations
, and the epoch defaults to 0
to get the last 15 minutes of data. In this script, we'll pass in the LA route 2 route information and use the default command that returns the most recent latitude/longitude of the bus.
We have a second function named nextmap()
that creates a map with a blue dot on the current location of the bus each time it is called. The map is created by building a GET URL for the OSM StaticMapLite API, which centers on the location of the bus and uses a zoom level between 1-18 and map size to determine the map extent. You can access the API directly in a browser to see an example of what the nextmap()
function does: http://staticmap.openstreetmap.de/staticmap.php?center=40.714728,-73.998672&zoom=14&size=865x512&maptype=mapnik&markers=40.702147,-74.015794,lightblue1|40.711614,-74.012318,lightblue2|40.718217,-73.998284,lightblue3
The nextmap()
function accepts a NextBus agency ID, route ID, and string for the base image name of the map. The function calls the nextbus()
function to get the latitude/longitude pair. The execution of this program loops through at timed intervals, creates a map on the first pass, and then overwrites the map on subsequent passes. The program also outputs a timestamp each time a map is saved. The requests
variable specifies the number of passes and the freq
variable represents the time in seconds between each loop:
import urllib.request import urllib.parse import urllib.error from xml.dom import minidom import time def nextbus(a, r, c="vehicleLocations", e=0): """Returns the most recent latitude and longitude of the selected bus line using the NextBus API (nbapi) Arguments: a=agency, r=route, c=command, e=epoch timestamp for start date of track, 0 = the last 15 minutes """ nbapi = "http://webservices.nextbus.com" nbapi += "/service/publicXMLFeed?" nbapi += "command={}&a={}&r={}&t={}".format(c, a, r, e) xml = minidom.parse(urllib.request.urlopen(nbapi)) # If more than one vehicle, just get the first bus = xml.getElementsByTagName("vehicle")[0] if bus: at = bus.attributes return(at["lat"].value, at["lon"].value) else: return (False, False) def nextmap(a, r, mapimg): """Plots a nextbus location on a map image and saves it to disk using the OpenStreetMap Static Map API (osmapi)""" # Fetch the latest bus location lat, lon = nextbus(a, r) if not lat: return False # Base url + service path osmapi = "http://staticmap.openstreetmap.de/staticmap.php?" # Center the map around the bus location osmapi += "center={},{}&".format(lat, lon) # Set the zoom level (between 1-18, higher=lower scale) osmapi += "zoom=14&" # Set the map image size osmapi += "&size=865x512&" # Use the mapnik rendering image osmapi += "maptype=mapnik&" # Use a red, pushpin marker to pin point the bus osmapi += "markers={},{},red-pushpin".format(lat, lon) img = urllib.request.urlopen(osmapi) # Save the map image with open("{}.png".format(mapimg), "wb") as f: f.write(img.read()) return True # Nextbus API agency and bus line variables agency = "lametro" route = "2" # Name of map image to save as PNG nextimg = "nextmap" # Number of updates we want to make requests = 3 # How often we want to update (seconds) freq = 5 # Map the bus location every few seconds for i in range(requests): success = nextmap(agency, route, nextimg) if not success: print("No data available.") continue print("Saved map {} at {}".format(i, time.asctime())) time.sleep(freq)
While the script runs, you'll see an output similar to the following, showing at what time the script saved each map:
Saved map 0 at Sun Nov 1 22:35:17 2015 Saved map 1 at Sun Nov 1 22:35:24 2015 Saved map 2 at Sun Nov 1 22:35:32 2015
This script saves a map image similar to the following screenshot, depending on where the bus was when you ran it:
This map is an excellent example of using an API to create a custom mapping product. However, it is a very basic tracking application. To begin to develop it into a more interesting geospatial product, we need to combine it with some other real-time data source that gives us more situational awareness.