Gargoyles of New York


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)
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);
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;
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];
return sunriseInterval <= intervalSinceReferenceDate && intervalSinceReferenceDate < sunsetInterval;

view raw
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,

view raw
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: Logo

You are commenting using your 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