Gargoyles of New York

Software

In New York, there are so many sights and sounds that it’s easy to overlook the city’s more commonplace features. Gargoyles of New York, out now on the iOS App Store, is a documentary photo essay that showcases the city’s ubiquitous standpipes.

Day and Night

I developed Gargoyles with Guido Jiménez-Cruz. The app displays a collection of his photos, accompanied by ambient sound from the location of each photo. Gargoyles also has a hidden feature: the photos you see reflect the local time in New York.

If it’s daytime in New York, the app will show photos taken during the day. After sunset, the images will be the nighttime versions of the same standpipes.

Solving for Day and Night

To achieve the goal of automatically switching into night mode at the correct time, I needed to know the sunrise and sunset times for the current day. This information is available online, but ideally, the app would not be dependent on internet connectivity. It’s possible to calculate the values using only the time and location, so we can hard code the location and query iOS for the current time. I found a summary of the algorithm on one site, with a more complete explanation on another. Here is the implementation in Objective C:

BOOL isCurrentlyDaytime(CGFloat latitude, CGFloat longitude)
{
// http://users.electromagnetic.net/bu/astro/iyf-calc.php
// http://aa.quae.nl/en/reken/zonpositie.html
static NSUInteger const secondsPerDay = 60*60*24;
static NSTimeInterval const julianLeap = 0.0009;
static double const degreeToRadian = M_PI/180.0;
static double const radianToDegree = 180.0/M_PI;
//reference date is Jan 1, 2000
static NSUInteger const julianReferenceDate = 2451545;
static NSTimeInterval const julianReferenceIntervalSince1970 = 946684800;
static double const earthMeanAnomaly = 357.5291;
static double const earthMeanAnomalyPerDay = 0.98560028;
static double const earthEquationOfCenterCoefficient1 = 1.9148;
static double const earthEquationOfCenterCoefficient2 = 0.0200;
static double const earthEquationOfCenterCoefficient3 = 0.0003;
static double const earthEccentricityVariance = 0.0053;
static double const earthObliquityVariance = –0.0069;
static double const earthObliquityEquator = 23.45;
static double const earthEllipticLongitude = 102.9372;
static double const earthLightRefraction = –0.83;
NSDate *referenceDate = [NSDate dateWithTimeIntervalSince1970:julianReferenceIntervalSince1970];
//julian cycle since Jan 1, 2000
NSTimeInterval intervalSinceReferenceDate = [[NSDate date] timeIntervalSinceDate:referenceDate];
NSUInteger daysSinceReferenceDate = intervalSinceReferenceDate / secondsPerDay;
NSUInteger n = (NSUInteger)round((daysSinceReferenceDate-julianLeap) – (longitude / 360.0));
double j1 = julianReferenceDate+julianLeap+(longitude/360.0)+n;
double M = (earthMeanAnomaly + earthMeanAnomalyPerDay * (j1 – julianReferenceDate));
M = fmod(M, 360.0);
double C = (earthEquationOfCenterCoefficient1 * sin(degreeToRadian * M))
+ (earthEquationOfCenterCoefficient2 * sin(degreeToRadian * 2.0 * M))
+ (earthEquationOfCenterCoefficient3 * sin(degreeToRadian * 3.0 * M));
double ellipticalLongitude = (M + earthEllipticLongitude + C + 180.0);
ellipticalLongitude = fmod(ellipticalLongitude, 360.0);
double julianTransit = j1
+ (earthEccentricityVariance * sin(degreeToRadian * M))
+ (earthObliquityVariance * sin(degreeToRadian * 2.0 * ellipticalLongitude));
double solarDeclination = radianToDegree*asin(sin(degreeToRadian * ellipticalLongitude)
* sin(degreeToRadian * earthObliquityEquator));
double hourAngle = radianToDegree*acos((sin(degreeToRadian * earthLightRefraction) – sin(degreeToRadian * latitude) * sin(degreeToRadian * solarDeclination))
/ (cos(degreeToRadian * latitude) * cos(degreeToRadian * solarDeclination)));
double j2 = julianReferenceDate + julianLeap + ((hourAngle + longitude)/360.0) + n;
NSTimeInterval julianSunset = j2
+ (earthEccentricityVariance * sin(degreeToRadian * M))
+ (earthObliquityVariance * sin(degreeToRadian * 2.0 * ellipticalLongitude));
NSTimeInterval julianSunrise = julianTransit – (julianSunset – julianTransit);
// http://stackoverflow.com/a/27709317/3813982
NSTimeInterval JD_JAN_1_2000_0000GMT = julianReferenceDate – 0.5;
NSTimeInterval sunriseInterval = (julianSunrise – JD_JAN_1_2000_0000GMT) * secondsPerDay;
NSTimeInterval sunsetInterval = (julianSunset – JD_JAN_1_2000_0000GMT) * secondsPerDay;
#if DEBUG
NSDate *sunrise = [NSDate dateWithTimeIntervalSince1970:julianReferenceIntervalSince1970+sunriseInterval];
NSDate *sunset = [NSDate dateWithTimeIntervalSince1970:julianReferenceIntervalSince1970+sunsetInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"hh:mm a"];
__unused NSString *formattedSunrise = [dateFormatter stringFromDate:sunrise];
__unused NSString *formattedSunset = [dateFormatter stringFromDate:sunset];
#endif
return sunriseInterval <= intervalSinceReferenceDate && intervalSinceReferenceDate < sunsetInterval;
}

view raw
SunriseSunset.m
hosted with ❤ by GitHub

The #if DEBUG section at the end is only for testing. If built in debug mode, you can set a break point and see the sunrise and sunset times in a human-readable form.

Now all we have to do is look up the latitude and longitude of New York, and plug those numbers into the algorithm:

BOOL isCurrentlyDaytimeInNYC()
{
static CGFloat const latitudeNYC = 40.7831;
static CGFloat const longitudeNYC = 73.9712;
return isCurrentlyDaytime(latitudeNYC,
longitudeNYC);
}

view raw
SunriseSunset.m
hosted with ❤ by GitHub

Available Now

I hope this peek into the development of Gargoyles has been interesting. Please head over to the App Store and give the app a try.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s