Developer's Guide - paulmoore/AstroCalendar GitHub Wiki

What this guide covers

This manual describes how to write applications based on the AstroCalendar project. Applications may be developed for many different platforms (Android, Apple, etc.), but at the time of this writing, the only application that has been developed is AstroCal for the iPhone.

Contents

This project contains two main aspects; the API and the application.

Working with the API and Swiss Ephemeris

The API is made up of a number of PHP functions. These functions parse data requests, communicate with Swiss Ephemeris, and pullout data computed by Swiss Ephemeris. There are also functions that format this Swiss Ephemeris data and send it back to the requester. The following is a break down of the parts that make up the API.

  1. api.php
    • This is the main entry point for the api. It takes a user’s requested data range, from a start date and an end date, and it takes a request type, longitude, latitude, altitude and GMT offset. These values are received through GET requests.

    • PHP will parse these GET values and place it in an array called $_GET. Request type is used for future development, because if the application allows users to specify what they want, through here, then the API has been set up to use a switch statement based upon what request type is.

    • The API uses getDate.php to convert the string given in $_GET to an understandable start date and end date. It then parses them into a numerical representation so that the API knows how many days to iterate through. Currently this function requires all, and will calculate all data.

    • From here, the API will begin going through the lunar days from start date to end date. The JSON is set up in solar terms for the solar part of the calendar and for ease of parsing. For each day, the API needs to calculate sunrise, sunset, moonrise, moonset and however many number of tithis, pakshas and lunar months happen.
      To do this, the API uses sweph.php. All of this calculated information is placed into an array. The array contains a count index, so the data handlers written for the iDevice know how many days have been received. Each numerical index is considered to be a solar day’s worth of data. In this “day” there is the day number, month, year and a “payload” of data. This payload consists of: sunrise, sunset, moonrise, moonset, name of the tithi(s) starting in this day, the time when the tithi(s) start, the paksha(s) starting on this day, and lunar month(s) starting on this day.
      Note that paksha and lunar month times are dependent on the tithi. Then the API calls sendJSON.php to convert the array to a JSON and sends it to the client’s data handlers.

    • getDate.php – This PHP file has a constructor that takes the start date and the end date and sets them into the object getDate. Then createArray() is called, and it parses the start date and end date, and finally places their numerical day, month and year into an array. This array is then returned to the API.

    • sweph.php – This PHP file is used to pull out ephemeris data and to calculate lunar data. It has the functions, getSunrise, getSunset, getMoonrise, getMoonset, that take the year, month, day, longitude, latitude and altitude to compute the respective time of each rise/set. There are functions that then return this calculated information to the API.

    • For each of these functions, it pulls ephemeris data by using swe_rise_trans and passing sun/moon and rise/set depending on what is being queried. Before these functions could be called, there needs to be a set time for ephemeris to look up for these data. Midnight was chosen for this time because this data was for the solar calendar. Times have to be converted to Julian time (a different means of measuring time that ephemeris uses).
      Once the time (Julian time) was received from ephemeris, it had to be converted back to universal time, before being returned.
      In order to calculate lunar data, the sun’s longitude and latitude and moon’s longitude were needed. The functions that make these calculations require the year, month, day, and hour (which could be a double and have fractional amounts), which translate to minutes and seconds. Once again, ephemeris needs the time translate to Julian time. Then an ephemeris function swe_calc_ut was used to find each of these time value, and then returned.

    • The next function, getDiff finds the difference between the sun’s and moon’s longitude mod 360. Finally, the function getLunarData uses the previous functions to calculate the tithis, paksha and lunar months, which use getHousePos to determine which house the moon is in. The actual use of the ephemeris functions was determined by looking up in the Swiss Ephemeris documentation to find what function gave the appropriate data. Then, PHPSweph can be referenced to see what order the parameters needed to be called in, because Swiss Ephemeris functions can not be called directly. Sweph functions cannot be called directly as they are written in C and PHPSweph was developed to extend Swiss Ephemeris to PHP.

    • sendJSON.php – This function in the PHP file is used to help with the creation of JSON and sending it.
      createJSON takes a PHP array and encodes it into JSON. If no errors happen, it will then return it to the API.
      returnJSON takes a JSON message, sets the header to ‘Content-type: text/json’ so the client’s data handlers will know they are receiving JSON. Finally the JSON is echoed to the client.

iPhone Data Management and UI Organizational Guide

Data Management on the iPhone is broken up in to four main components:

  1. AFNetworking
    Open source asynchronous networking library. Used for network requests (to the API) in askApiForDates(). Utilizes iOS core JSON libraries to consume JSON.
    https://github.com/AFNetworking/AFNetworking

  2. JSONKit
    Open source library used to consume JSON if the Apple core JSON libraries aren't available - this is the case for older iOS versions (such as 3) however our code only builds for iOS 5, so this is a non-issue (it just needs to be there).
    https://github.com/johnezang/JSONKit

  3. Tapku
    Open source library that offers many custom View components. We use this library or the Sun Calendar View that it provides.
    https://github.com/devinross/tapkulibrary

  4. AstroCal

All of AFNetworking, JSONKit, and Tapku are available on Github with the links provided. NOTE: All of these libraries do not support ARC. AstroCal utilizes the ARC introduced in iOS 5, so these two libraries must be compiled specifically with the ARC off (-fno-objc-arc in the compiler flags). A future build of these libraries will most likely introduce ARC into their code base.

AstroCal data management is broken in to the following source modules:

  1. CoreLocationControllerDelegate
    Describes and protocol and implementation of said protocol to allow the MasterDataHandler to respond to CoreLocation updates (such as GPS). This most likely will not require modification, as it simply calls a delegate that is implemented in the MasterDataHandler.

  2. RingBuffer
    ARC-compatible local implementation of a RingBuffer. The ring buffer is generic (should handle any object type) and includes the ability to write/restore plists (as in, you can save the contents of the ring buffer and its state to a plist, and load it back). Note that only value types, some NS-based objects, arrays, and dictionaries can be automatically serialized for plists.
    This class is utilized as a way to manage the data cache for 24 months - however it does not contain any specific logic for that use (it's general-purpose).

  3. DayContainer
    Mostly a container class that stores the main attributes for a single solar day. All data incoming from the API is stored in a DayContainer and cached both in-memory and on-disk. This class contains two utility methods required for storing its properties in an NSDictionary, and reconstructing a DayContainer from an NSDictionary. This is utilized by the MasterDataHandler in order to save/load DayContainers in plists.

  4. MasterDataHandler
    This is the primary logic class. It is designed as a singleton, so that there is only ever one reference to the MasterDataHandler (to preserve data integrity). All GPS, caching, API requests, etc go through here.
    Initialization of the object (for when a reference is required) looks like this: MasterDataHandler *handler = [MasterDataHandler sharedManager]; which will return the active instance. NEVER alloc/init manually, as this will cause access issues and problems with the ARC.

On initialization, CoreLocationServices are initialized (to receive GPS updates), application settings are loaded off-disk, and the data cache is initialized. If the cache cannot be properly initialized (due to a lack of data integrity), it will be reset to the standard 24-months. More details on caching are given below.

getDate(startDate, endDate, delegate) is the front-facing method call utilized to retrieve data. Each day in the provided date range is checked against the cache - once a date is found that is NOT in the cache, the API will be queried for all dates in the startDate-endDate range, updating the cache; otherwise the cached information is returned. This method utilizes a delegate that is raised to handle the returned data - this is because accessing the API (if necessary) is an asynchronous action, and we cannot guarantee when a response will be given. In this way the calling thread does not have to spin-wait for data to be returned.

askApiForDates(startDate, endDate, delegate) makes actual calls to the API endpoint, and parses the response. Requests are sent as an asynchronous JSON request (which automatically handles JSON verification when a response is received). On success, the JSON is decoded and turned into an array of DayContainer objects. These objects are then parsed to handle missing information, split days into multiple units (i.e. a day with two tithis is split in to two logical DayContainers with the same date). Once this is done, the array is sorted by date (in ascending order), added to the cache, and finally passed to the input delegate for invoker consumption. If an error is encountered, the user is notified through a UIAlertView.

Local alerts (or local notifications) with a custom message and alert date/time can be registered/queried/removed using registerAlertOnDate, getAlertsOnDate, and deregisterAlert.

locationUpdate is the delegate satisfying the CoreLocationControllerDelegate protocol, and is raised whenever GPS information is updated. At the moment, this data is stored in the application settings dictionary and saved to disk.

Caching

The cache works by breaking data in to two components. 24 plists are stored on the iDevice, one plist per-month of data stored. These are persistent and overwritten as required. A ring buffer instance is used to index these plists for specific months. An NSDictionary is utilized to store this information in-memory for fast access - whenever a change gets made to the cache (such as adding a new day), the cache is written out to the iDevice (stored as a plist).

Cache writes (and updates) are triggered any time new data is received from the API, in order to maintain consistency. This is not optimal, but guarantees data integrity.

Adding a date to the cache consists of looking up the date month in the ring buffer. If it's found, the date is stored in the NSDictionary at the given index. Otherwise the ring buffer is given a new NSDictionary which overwrites the oldest entry in the cache.

AstroCal User Interface is broken into the following source modules:

  1. AstroCalendarAppDelegate
    The Application Delegate is the entry point to the application.
    Handles initializing objects, and defining a new viewing window.
    Creates the root of the navigation stack, a UINavigationController instance.
    Pushes the first UIViewController onto the navigation stack, which starts the application (an AstroCalendarMoonViewController instance).
    Makes a request for the next 31 days worth of data, the initial view of the application.

  2. UINavigationController+UniqueStack
    This custom category of the UINavigationController contains methods to only push a UIViewController onto the navigation stack if an instance of that Class does not already exist within the stack. This is useful for the requirement that the app should be navigable, but buttons on the bottom to allow navigation to anywhere.
    See Any of the UIViewController subclasses to see examples on how to use this.

  3. AstroCalendarMoonViewController
    This UIViewController subclass provides, at its simplest, a UITableView that displays the requested Lunar Calendar Data.
    This class conforms the the MasterDataHandlerProtocol protocol, to allow data to be received from the MasterDataHandler.
    To display Lunar Calendar information, make a query to the class by calling the following method: (void)loadDates:(DateRangeRequest *)request.
    When data is received from the MasterDataHandler, this class populates its UITableView with the proper information. It dynamically inserts Sections (which represent Lunar Months) based on the incoming data.
    While waiting for data, this class approximates the amount of data it should receive, and displays loading progress for the data to make the app feel active.
    The Cells use the reuse identifier "AstroCalendarMoonViewCell", this allows the class to repopulate the table and later reuse those cells, thus reducing lag when the real data is received from the server.

  4. AstroCalendarMoonViewCell
    This UITableViewCell subclass handles displaying Lunar Calendar Data in a 'row' of the table.
    You can configure the cell with Lunar Calendar by calling the method: - (void)configureWithDate:(NSDate *)date tithi:(NSString *)tithe fortnight:(NSString *)fortnight.
    Note there is no argument for Lunar Month. That is because they are displayed as sections in the parent Table, which manages them itself.
    This class also manages colouring the cells. You can change these colours by modifying the code in the previously mentioned method.
    Related NIB files: AstroCalendarMoonViewCell_iPhone.xib, AstroCalendarMoonViewCell_iPad.xib

  5. AstroCalendarSunViewController
    This class is a TKCalendarMonthView subclass.
    This super class allows for the display of a sun calendar's month view.
    This replicates the behaviour of the built-in Application iCalMobile
    When this class receives the event that a user has selected a date, the - (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)date.
    In here, the sun calendar view pushes a day details view onto the navigation stack, this leads us to the next class.

  6. AstroCalendarDayViewController
    This class is a simple UIViewController subclass.
    This class conforms to the MasterDataHandlerProtocol protocol, as does the AstroCalendarMoonViewController.
    Date requests are make in the same way they are via the AstroCalendarMoonViewController.
    Currently, this class simply makes a request for the requested day, and displays the following information:

    • sunrise
    • sunset
    • moonrise
    • moonset
    • the date
      This class can be extended by simply adding components to the view.
      Related NIB files: AstroCalendarDayViewController_iPhone.xib, AstroCalendarDayViewController_iPad.xib.
  7. AstroCalendarHelpViewController
    Presently, this class is a simple UIViewController subclass that contains a scrollable text view.
    The text view displays Astrology related information
    Related NIB files: AstroCalendarHelpViewController_iPhone.xib, AstroCalendarHelpViewController_iPad.xib.

Unit Tests
Unit tests exist in AstroCalendarTests, and are written to test the RingBuffer and data caching logic. No networking tests have been written. Adding a new test is as simple as adding a new method.

Version History

At the time of this writing, there is only one version of the AstroCalendar.