{"action":"created","comment":{"url":"https://api.github.com/repos/astropy/astroplan/pulls/comments/38163970","id":38163970,"diff_hunk":"@@ -0,0 +1,577 @@\n+# Licensed under a 3-clause BSD style license - see LICENSE.rst\n+\"\"\"\n+Specify and constraints to determine which targets are observable for\n+an observer.\n+\"\"\"\n+\n+from __future__ import (absolute_import, division, print_function,\n+                        unicode_literals)\n+\n+# Standard library\n+from abc import ABCMeta, abstractmethod\n+import datetime\n+\n+# Third-party\n+from astropy.time import Time\n+import astropy.units as u\n+from astropy.coordinates import get_sun, Angle\n+from astropy.coordinates.angle_utilities import angular_separation\n+from astropy.extern.six import string_types\n+import numpy as np\n+\n+# Package\n+from .moon import get_moon, moon_illumination\n+\n+DEFAULT_TIME_RESOLUTION = 0.5*u.hour\n+\n+__all__ = [\"AltitudeConstraint\", \"AirmassConstraint\", \"AtNightConstraint\",\n+           \"is_observable\", \"is_always_observable\", \"time_grid_from_range\",\n+           \"SunSeparationConstraint\", \"MoonSeparationConstraint\",\n+           \"MoonIlluminationConstraint\", \"LocalTimeConstraint\"]\n+\n+@u.quantity_input(time_resolution=u.hour)\n+def time_grid_from_range(time_range, time_resolution=DEFAULT_TIME_RESOLUTION):\n+    \"\"\"\n+    Get linearly-spaced sequence of times.\n+\n+    Parameters\n+    ----------\n+    time_range : `~astropy.time.Time`\n+        Lower and upper bounds on time sequence. If a scalar time is input,\n+        make the input time the lower limit on the time sequence, and the\n+        upper bound one attosecond later than the input time.\n+\n+    time_resolution : `~astropy.units.quantity` (optional)\n+        Time-grid spacing\n+\n+    Returns\n+    -------\n+    times : `~astropy.time.Time`\n+        Linearly-spaced sequence of times\n+    \"\"\"\n+    if time_range.isscalar:\n+        return Time([time_range])\n+    else:\n+        return Time(np.arange(time_range[0].jd, time_range[1].jd,\n+                              time_resolution.to(u.day).value), format='jd')\n+\n+@abstractmethod\n+class Constraint(object):\n+    \"\"\"\n+    Abstract class for objects defining observational constraints.\n+    \"\"\"\n+    __metaclass__ = ABCMeta\n+\n+    def __call__(self, time_range, observer, targets,\n+                 time_resolution=DEFAULT_TIME_RESOLUTION):\n+\n+        if not isinstance(time_range, Time):\n+            time_range = Time(time_range)\n+        cons = self._compute_constraint(time_range, observer, targets,\n+                                        time_resolution=time_resolution)\n+        return cons\n+\n+    def _compute_constraint(self, time_range, observer, targets):\n+        # Should be implemented on each subclass of Constraint\n+        raise NotImplementedError\n+\n+    def _get_altaz(self, time_range, observer, targets,\n+                   time_resolution=DEFAULT_TIME_RESOLUTION,\n+                   force_zero_pressure=False):\n+        \"\"\"\n+        Calculate alt/az for ``target`` at times linearly spaced between\n+        the two times in ``time_range`` with grid spacing ``time_resolution``\n+        for ``observer``.\n+\n+        Cache the result on the ``observer`` object.\n+\n+        Parameters\n+        ----------\n+        time_range : `~astropy.time.Time`\n+            Lower and upper time bounds on which to compute the constraints.\n+            If input is a scalar time, will only compute at that time (no\n+            range).\n+\n+        targets : {list, `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`}\n+            Target or list of targets\n+\n+        observer : `~astroplan.Observer`\n+            The observer who has constraints ``constraints``\n+\n+        time_resolution : `~astropy.units.Quantity` (optional)\n+            Set the time resolution in calculations of the altitude/azimuth\n+\n+        Returns\n+        -------\n+        altaz_dict : dict\n+            Dictionary containing two key-value pairs. (1) 'times' contains the\n+            times for the alt/az computations, (2) 'altaz' contains the\n+            corresponding alt/az coordinates at those times.\n+        \"\"\"\n+        if not hasattr(observer, '_altaz_cache'):\n+            observer._altaz_cache = {}\n+\n+        times = time_grid_from_range(time_range,\n+                                     time_resolution=time_resolution)\n+\n+        # convert times, targets to tuple for hashing\n+        aakey = (tuple(times.jd), tuple(targets))\n+\n+        if aakey not in observer._altaz_cache:\n+            if force_zero_pressure:\n+                observer_old_pressure = observer.pressure\n+                observer.pressure = 0\n+\n+            altaz = observer.altaz(times, targets)\n+            observer._altaz_cache[aakey] = dict(times=times,\n+                                                altaz=altaz)\n+            if force_zero_pressure:\n+                observer.pressure = observer_old_pressure\n+\n+        return observer._altaz_cache[aakey]\n+\n+class AltitudeConstraint(Constraint):\n+    \"\"\"\n+    Constrain the altitude of the target.\n+\n+    .. note::\n+        This will misbehave if you try to constrain negative altitudes, as\n+        the `~astropy.coordinates.AltAz` frame tends to mishandle negative\n+        altitudes.\n+    \"\"\"\n+    def __init__(self, min=None, max=None):\n+        \"\"\"\n+        Parameters\n+        ----------\n+        min : `~astropy.units.Quantity` or `None`\n+            Minimum altitude of the target. `None` indicates no limit.\n+\n+        max : `~astropy.units.Quantity` or `None`\n+            Maximum altitude of the target. `None` indicates no limit.\n+        \"\"\"\n+        if min is None:\n+            self.min = 0*u.deg\n+        else:\n+            self.min = min\n+        if max is None:\n+            self.max = 90*u.deg\n+        else:\n+            self.max = max\n+\n+    def _compute_constraint(self, time_range, observer, targets,\n+                            time_resolution=DEFAULT_TIME_RESOLUTION):\n+\n+        cached_altaz = self._get_altaz(time_range, observer, targets,\n+                                       time_resolution=time_resolution)\n+        altaz = cached_altaz['altaz']\n+        lowermask = self.min < altaz.alt\n+        uppermask = altaz.alt < self.max\n+        return lowermask & uppermask\n+\n+class AirmassConstraint(AltitudeConstraint):\n+    \"\"\"\n+    Constrain the airmass of a target.\n+\n+    In the current implementation the airmass is approximated by the secant of\n+    the zenith angle.\n+    \"\"\"\n+    def __init__(self, max=None, min=None):\n+        \"\"\"\n+        .. note::\n+            The ``max`` and ``min`` arguments appear in the order (max, min)\n+            in this initializer to support the common case for users who care\n+            about the upper limit on the airmass (``max``) and not the lower\n+            limit.\n+\n+        Parameters\n+        ----------\n+        max : float or `None`\n+            Maximum airmass of the target. `None` indicates no limit.\n+\n+        min : float or `None`\n+            Minimum airmass of the target. `None` indicates no limit.\n+\n+        Examples\n+        --------\n+        To create a constraint that requires the airmass be \"better than 2\",\n+        i.e. at a higher altitude than airmass=2::\n+\n+            AirmassConstraint(2)\n+        \"\"\"\n+        self.min = min\n+        self.max = max\n+\n+    def _compute_constraint(self, time_range, observer, targets,\n+                            time_resolution=DEFAULT_TIME_RESOLUTION):\n+        cached_altaz = self._get_altaz(time_range, observer, targets,\n+                                       time_resolution=time_resolution)\n+        altaz = cached_altaz['altaz']\n+        if self.min is None and self.max is not None:\n+            mask = altaz.secz < self.max\n+        elif self.max is None and self.min is not None:\n+            mask = self.min < altaz.secz\n+        elif self.min is not None and self.max is not None:\n+            mask = (self.min < altaz.secz) & (altaz.secz < self.max)\n+        else:\n+            raise ValueError(\"No max and/or min specified in \"\n+                             \"AirmassConstraint.\")\n+        return mask\n+\n+class AtNightConstraint(Constraint):\n+    \"\"\"\n+    Constrain the Sun to be below ``horizon``.\n+    \"\"\"\n+    @u.quantity_input(horizon=u.deg)\n+    def __init__(self, max_solar_altitude=0*u.deg, force_pressure_zero=True):\n+        \"\"\"\n+        Parameters\n+        ----------\n+        max_solar_altitude : `~astropy.units.Quantity`\n+            Define \"night\" as when the sun is below ``max_solar_altitude``.\n+            Default is zero degrees altitude.\n+\n+        force_pressure_zero : bool (optional)\n+            Force the pressure to zero for solar altitude calculations. This\n+            avoids errors in the altitude of the Sun that can occur when the\n+            Sun is below the horizon and the corrections for atmospheric\n+            refraction return nonsense values. Default is `True`.\n+        \"\"\"\n+        self.max_solar_altitude = max_solar_altitude\n+        self.force_pressure_zero = force_pressure_zero\n+\n+    @classmethod\n+    def twilight_civil(cls, **kwargs):\n+        \"\"\"\n+        Consider nighttime as time between civil twilights (-6 degrees).\n+        \"\"\"\n+        return cls(max_solar_altitude=-6*u.deg, **kwargs)\n+\n+    @classmethod\n+    def twilight_nautical(cls, **kwargs):\n+        \"\"\"\n+        Consider nighttime as time between nautical twilights (-12 degrees).\n+        \"\"\"\n+        return cls(max_solar_altitude=-12*u.deg, **kwargs)\n+\n+    @classmethod\n+    def twilight_astronomical(cls, **kwargs):\n+        \"\"\"\n+        Consider nighttime as time between astronomical twilights (-18 degrees).\n+        \"\"\"\n+        return cls(max_solar_altitude=-18*u.deg, **kwargs)\n+\n+    def _get_solar_altitudes(self, time_range, observer, targets,\n+                             time_resolution=DEFAULT_TIME_RESOLUTION):\n+        if not hasattr(observer, '_altaz_cache'):\n+            observer._altaz_cache = {}\n+\n+        times = time_grid_from_range(time_range,\n+                                     time_resolution=time_resolution)\n+\n+        aakey = (tuple(times.jd), 'sun')\n+\n+        if aakey not in observer._altaz_cache:\n+            if self.force_pressure_zero:\n+                observer_old_pressure = observer.pressure\n+                observer.pressure = 0","path":"astroplan/constraints.py","position":276,"original_position":276,"commit_id":"58db14fcfdbb855770f9cd2f464c3ce0eddc6bad","original_commit_id":"58db14fcfdbb855770f9cd2f464c3ce0eddc6bad","user":{"login":"ejeschke","id":1305473,"avatar_url":"https://avatars.githubusercontent.com/u/1305473?v=3","gravatar_id":"","url":"https://api.github.com/users/ejeschke","html_url":"https://github.com/ejeschke","followers_url":"https://api.github.com/users/ejeschke/followers","following_url":"https://api.github.com/users/ejeschke/following{/other_user}","gists_url":"https://api.github.com/users/ejeschke/gists{/gist_id}","starred_url":"https://api.github.com/users/ejeschke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ejeschke/subscriptions","organizations_url":"https://api.github.com/users/ejeschke/orgs","repos_url":"https://api.github.com/users/ejeschke/repos","events_url":"https://api.github.com/users/ejeschke/events{/privacy}","received_events_url":"https://api.github.com/users/ejeschke/received_events","type":"User","site_admin":false},"body":"Ditto.  This form of stateful, temporary overrides is prone to problems when there are intervening exceptions before the restoration of the state.  e.g. someone working in an interactive session with an `observer` they won't realize that the pressure that they carefully set when constructing the object has been zeroed.","created_at":"2015-08-28T01:13:15Z","updated_at":"2015-08-28T01:13:15Z","html_url":"https://github.com/astropy/astroplan/pull/61#discussion_r38163970","pull_request_url":"https://api.github.com/repos/astropy/astroplan/pulls/61","_links":{"self":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/comments/38163970"},"html":{"href":"https://github.com/astropy/astroplan/pull/61#discussion_r38163970"},"pull_request":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/61"}}},"pull_request":{"url":"https://api.github.com/repos/astropy/astroplan/pulls/61","id":42296601,"html_url":"https://github.com/astropy/astroplan/pull/61","diff_url":"https://github.com/astropy/astroplan/pull/61.diff","patch_url":"https://github.com/astropy/astroplan/pull/61.patch","issue_url":"https://api.github.com/repos/astropy/astroplan/issues/61","number":61,"state":"open","locked":false,"title":"Constraints","user":{"login":"bmorris3","id":3497584,"avatar_url":"https://avatars.githubusercontent.com/u/3497584?v=3","gravatar_id":"","url":"https://api.github.com/users/bmorris3","html_url":"https://github.com/bmorris3","followers_url":"https://api.github.com/users/bmorris3/followers","following_url":"https://api.github.com/users/bmorris3/following{/other_user}","gists_url":"https://api.github.com/users/bmorris3/gists{/gist_id}","starred_url":"https://api.github.com/users/bmorris3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bmorris3/subscriptions","organizations_url":"https://api.github.com/users/bmorris3/orgs","repos_url":"https://api.github.com/users/bmorris3/repos","events_url":"https://api.github.com/users/bmorris3/events{/privacy}","received_events_url":"https://api.github.com/users/bmorris3/received_events","type":"User","site_admin":false},"body":"Here's a first implementation of the constraints module, based on a rough pseudocode sketch for the inheritance scheme by @eteq. \r\n\r\nTo see how this constraints module would be used, take a peek at this [demo notebook](https://github.com/bmorris3/gsoc2015/blob/master/constraints-demo.ipynb). UPDATE: I've added [high level docs](http://staff.washington.edu/bmmorris/_build/html/tutorials/constraints.html) for you to inspect also.\r\n\r\nThis PR is intended to be a conversation starter. I've made some decisions in naming that will likely be controversial, so let's discuss!","created_at":"2015-08-12T22:26:42Z","updated_at":"2015-08-28T01:13:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5eca3648873341ace33d5a4b1c41e863c5a5dcfe","assignee":null,"milestone":{"url":"https://api.github.com/repos/astropy/astroplan/milestones/1","html_url":"https://github.com/astropy/astroplan/milestones/0.1%20release","labels_url":"https://api.github.com/repos/astropy/astroplan/milestones/1/labels","id":1087263,"number":1,"title":"0.1 release","description":"First release","creator":{"login":"cdeil","id":852409,"avatar_url":"https://avatars.githubusercontent.com/u/852409?v=3","gravatar_id":"","url":"https://api.github.com/users/cdeil","html_url":"https://github.com/cdeil","followers_url":"https://api.github.com/users/cdeil/followers","following_url":"https://api.github.com/users/cdeil/following{/other_user}","gists_url":"https://api.github.com/users/cdeil/gists{/gist_id}","starred_url":"https://api.github.com/users/cdeil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cdeil/subscriptions","organizations_url":"https://api.github.com/users/cdeil/orgs","repos_url":"https://api.github.com/users/cdeil/repos","events_url":"https://api.github.com/users/cdeil/events{/privacy}","received_events_url":"https://api.github.com/users/cdeil/received_events","type":"User","site_admin":false},"open_issues":13,"closed_issues":31,"state":"open","created_at":"2015-04-28T11:35:13Z","updated_at":"2015-08-27T23:24:56Z","due_on":"2015-08-27T22:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/astropy/astroplan/pulls/61/commits","review_comments_url":"https://api.github.com/repos/astropy/astroplan/pulls/61/comments","review_comment_url":"https://api.github.com/repos/astropy/astroplan/pulls/comments{/number}","comments_url":"https://api.github.com/repos/astropy/astroplan/issues/61/comments","statuses_url":"https://api.github.com/repos/astropy/astroplan/statuses/58db14fcfdbb855770f9cd2f464c3ce0eddc6bad","head":{"label":"bmorris3:constraints","ref":"constraints","sha":"58db14fcfdbb855770f9cd2f464c3ce0eddc6bad","user":{"login":"bmorris3","id":3497584,"avatar_url":"https://avatars.githubusercontent.com/u/3497584?v=3","gravatar_id":"","url":"https://api.github.com/users/bmorris3","html_url":"https://github.com/bmorris3","followers_url":"https://api.github.com/users/bmorris3/followers","following_url":"https://api.github.com/users/bmorris3/following{/other_user}","gists_url":"https://api.github.com/users/bmorris3/gists{/gist_id}","starred_url":"https://api.github.com/users/bmorris3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bmorris3/subscriptions","organizations_url":"https://api.github.com/users/bmorris3/orgs","repos_url":"https://api.github.com/users/bmorris3/repos","events_url":"https://api.github.com/users/bmorris3/events{/privacy}","received_events_url":"https://api.github.com/users/bmorris3/received_events","type":"User","site_admin":false},"repo":{"id":34740000,"name":"astroplan","full_name":"bmorris3/astroplan","owner":{"login":"bmorris3","id":3497584,"avatar_url":"https://avatars.githubusercontent.com/u/3497584?v=3","gravatar_id":"","url":"https://api.github.com/users/bmorris3","html_url":"https://github.com/bmorris3","followers_url":"https://api.github.com/users/bmorris3/followers","following_url":"https://api.github.com/users/bmorris3/following{/other_user}","gists_url":"https://api.github.com/users/bmorris3/gists{/gist_id}","starred_url":"https://api.github.com/users/bmorris3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bmorris3/subscriptions","organizations_url":"https://api.github.com/users/bmorris3/orgs","repos_url":"https://api.github.com/users/bmorris3/repos","events_url":"https://api.github.com/users/bmorris3/events{/privacy}","received_events_url":"https://api.github.com/users/bmorris3/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bmorris3/astroplan","description":"Observation planning package for astronomers","fork":true,"url":"https://api.github.com/repos/bmorris3/astroplan","forks_url":"https://api.github.com/repos/bmorris3/astroplan/forks","keys_url":"https://api.github.com/repos/bmorris3/astroplan/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bmorris3/astroplan/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bmorris3/astroplan/teams","hooks_url":"https://api.github.com/repos/bmorris3/astroplan/hooks","issue_events_url":"https://api.github.com/repos/bmorris3/astroplan/issues/events{/number}","events_url":"https://api.github.com/repos/bmorris3/astroplan/events","assignees_url":"https://api.github.com/repos/bmorris3/astroplan/assignees{/user}","branches_url":"https://api.github.com/repos/bmorris3/astroplan/branches{/branch}","tags_url":"https://api.github.com/repos/bmorris3/astroplan/tags","blobs_url":"https://api.github.com/repos/bmorris3/astroplan/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bmorris3/astroplan/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bmorris3/astroplan/git/refs{/sha}","trees_url":"https://api.github.com/repos/bmorris3/astroplan/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bmorris3/astroplan/statuses/{sha}","languages_url":"https://api.github.com/repos/bmorris3/astroplan/languages","stargazers_url":"https://api.github.com/repos/bmorris3/astroplan/stargazers","contributors_url":"https://api.github.com/repos/bmorris3/astroplan/contributors","subscribers_url":"https://api.github.com/repos/bmorris3/astroplan/subscribers","subscription_url":"https://api.github.com/repos/bmorris3/astroplan/subscription","commits_url":"https://api.github.com/repos/bmorris3/astroplan/commits{/sha}","git_commits_url":"https://api.github.com/repos/bmorris3/astroplan/git/commits{/sha}","comments_url":"https://api.github.com/repos/bmorris3/astroplan/comments{/number}","issue_comment_url":"https://api.github.com/repos/bmorris3/astroplan/issues/comments{/number}","contents_url":"https://api.github.com/repos/bmorris3/astroplan/contents/{+path}","compare_url":"https://api.github.com/repos/bmorris3/astroplan/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bmorris3/astroplan/merges","archive_url":"https://api.github.com/repos/bmorris3/astroplan/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bmorris3/astroplan/downloads","issues_url":"https://api.github.com/repos/bmorris3/astroplan/issues{/number}","pulls_url":"https://api.github.com/repos/bmorris3/astroplan/pulls{/number}","milestones_url":"https://api.github.com/repos/bmorris3/astroplan/milestones{/number}","notifications_url":"https://api.github.com/repos/bmorris3/astroplan/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bmorris3/astroplan/labels{/name}","releases_url":"https://api.github.com/repos/bmorris3/astroplan/releases{/id}","created_at":"2015-04-28T15:47:16Z","updated_at":"2015-04-28T15:47:16Z","pushed_at":"2015-08-28T00:06:28Z","git_url":"git://github.com/bmorris3/astroplan.git","ssh_url":"git@github.com:bmorris3/astroplan.git","clone_url":"https://github.com/bmorris3/astroplan.git","svn_url":"https://github.com/bmorris3/astroplan","homepage":"https://astroplan.readthedocs.org/","size":2943,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"astropy:master","ref":"master","sha":"8492a1e6cb3b67b2958ad6db6a708898ab8a8377","user":{"login":"astropy","id":847984,"avatar_url":"https://avatars.githubusercontent.com/u/847984?v=3","gravatar_id":"","url":"https://api.github.com/users/astropy","html_url":"https://github.com/astropy","followers_url":"https://api.github.com/users/astropy/followers","following_url":"https://api.github.com/users/astropy/following{/other_user}","gists_url":"https://api.github.com/users/astropy/gists{/gist_id}","starred_url":"https://api.github.com/users/astropy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astropy/subscriptions","organizations_url":"https://api.github.com/users/astropy/orgs","repos_url":"https://api.github.com/users/astropy/repos","events_url":"https://api.github.com/users/astropy/events{/privacy}","received_events_url":"https://api.github.com/users/astropy/received_events","type":"Organization","site_admin":false},"repo":{"id":34693515,"name":"astroplan","full_name":"astropy/astroplan","owner":{"login":"astropy","id":847984,"avatar_url":"https://avatars.githubusercontent.com/u/847984?v=3","gravatar_id":"","url":"https://api.github.com/users/astropy","html_url":"https://github.com/astropy","followers_url":"https://api.github.com/users/astropy/followers","following_url":"https://api.github.com/users/astropy/following{/other_user}","gists_url":"https://api.github.com/users/astropy/gists{/gist_id}","starred_url":"https://api.github.com/users/astropy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astropy/subscriptions","organizations_url":"https://api.github.com/users/astropy/orgs","repos_url":"https://api.github.com/users/astropy/repos","events_url":"https://api.github.com/users/astropy/events{/privacy}","received_events_url":"https://api.github.com/users/astropy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/astropy/astroplan","description":"Observation planning package for astronomers","fork":false,"url":"https://api.github.com/repos/astropy/astroplan","forks_url":"https://api.github.com/repos/astropy/astroplan/forks","keys_url":"https://api.github.com/repos/astropy/astroplan/keys{/key_id}","collaborators_url":"https://api.github.com/repos/astropy/astroplan/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/astropy/astroplan/teams","hooks_url":"https://api.github.com/repos/astropy/astroplan/hooks","issue_events_url":"https://api.github.com/repos/astropy/astroplan/issues/events{/number}","events_url":"https://api.github.com/repos/astropy/astroplan/events","assignees_url":"https://api.github.com/repos/astropy/astroplan/assignees{/user}","branches_url":"https://api.github.com/repos/astropy/astroplan/branches{/branch}","tags_url":"https://api.github.com/repos/astropy/astroplan/tags","blobs_url":"https://api.github.com/repos/astropy/astroplan/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/astropy/astroplan/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/astropy/astroplan/git/refs{/sha}","trees_url":"https://api.github.com/repos/astropy/astroplan/git/trees{/sha}","statuses_url":"https://api.github.com/repos/astropy/astroplan/statuses/{sha}","languages_url":"https://api.github.com/repos/astropy/astroplan/languages","stargazers_url":"https://api.github.com/repos/astropy/astroplan/stargazers","contributors_url":"https://api.github.com/repos/astropy/astroplan/contributors","subscribers_url":"https://api.github.com/repos/astropy/astroplan/subscribers","subscription_url":"https://api.github.com/repos/astropy/astroplan/subscription","commits_url":"https://api.github.com/repos/astropy/astroplan/commits{/sha}","git_commits_url":"https://api.github.com/repos/astropy/astroplan/git/commits{/sha}","comments_url":"https://api.github.com/repos/astropy/astroplan/comments{/number}","issue_comment_url":"https://api.github.com/repos/astropy/astroplan/issues/comments{/number}","contents_url":"https://api.github.com/repos/astropy/astroplan/contents/{+path}","compare_url":"https://api.github.com/repos/astropy/astroplan/compare/{base}...{head}","merges_url":"https://api.github.com/repos/astropy/astroplan/merges","archive_url":"https://api.github.com/repos/astropy/astroplan/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/astropy/astroplan/downloads","issues_url":"https://api.github.com/repos/astropy/astroplan/issues{/number}","pulls_url":"https://api.github.com/repos/astropy/astroplan/pulls{/number}","milestones_url":"https://api.github.com/repos/astropy/astroplan/milestones{/number}","notifications_url":"https://api.github.com/repos/astropy/astroplan/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/astropy/astroplan/labels{/name}","releases_url":"https://api.github.com/repos/astropy/astroplan/releases{/id}","created_at":"2015-04-27T22:00:00Z","updated_at":"2015-08-25T20:19:34Z","pushed_at":"2015-08-28T00:06:28Z","git_url":"git://github.com/astropy/astroplan.git","ssh_url":"git@github.com:astropy/astroplan.git","clone_url":"https://github.com/astropy/astroplan.git","svn_url":"https://github.com/astropy/astroplan","homepage":"https://astroplan.readthedocs.org/","size":3516,"stargazers_count":9,"watchers_count":9,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":10,"mirror_url":null,"open_issues_count":23,"forks":10,"open_issues":23,"watchers":9,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/61"},"html":{"href":"https://github.com/astropy/astroplan/pull/61"},"issue":{"href":"https://api.github.com/repos/astropy/astroplan/issues/61"},"comments":{"href":"https://api.github.com/repos/astropy/astroplan/issues/61/comments"},"review_comments":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/61/comments"},"review_comment":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/astropy/astroplan/pulls/61/commits"},"statuses":{"href":"https://api.github.com/repos/astropy/astroplan/statuses/58db14fcfdbb855770f9cd2f464c3ce0eddc6bad"}}}}