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. main.m
  2. Class ManhattanAppDelegate
  3. Class View

Latitude and longitude

To get the latitudes and longitudes of the points around the Manhattan shoreline, go to Google Maps. Right-click (or control-click on Mac) on a point and select “What’s Here?”. The latitude and longitude will appear in the box where you type the URLs.

I wrote the plotter using the JavaScript Google Maps API Version 3. Column 1 is latitude, column 2 is longitude.

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 CLLocationCoordinate2D is a structure containing two fields, latitude and longitude. We mentioned this structure here. The drawRect: method of class View contains an array of 48 of these structures.

The name of the array is a. The expression sizeof a is the total number of bytes in the entire array a. The expression sizeof a[0] is the total number of bytes in the first element of the array. (It doesn’t matter which element we use—they’re all the same size—so we’ll use the first one.) The quotient sizeof a / sizeof a[0] is therefore the number of elements in the array (48). A variable that holds the number of elements in an array, or that holds an array subscript, should be of type size_t. Finally, a variable marked with the keyword static is created once and for all. It is not re-created every time drawRect: is called. drawRect: is called only once in this app, but we might use class View in another app where its drawRect: is called many times.

Three transformations

The drawRect: method of class View 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 by 2000.

  1. CGTranslateCTM. Translate the origin (0, 0) to the center of the View.

  2. CGContextScaleCTM. Without this scaling, Manhattan would be too small to see. scale is the number of pixels per degree of latitude (about 60 miles). It has to be negated because the latitude increases upwards (as you go towards the North Pole) while y increases downwards (as you go towards the bottom edge of the view). scale * cosf(ytranslate * π / 180) is the number 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. We call cosf instead of cos because all we need is a CGFloat answer, not a double answer. Multiplication by π / 180 converts degrees to radians.

  3. Another CGTranslateCTM. 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.

Things to try

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

  2. Insert the following after the CGContextClosePath.
    	CGContextMoveToPoint(c,    -73.97305,  40.764291);	//Grand Army Plaza
    	CGContextAddLineToPoint(c, -73.981762, 40.767997);	//Columbus Circle
    	CGContextAddLineToPoint(c, -73.958116, 40.800556);	//Frederick Douglass Circle
    	CGContextAddLineToPoint(c, -73.949235, 40.796848);	//Frawley Circle
    	CGContextClosePath(c);					//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 red lake in Central Park, and a white island in the lake.

  4. Make Manhattan vertical. Insert the following statement immediately before the CGContextScaleCTM. (If it was after, the angle would have to be positive for counterclockwise.) Multiplication by π/180 converts degrees to radians.
    	CGContextRotateCTM(c, -28.9 * M_PI / 180);	//negative for counterclockwise
    
  5. Now that Manhattan is vertical, make it fat. Insert the following statement immediately before the CGContextRotateCTM. (What happens if you insert it after the rotate?)
    	CGContextScaleCTM(c, 2, 1);	//Make Manhattan fat.
    

  6. If you 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 initWithFrame: once and drawRect: once. The initWithFrame: method of a view object will always be called only once, but drawRect: may be called many times. We should therefore move as much of the work as we can into initWithFrame:.

    Add the following instance variable to class View. We saw it in the triangle example in Japan.

    	CGMutablePathRef shoreline;	//inside {curly braces} in View.h
    

    Initialize shoreline inside the if statement in initWithFrame:, immediately after setting the background color.

    		static const CLLocationCoordinate2D a[] = {
    			{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
    		};
    		static const size_t n = sizeof a / sizeof a[0];
    
    		shoreline = CGPathCreateMutable();
    		CGPathMoveToPoint(shoreline, NULL, a[0].longitude, a[0].latitude);
    		for (size_t i = 1; i < n; ++i) {
    			CGPathAddLineToPoint(shoreline, NULL, a[i].longitude, a[i].latitude);
    		}
    		CGPathCloseSubpath(shoreline);
    

    Change the body of drawRect: 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.
    	CGFloat latitude =    40.79;	//north of equator is positive
    	CGFloat longitude =  -73.96;	//west of Greenwich is negative
    
    	CGFloat scale = 2400;	//pixels per degree of latitude (approx. 60 miles)
    
    	CGContextRef c = UIGraphicsGetCurrentContext();
    	CGContextBeginPath(c);
    
    	//Three transformations:
    	//Transformation 1: move the origin (0, 0) to the center of the View.
    	CGSize s = self.bounds.size;
    	CGContextTranslateCTM(c, s.width / 2, s.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.
    	CGContextScaleCTM(c, scale * cosf(latitude * M_PI / 180), -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, 1, 0, 0, 1);	//red, opaque
    	CGContextFillPath(c);
    

    Add the following method to class View. For dealloc, see the memory management documentation.

    //Called automatically at the end of the View's life.
    
    - (void) dealloc {
    	CGPathRelease(shoreline);
    }