Manhattan

Source code in Manhattan.zip

This app uses the same graphics functions as the triangle in Japan, but the math is much more sophisticated.

  1. Class AppDelegate: unchanged
  2. Class ViewController.swift: unchanged.
  3. Class ManhattanView.swift: added the init that takes an NSCoder, and draw(_:).
  4. Main.storyboard
  5. Info.plist

Latitude and longitude

To get the latitudes and longitudes of the points around the Manhattan shoreline, go to Google Maps. Control-click (or right-click in Microsoft Windows) on a point and select “What’s Here?”. The point’s latitude and longitude will appear.

I wrote the plotter using the JavaScript Google Maps API Version 3. Column 1 is latitude (north of the equator is positive). Column 2 is longitude (west of Greenwich is negative).

40.72921	-73.971548	East River at East 17th Street
40.735519	-73.974595	24
40.742998	-73.971806	34
40.754767	-73.96215	53
40.762146	-73.954296	65
40.771474	-73.946185	81
40.776154	-73.942022	89
40.776154	-73.942022	96
40.787008	-73.93816	103
40.795326	-73.929534	118
40.800946	-73.929062	125
40.808775	-73.934212	Harlem River at 132nd Street
40.817772	-73.933868	143
40.83547	-73.935113	163
40.855857	-73.922195	Dyckman Street
40.869878	-73.91078	218
40.873416	-73.911767	Broadway Bridge
40.877018	-73.922968	Henry Hudson Parkway Bridge
40.877082	-73.926916	Hudson River
40.867379	-73.933096	Riverside Drive
40.852417	-73.943224	Hudson River at West 181st Street
40.850339	-73.946786	George Washingon Bridge
40.850339	-73.946786	168
40.834626	-73.95052	155
40.827417	-73.955026	144 sewage treatment plant
40.828034	-73.956399	144
40.82365	-73.959446	137
40.822676	-73.957601	137
40.771669	-73.994765	57
40.769524	-73.995152	54
40.763316	-73.999872	44
40.762276	-74.001718	42
40.754052	-74.007726	29
40.749825	-74.009442	23
40.748362	-74.00794	21
40.740754	-74.009228	Meatpacking District
40.739258	-74.010344	Gansevoort Street
40.726218	-74.011545	Holland Tunnel
40.718315	-74.013176	Battery Park City
40.718737	-74.016609	Battery Park City
40.706539	-74.019227	South Cove
40.70078	-74.014893	Battery Park
40.701919	-74.009314	Heliport
40.708523	-73.997984	north of Brooklyn Bridge
40.710475	-73.977985	Corlears Hook Park
40.712752	-73.976011	Grand Street
40.720819	-73.972964	East 6th Street

A Swift tuple

A tuple is a variable that contains a series of smaller variables inside it. It’s like a structure data type that we create on the fly, whenever we need it. The word “tuple” is a generalization of “double”, “triple”, “quadruple”, “quintuple”, etc.

var person: (String, String, Int) = ("John", "Doe", 123456789);

print("The first name is \(person.0).");
print("The last name is \(person.1).");
print("The social security number is \(person.2).");
The first name is John.
The last name is Doe.
The social security number is 123456789.

You can create the tuple without bothering to write the type annotation:

var person = ("John", "Doe", 123456789);

print("The first name is \(person.0).");
print("The last name is \(person.1).");
print("The social security number is \(person.2).");

On the other hand, a type annotation will let us give names to the individual values:

var person: (firstName: String, lastName: String, socialSecurity: Int) = ("John", "Doe", 123456789);

print("The first name is \(person.firstName).");
print("The last name is \(person.lastName).");
print("The social security number is \(person.socialSecurity).");

An array of tuples



The draw(_:) method in class ManhattanView contains an array of 48 CGPoint structs, but the array would be easier to type if it contained 48 tuples.

Three transformations

The draw(_:) method of class ManhattanView makes the following three changes to the coördinate system. The translation has to be split into two parts because only the second part should be subjected to the scaling.

  1. translateBy(x:y:). Translate the origin (0, 0) to the center of the ManhattanView.

  2. scaleBy(x:y:). Without this scaling, Manhattan would be too small to see. scale is the number of pairs of pixels per degree of latitude (about 60 miles). It has to be negated because the latitude increases upwards (as we go towards the North Pole) while y increases downwards (as you go towards the bottom edge of the view). scale * cosineOfLatitude is the number of pairs of pixels per degree of longitude. This is smaller than the number of pixels per degree of latitude, because here in New York a degree of longitude is narrower than a degree of latitude. As you get closer to the North Pole, the meridians of longitude get closer together.

  3. Another translateBy(x:y:). Without this translation, we would be looking at Latitude 0° Longitude 0° in the South Atlantic Ocean. This point is in the middle of featureless water.

Type conversions

I declared the variables that hold latitudes and longitudes to be of type CGFloat, because they will be used to draw on the screen. The function GLKMathDegreesToRadians, however, demands a Float argument and returns a Float value. Then the cosine function demands a CGFloat argument and returns a CGFloat value. Fortunately, this CGFloat value is acceptable to the scaleBy(x:y:) method.

Things to try

  1. Add Roosevelt Island, Governors Island, or Randall’s Island.

  2. Insert the following after the closePath().
    	//clockwise perimeter of Central Park
    
    	c.move   (to: CGPoint(x: -73.97305,  y: 40.764291)); //Grand Army Plaza
    	c.addLine(to: CGPoint(x: -73.981762, y: 40.767997)); //Columbus Circle
    	c.addLine(to: CGPoint(x: -73.958116, y: 40.800556)); //Frederick Douglass Circle
    	c.addLine(to: CGPoint(x: -73.949235, y: 40.796848)); //Duke Ellington Circle
    
    	c.closePath();                                       //back to Grand Army Plaza
    
    The four corners of Central Park consitute a subpath. To see why the park is not filled in, read about winding numbers. This will explain why I went counterclockwise around the shoreline and clockwise around Central Park.

  3. Use winding numbers to add a blue lake in Central Park, and a white island in the lake. See the “triple island” in Ken Jenning’s Maphead, p. 2.

  4. Make Manhattan vertical. Insert the following statement immediately before the scaleBy(x:y:). (If it was after, the angle would have to be positive for counterclockwise.)
    	c.rotate(by: CGFloat(GLKMathDegreesToRadians(-28.9)));	//negative for counterclockwise
    
  5. Now that Manhattan is vertical, make it fat. Insert the following statement immediately before the rotate(by:). (What would happen if we inserted it after the rotate(by:)?)
    	c.scaleBy(x: 2, y: 1);	//Make Manhattan fat.
    

  6. If we stroke Manhattan instead of filling it, make the line width very thin. Set it to 1.0 / scale to compensate for the fact that we blew up the picture by a factor of scale.

  7. This app calls init once and draw(_:) once. The init method of a view object will always be called only once, but draw(_:) may be called many times. draw(_:) We should therefore move as much of the work as possible from draw(_:) into init.

    Add the following property to class ManhattanView. We saw it in the fill and stroke triangle example in Japan. There is no need to call CGPathRelease in Swift.

    	//in ManhattanView.swift, immediately after the class View: UIView {
    	var shoreline: CGMutablePath = CGMutablePath();
    

    Initialize shoreline in init, immediately after setting the background color:

    		let a: [(latitude: CGFloat, longitude: CGFloat)] = [	//an array of tuples
    			(40.72921,  -73.971548),    //East River at East 17th Street
    			(40.735519, -73.974595),    //24
    			(40.742998, -73.971806),    //34
    			(40.754767, -73.96215),     //53
    			(40.762146, -73.954296),    //65
    			(40.771474, -73.946185),    //81
    			(40.776154, -73.942022),    //89
    			(40.776154, -73.942022),    //96
    			(40.787008, -73.93816),     //103
    			(40.795326, -73.929534),    //118
    			(40.800946, -73.929062),    //125
    			(40.808775, -73.934212),    //Harlem River at 132nd Street
    			(40.817772, -73.933868),    //143
    			(40.83547,  -73.935113),    //163
    			(40.855857, -73.922195),    //Dyckman Street
    			(40.869878, -73.91078),     //218
    			(40.873416, -73.911767),    //Broadway Bridge
    			(40.877018, -73.922968),    //Henry Hudson Parkway Bridge
    			(40.877082, -73.926916),    //Hudson River
    			(40.867379, -73.933096),    //Riverside Drive
    			(40.852417, -73.943224),    //Hudson River at West 181st Street
    			(40.850339, -73.946786),    //George Washingon Bridge
    			(40.850339, -73.946786),    //168
    			(40.834626, -73.95052),     //155
    			(40.827417, -73.955026),    //144 sewage treatment plant
    			(40.828034, -73.956399),    //144
    			(40.82365,  -73.959446),    //137
    			(40.822676, -73.957601),    //137
    			(40.771669, -73.994765),    //57
    			(40.769524, -73.995152),    //54
    			(40.763316, -73.999872),    //44
    			(40.762276, -74.001718),    //42
    			(40.754052, -74.007726),    //29
    			(40.749825, -74.009442),    //23
    			(40.748362, -74.00794),     //21
    			(40.740754, -74.009228),    //Meatpacking District
    			(40.739258, -74.010344),    //Gansevoort Street
    			(40.726218, -74.011545),    //Holland Tunnel
    			(40.718315, -74.013176),    //Battery Park City
    			(40.718737, -74.016609),    //Battery Park City
    			(40.706539, -74.019227),    //South Cove
    			(40.70078,  -74.014893),    //Battery Park
    			(40.701919, -74.009314),    //Heliport
    			(40.708523, -73.997984),    //north of Brooklyn Bridge
    			(40.710475, -73.977985),    //Corlears Hook Park
    			(40.712752, -73.976011),    //Grand Street
    			(40.720819, -73.972964)
    		];
    
    		CGPathMoveToPoint(shoreline, nil, a[0].longitude, a[0].latitude);
    		for var i = 1; i < a.count; ++i {
    			CGPathAddLineToPoint(shoreline, nil, a[i].longitude, a[i].latitude);
    		}
    		CGPathCloseSubpath(shoreline);
    

    Change the body of draw(_:) to the following.

    		//Drawing code
    
    		//Latitude and longitude in degrees of the heart of Manhattan,
    		//probably somewhere around the Angel of the Waters in Central Park.
    		let latitude: CGFloat =    40.79;	//north of equator is positive
    		let longitude: CGFloat =  -73.96;	//west of Greenwich is negative
    
    		//How many pixels per degree of latitude (approx. 60 miles).
    		//The screen will cover 1/4 of a degree of latitude vertically.
    		let scale: CGFloat = 4 * bounds.size.height;
    
    		let c: CGContextRef = UIGraphicsGetCurrentContext();
    		CGContextBeginPath(c);	//unnecesary here: the path is already empty
    
    		//Three transformations:
    		//Transformation 1: move the origin (0, 0) to the center of the View.
    		CGContextTranslateCTM(c, bounds.size.width / 2, bounds.size.height / 2);
    
    		//Transformation 2: magnify Manhattan to make it big enough to see.
    		//Also make the Y axis point upwards so that north is up.
    		let radians: Float = GLKMathDegreesToRadians(Float(latitude));
    		CGContextScaleCTM(c, scale * CGFloat(cos(radians)), -scale);
    
    		//Transformation 3: move the camera from the South Atlantic Ocean
    		//left and up to Manhattan.  More precisely, move Manhattan right
    		//and down to the South Atlantic.  That's why there are negative signs.
    		CGContextTranslateCTM(c, -longitude, -latitude);
    
    		CGContextAddPath(c, shoreline);
    		CGContextSetRGBFillColor(c, 0, 0, 1, 1);	//blue, opaque
    		CGContextFillPath(c);