This app uses the same graphics functions as the triangle in Japan, but the math is much more sophisticated.
main.m
ManhattanAppDelegate
View
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
a
.
The expression
sizeof a[0]
sizeof a / sizeof a[0]
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.
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.
CGTranslateCTM
.
Translate the origin
View
.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)
cosf
instead of
cos
because all we need is a
CGFloat
answer,
not a
double
answer.
Multiplication by
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.
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 PlazaThe 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.
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
CGContextRotateCTM
.
(What happens if you insert it after the rotate?)
CGContextScaleCTM(c, 2, 1); //Make Manhattan fat.
1.0 / scale
scale
.
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); }