When I saw Dan Macy’s aerial photo of Washington, DC, I knew I had to turn it into the background for a map. Dan’s plane was flying into National Airport from New York City, and made an unusual entry over the Anacostia River, giving him a spectacular view of East Capitol Street, the Anacostia and Potomac Rivers, and the National Mall.

I’d been looking at the 2013 data from Capital Bikeshare, and made a little app that shows you the total number of bikeouts from each station, the Birds-eye view of CaBi stations. (A bikeout means someone rented a bike from that station.) The program was mostly an excuse to play with JavaScript and SVG graphics, without using a mapping or graphics library.

I had to figure out a not-too-complex way of mapping the latitude and longitude coordinates to the oblique photo. Unlike a map, the meridians (longitudes) aren’t vertical and the parallels (latitudes) aren’t horizontal. The bird’s-eye perspective also meant the meridians and parallels would be skewed.

I decided to use an approximate interpolation. I drew a column of quadrilaterals over the photo, using meridians and parallels with visible landmarks that I could compare to an actual map. I got the coordinates of the points using my own Spot Picker tool (see How to Get the Coordinates of Any Point). On a regular map the quadrilaterals would form a series of rectangles with equal heights, but on the photo they looked like this:

I didn’t label the sides, but the left border (south) is drawn over G St SE/SW, at 38.88127″, and the right border (north) is drawn over East Capitol St, at 38.88982″. I picked these parallels because I could identify the streets in the photo. The longitudinal scale rapidly changes as the distance increases. Since I am using a mathematically-simple linear interpolation for my calculations, I have to use a different scale depending on how far away the points are. This method also ignores the curvature of the Earth.

When I calculate the x-y coordinates from a lat-lng pair, I first determine which rectangle to use for my interpolation. Since I have a single column (for simplicity), I just use the point’s longitude, seeing which rectangle it lines up with. The radius of the circle depends on which rectangle it’s using, which increases the sense of perspective.

To interpolate a point in a quadrilateral, I start by interpolating two intermediate x-values: one on the quadrilateral’s bottom line, as if the longitude were the max, and another on the quadrilateral’s top line, as if the longitude were the min, using the proportional latitudinal position. Then I just interpolate those two values, based on the relative longitudinal position.

The method is not wholly accurate, but with small-enough sample areas, it’s good enough. Most points are actually outside the quadrilaterals; they are effectively extrapolated, and their accuracy is slightly worse.

I draw over the photo using the SVG element. The circles are grouped with their labels using the <g> element, which is generated dynamically by JavaScript. By having the labels as a sub element to a group, I can use CSS to animate the appearance of the labels as you hover over the circle.

g text {  
  font-size: 0px;
  opacity: 0;  
  transition: 0.2s;
  }  
g:hover text {    
  font-size: 20px;
  opacity: 1; 
  }

Note I am using the element names themselves to control the CSS. I don’t need class names or IDs because there are no other group elements being used. The hover pseudo-class is positioned after the g element, meaning that the styles are applied to any text elements that are children of a group element being hovered over.

There is also a slight animation as the circles fade from red to blue when you move the slider. This effect is triggered by JavaScript rather than the hover effect, but I still use CSS to generate the animation.

circle {   
  transition: 0.8s;
  }

With that in place, any change to a circle will be animated. Whenever the slider is moved, I run the following JavaScript:

for (var i = 0; i < stations.length; i++)  
  stations[i].circle.setAttribute("fill", 
    (stations[i].totalBikeouts >= x) ? "red" : "rgba(0,0,255,0.5)"); 

I don’t have to fiddle with the opacity setting since I am using the rgba notation to include the “alpha channel,” which is basically a number from 0 (transparent) to 1 (opaque). The CSS color name “red” implies 100% opacity.

The Birds-eye view of CaBi stations program is a first-draft effort to visualize geographic data using aerial photography instead of a traditional map. I think this method has a strong visual impact, and good potential for further exploration.

A Bird’s-Eye View of CaBi Stations

One Response to “A Bird’s-Eye View of CaBi Stations”