This app uses the same graphics functions as the triangle in Japan, but the math is much more sophisticated.
AppDelegate
:
unchangedViewController.swift
:
unchanged.ManhattanView.swift
:
added the
init
that takes an
NSCoder
,
and
draw(_:)
.
Main.storyboard
Info.plist
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 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).");
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.
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.
translateBy(x:y:)
.
Translate the origin
ManhattanView
.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.
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.
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.
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 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.
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
rotate(by:)
.
(What would happen if we inserted it after the
rotate(by:)
?)
c.scaleBy(x: 2, y: 1); //Make Manhattan fat.
1.0 / scale
scale
.
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);