{"action":"created","comment":{"url":"https://api.github.com/repos/astroplanners/astroplan/pulls/comments/34292389","id":34292389,"diff_hunk":"@@ -162,171 +207,851 @@ def altaz(self, time, target=None, obswl=None):\n             return coordinate.transform_to(altaz_frame)\n \n     # Sun-related methods.\n+    @u.quantity_input(horizon=u.deg)\n+    def _horiz_cross(self, t, alt, rise_set, horizon=0*u.degree):\n+        '''\n+        Find time ``t`` when values in array ``a`` go from\n+        negative to positive or positive to negative (exclude endpoints)\n \n-    def noon(self, time):\n-        \"\"\"\n-        Returns the local, solar noon time.\n+        ``return_limits`` will return nearest times to zero-crossing.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n-        \"\"\"\n-        raise NotImplementedError()\n+        t : `~astropy.time.Time`\n+            Grid of times\n+        alt : `~astropy.units.Quantity`\n+            Grid of altitudes\n+        rise_set : {\"rising\",  \"setting\"}\n+            Calculate either rising or setting across the horizon\n+        horizon : float\n+            Number of degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+        Returns\n+        -------\n+        Returns the lower and upper limits on the time and altitudes\n+        of the horizon crossing.\n+        '''\n+        if rise_set == 'rising':\n+            condition = (alt[:-1] < horizon) * (alt[1:] > horizon)\n+        elif rise_set == 'setting':\n+            condition = (alt[:-1] > horizon) * (alt[1:] < horizon)\n \n-    def midnight(self, time):\n-        \"\"\"\n-        Returns the local, solar midnight time.\n+        if sum(condition) < 1:\n+            raise ValueError('Target does not rise/set with respect to '\n+                             '`horizon` within 24 hours')\n+\n+        # Isolate horizon crossing\n+        nearest_index = np.argwhere(condition)[0][0]\n+\n+        # Capture points on either side of horizon crossing\n+        lower_t, upper_t = t[nearest_index:nearest_index+2]\n+        lower_alt, upper_alt = alt[nearest_index:nearest_index+2]\n+\n+        return (lower_t, upper_t), (lower_alt, upper_alt)\n+\n+    @u.quantity_input(horizon=u.deg)\n+    def _two_point_interp(self, times, altitudes, horizon=0*u.deg):\n+        '''\n+        Do linear interpolation between two ``altitudes`` at\n+        two ``times`` to determine the time where the altitude\n+        goes through zero.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n-        \"\"\"\n-        raise NotImplementedError()\n+        times : `~astropy.time.Time`\n+            Two times for linear interpolation between\n \n-    def sunset(self, time, **kwargs):\n-        \"\"\"\n-        Returns the local sunset time.\n+        altitudes : array of `~astropy.units.Quantity`\n+            Two altitudes for linear interpolation between\n \n-        The default sunset returned is the next one to occur.\n+        horizon : `~astropy.units.Quantity`\n+            Solve for the time when the altitude is equal to\n+            reference_alt.\n+\n+        Returns\n+        -------\n+        t : `~astropy.time.Time`\n+            Time when target crosses the horizon\n+\n+        '''\n+        slope = (altitudes[1] - altitudes[0])/(times[1].jd - times[0].jd)\n+        return Time(times[1].jd - ((altitudes[1] - horizon)/slope).value,\n+                    format='jd')\n+\n+    def _altitude_trig(self, LST, target):\n+        '''\n+        Calculate the altitude of ``target`` at local sidereal times ``LST``.\n+\n+        This method provides a factor of ~3 speed up over calling `altaz`, and\n+        inherently does *not* take the atmosphere into account.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        LST : `~astropy.time.Time`\n+            Local sidereal times (array)\n \n-        Keywords: str, optional\n-            previous\n-            next\n-        \"\"\"\n-        raise NotImplementedError()\n+        target : {`~astropy.coordinates.SkyCoord`, `FixedTarget`} or similar\n+            Target celestial object's coordinates.\n \n-    def sunrise(self, time, **kwargs):\n-        \"\"\"\n-        Returns the local sunrise time.\n+        Returns\n+        -------\n+        alt : `~astropy.unit.Quantity`\n+            Array of altitudes\n+        '''\n+        alt = np.arcsin(np.sin(self.location.latitude.radian) *\n+                        np.sin(target.dec) +\n+                        np.cos(self.location.latitude.radian) *\n+                        np.cos(target.dec) *\n+                        np.cos(LST.radian - target.ra.radian))\n+        return alt\n+\n+    def _calc_riseset(self, time, target, prev_next, rise_set, horizon, N=150):\n+        '''\n+        Time at next rise/set of ``target``.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n+\n+        target : `~astropy.coordinates.SkyCoord`\n+            Position of target or multiple positions of that target\n+            at multiple times (if target moves, like the Sun)\n \n-        The default sunrise returned is the next one to occur.\n+        prev_next : str - either 'previous' or 'next'\n+            Test next rise/set or previous rise/set\n+\n+        rise_set : str - either 'rising' or 'setting'\n+            Compute prev/next rise or prev/next set\n+\n+        location : `~astropy.coordinates.EarthLocation`\n+            Location of observer\n+\n+        horizon : `~astropy.units.Quantity`\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        N : int\n+            Number of altitudes to compute when searching for\n+            rise or set.\n+\n+        Returns\n+        -------\n+        ret1 : `~astropy.time.Time`\n+            Time of rise/set\n+        '''\n+\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if prev_next == 'next':\n+            times = _generate_24hr_grid(time, 0, 1, N)\n+        else:\n+            times = _generate_24hr_grid(time, -1, 0, N)\n+\n+        altitudes = self.altaz(times, target).alt\n+\n+        horizon_crossing_limits = self._horiz_cross(times, altitudes, rise_set,\n+                                                    horizon)\n+        return self._two_point_interp(*horizon_crossing_limits, horizon=horizon)\n+\n+    def _calc_riseset_brentq(self, time, target, prev_next, rise_set, horizon,\n+                             N=150, **kwargs):\n+        '''\n+        Time at next rise/set of ``target`` with `~scipy.optimize.brentq`.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n \n-        Keywords: str, optional\n-            previous\n-            next\n-        \"\"\"\n-        raise NotImplementedError()\n+        target : `~astropy.coordinates.SkyCoord`\n+            Position of target or multiple positions of that target\n+            at multiple times (if target moves, like the Sun)\n \n-    # Moon-related methods.\n+        prev_next : str - either 'previous' or 'next'\n+            Test next rise/set or previous rise/set\n \n-    def moonrise(self, time, **kwargs):\n-        \"\"\"\n-        Returns the local moonrise time.\n+        rise_set : str - either 'rising' or 'setting'\n+            Compute prev/next rise or prev/next set\n \n-        The default moonrise returned is the next one to occur.\n+        location : `~astropy.coordinates.EarthLocation`\n+            Location of observer\n+\n+        horizon : `~astropy.units.Quantity`\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        N : int\n+            Number of altitudes to compute when searching for\n+            rise or set.\n+\n+        Returns\n+        -------\n+        ret1 : `~astropy.time.Time`\n+            Time of rise/set\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if prev_next == 'next':\n+            times = _generate_24hr_grid(time, 0, 1, N)\n+        else:\n+            times = _generate_24hr_grid(time, -1, 0, N)\n+\n+        altitudes = self.altaz(times, target).alt\n+\n+        time_limits, alt_limits = self._horiz_cross(times, altitudes, rise_set,\n+                                                    horizon)\n+\n+        calc_altitude = lambda t: (self.altaz(Time(t, format='jd'),\n+                                              target).alt - horizon).value\n+        from scipy.optimize import brentq\n+        root = brentq(calc_altitude, time_limits[0].jd, time_limits[1].jd,\n+                      **kwargs)\n+        return Time(root, format='jd')\n+\n+    def _calc_transit(self, time, target, prev_next, antitransit=False, N=150):\n+        '''\n+        Time at next transit of the meridian of `target`.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n \n-        Keywords: str, optional\n-            previous\n-            next\n+        target : `~astropy.coordinates.SkyCoord`\n+            Position of target or multiple positions of that target\n+            at multiple times (if target moves, like the Sun)\n+\n+        prev_next : str - either 'previous' or 'next'\n+            Test next rise/set or previous rise/set\n+\n+        antitransit : bool\n+            Toggle compute antitransit (below horizon, equivalent to midnight\n+            for the Sun)\n+\n+        location : `~astropy.coordinates.EarthLocation`\n+            Location of observer\n+\n+        N : int\n+            Number of altitudes to compute when searching for\n+            rise or set.\n+\n+        Returns\n+        -------\n+        ret1 : `~astropy.time.Time`\n+            Time of transit/antitransit\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if prev_next == 'next':\n+            times = _generate_24hr_grid(time, 0, 1, N, for_deriv=True)\n+        else:\n+            times = _generate_24hr_grid(time, -1, 0, N, for_deriv=True)\n+\n+        # The derivative of the altitude with respect to time is increasing\n+        # from negative to positive values at the anti-transit of the meridian\n+        if antitransit:\n+            rise_set = 'rising'\n+        else:\n+            rise_set = 'setting'\n+\n+        altitudes = self.altaz(times, target).alt\n+        dt = Time((times.jd[1:] + times.jd[:-1])/2, format='jd')\n+        d_altitudes = altitudes.diff()\n+\n+        horizon = 0*u.degree # Find when derivative passes through zero\n+        horizon_crossing_limits = self._horiz_cross(dt, d_altitudes, rise_set,\n+                                                    horizon)\n+        return self._two_point_interp(*horizon_crossing_limits, horizon=horizon)\n+\n+    @u.quantity_input(horizon=u.deg)\n+    def calc_rise(self, time, target, which='nearest', horizon=0*u.degree):\n+        '''\n+        Calculate rise time.\n+\n+        Compute time of the next/previous/nearest rise of the ``target``\n+        object, where \"rise\" is defined as the time when the ``target``\n+        transitions from altitudes below the ``horizon`` to above the\n+        ``horizon``.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n+\n+        target : coordinate object (i.e. `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`)\n+            Target celestial object\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunrise relative to the present ``time`` would you\n+            like to calculate\n+\n+        horizon : `~astropy.units.Quantity` (optional), default = zero degrees\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Rise time of target\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if which == 'next' or which == 'nearest':\n+            next_rise = self._calc_riseset(time, target, 'next', 'rising',\n+                                           horizon)\n+            if which == 'next':\n+                return next_rise\n+\n+        if which == 'previous' or which == 'nearest':\n+            previous_rise = self._calc_riseset(time, target, 'previous',\n+                                               'rising', horizon)\n+            if which == 'previous':\n+                return previous_rise\n+\n+        if which == 'nearest':\n+            if abs(time - previous_rise) < abs(time - next_rise):\n+                return previous_rise\n+            else:\n+                return next_rise\n+        raise ValueError('\"which\" kwarg must be \"next\", \"previous\" or '\n+                         '\"nearest\".')\n+\n+    @u.quantity_input(horizon=u.deg)\n+    def calc_set(self, time, target, which='nearest', horizon=0*u.degree):\n+        '''\n+        Calculate set time.\n+\n+        Compute time of the next/previous/nearest set of ``target``, where\n+        \"set\" is defined as when the ``target`` transitions from altitudes\n+        above ``horizon`` to below ``horizon``.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n+\n+        target : coordinate object (i.e. `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`)\n+            Target celestial object\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunset relative to the present ``time`` would you\n+            like to calculate\n+\n+        horizon : `~astropy.units.Quantity` (optional), default = zero degrees\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Set time of target.\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if which == 'next' or which == 'nearest':\n+            next_set = self._calc_riseset(time, target, 'next', 'setting',\n+                                          horizon)\n+            if which == 'next':\n+                return next_set\n+\n+        if which == 'previous' or which == 'nearest':\n+            previous_set = self._calc_riseset(time, target, 'previous',\n+                                              'setting', horizon)\n+            if which == 'previous':\n+                return previous_set\n+\n+        if which == 'nearest':\n+            if abs(time - previous_set) < abs(time - next_set):\n+                return previous_set\n+            else:\n+                return next_set\n+\n+        raise ValueError('\"which\" kwarg must be \"next\", \"previous\" or '\n+                         '\"nearest\".')\n+\n+    def calc_transit(self, time, target, which='nearest'):\n+        '''\n+        Calculate time at the transit of the meridian.\n+\n+        Compute time of the next/previous/nearest transit of the ``target``\n+        object.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object)\n+\n+        target : coordinate object (i.e. `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`)\n+            Target celestial object\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunrise relative to the present ``time`` would you\n+            like to calculate\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Transit time of target\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if which == 'next' or which == 'nearest':\n+            next_transit = self._calc_transit(time, target, 'next')\n+            if which == 'next':\n+                return next_transit\n+\n+        if which == 'previous' or which == 'nearest':\n+            previous_transit = self._calc_transit(time, target, 'previous')\n+            if which == 'previous':\n+                return previous_transit\n+\n+        if which == 'nearest':\n+            if abs(time - previous_transit) < abs(time - next_transit):\n+                return previous_transit\n+            else:\n+                return next_transit\n+        raise ValueError('\"which\" kwarg must be \"next\", \"previous\" or '\n+                         '\"nearest\".')\n+\n+    def calc_antitransit(self, time, target, which='nearest'):\n+        '''\n+        Calculate time at the antitransit of the meridian.\n+\n+        Compute time of the next/previous/nearest antitransit of the ``target``\n+        object.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        target : coordinate object (i.e. `~astropy.coordinates.SkyCoord`, `~astroplan.FixedTarget`)\n+            Target celestial object\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunrise relative to the present ``time`` would you\n+            like to calculate\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Antitransit time of target\n+        '''\n+        if not isinstance(time, Time):\n+            time = Time(time)\n+\n+        if which == 'next' or which == 'nearest':\n+            next_antitransit = self._calc_transit(time, target, 'next',\n+                                                  antitransit=True)\n+            if which == 'next':\n+                return next_antitransit\n+\n+        if which == 'previous' or which == 'nearest':\n+            previous_antitransit = self._calc_transit(time, target, 'previous',\n+                                                      antitransit=True)\n+            if which == 'previous':\n+                return previous_antitransit\n+\n+        if which == 'nearest':\n+            if abs(time - previous_antitransit) < abs(time - next_antitransit):\n+                return previous_antitransit\n+            else:\n+                return next_antitransit\n+        raise ValueError('\"which\" kwarg must be \"next\", \"previous\" or '\n+                         '\"nearest\".')\n+\n+    @u.quantity_input(horizon=u.deg)\n+    def sunrise(self, time, which='nearest', horizon=0*u.degree):\n+        '''\n+        Time of sunrise.\n+\n+        Compute time of the next/previous/nearest sunrise, where\n+        sunrise is defined as when the Sun transitions from altitudes\n+        below ``horizon`` to above ``horizon``.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunrise relative to the present ``time`` would you\n+            like to calculate.\n+\n+        horizon : `~astropy.units.Quantity` (optional), default = zero degrees\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of sunrise\n+        '''\n+        return self.calc_rise(time, get_sun(time), which, horizon)\n+\n+    @u.quantity_input(horizon=u.deg)\n+    def sunset(self, time, which='nearest', horizon=0*u.degree):\n+        '''\n+        Time of sunset.\n+\n+        Compute time of the next/previous/nearest sunset, where\n+        sunset is defined as when the Sun transitions from altitudes\n+        below ``horizon`` to above ``horizon``.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which sunset relative to the present ``time`` would you\n+            like to calculate\n+\n+        horizon : `~astropy.units.Quantity` (optional), default = zero degrees\n+            Degrees above/below actual horizon to use\n+            for calculating rise/set times (i.e.,\n+            -6 deg horizon = civil twilight, etc.)\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of sunset\n+        '''\n+        return self.calc_set(time, get_sun(time), which, horizon)\n+\n+    def noon(self, time, which='nearest'):\n+        '''\n+        Time at solar noon.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which noon relative to the present ``time`` would you\n+            like to calculate\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time at solar noon\n+        '''\n+        return self.calc_transit(time, get_sun(time), which)\n+\n+    def midnight(self, time, which='nearest'):\n+        '''\n+        Time at solar midnight.\n+\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observation. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which noon relative to the present ``time`` would you\n+            like to calculate\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time at solar midnight\n+        '''\n+        return self.calc_antitransit(time, get_sun(time), which)\n+\n+    # Twilight convenience functions\n+\n+    def evening_astronomical(self, time, which='nearest'):\n         \"\"\"\n-        raise NotImplementedError()\n+        Time at evening astronomical (-18 degree) twilight.\n \n-    def moonset(self, time, **kwargs):\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate. Default is nearest.\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of twilight\n         \"\"\"\n-        Returns the local moonset time.\n+        return self.sunset(time, which, horizon=-18*u.degree)\n \n-        The default moonset returned is the next one to occur.\n+    def evening_nautical(self, time, which='nearest'):\n+\n+        \"\"\"\n+        Time at evening nautical (-12 degree) twilight.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n \n-        Keywords: str, optional\n-            previous\n-            next\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate. Default is nearest.\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of twilight\n         \"\"\"\n-        raise NotImplementedError()\n+        return self.sunset(time, which, horizon=-12*u.degree)\n \n-    def moon_illumination(self, time):\n+    def evening_civil(self, time, which='nearest'):\n         \"\"\"\n-        Returns a float giving the percent illumation.\n+        Time at evening civil (-6 degree) twilight.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate. Default is nearest.\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of twilight\n         \"\"\"\n-        raise NotImplementedError()\n+        return self.sunset(time, which, horizon=-6*u.degree)\n \n-    def moon_position(self, time):\n+    def morning_astronomical(self, time, which='nearest'):\n         \"\"\"\n-        Returns the position of the moon in alt/az.\n+        Time at morning astronomical (-18 degree) twilight.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of twilight\n         \"\"\"\n-        raise NotImplementedError()\n+        return self.sunrise(time, which, horizon=-18*u.degree)\n+\n+    def morning_nautical(self, time, which='nearest'):\n+        \"\"\"\n+        Time at morning nautical (-12 degree) twilight.\n \n-    # Other time-related methods.\n+        Parameters\n+        ----------\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate. Default is nearest.\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of twilight\n+        \"\"\"\n+        return self.sunrise(time, which, horizon=-12*u.degree)\n \n-    def evening_nautical(self, time):\n+    def morning_civil(self, time, which='nearest'):\n         \"\"\"\n-        Returns the local evening (nautical) twilight time.\n+        Time at morning civil (-6 degree) twilight.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            Time of observations. This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        which : {'next', 'previous', 'nearest'}\n+            Choose which twilight relative to the present ``time`` would you\n+            like to calculate. Default is nearest.\n+\n+        Returns\n+        -------\n+        `~astropy.time.Time`\n+            Time of sunset\n         \"\"\"\n-        raise NotImplementedError()\n+        return self.sunrise(time, which, horizon=-6*u.degree)\n+\n+    # Moon-related methods.\n \n-    def evening_civil(self, time):\n+    def moonrise(self, time, **kwargs):\n         \"\"\"\n-        Returns the local evening (civil) twilight time.\n+        Returns the local moonrise time.\n+\n+        The default moonrise returned is the next one to occur.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+\n+        Keywords: str, optional\n+            previous\n+            next\n         \"\"\"\n         raise NotImplementedError()\n \n-    def morning_astronomical(self, time):\n+    def moonset(self, time, **kwargs):\n         \"\"\"\n-        Returns the local morning (astronomical) twilight time.\n+        Returns the local moonset time.\n+\n+        The default moonset returned is the next one to occur.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n+\n+        Keywords: str, optional\n+            previous\n+            next\n         \"\"\"\n         raise NotImplementedError()\n \n-    def morning_nautical(self, time):\n+    def moon_illumination(self, time):\n         \"\"\"\n-        Returns the local morning (nautical) twilight time.\n+        Returns a float giving the percent illumation.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n         \"\"\"\n         raise NotImplementedError()\n \n-    def morning_civil(self, time):\n+    def moon_position(self, time):\n         \"\"\"\n-        Returns the local morning (civil) twilight time.\n+        Returns the position of the moon in alt/az.\n \n         Parameters\n         ----------\n-        time : `~astropy.time.Time`\n+        time : `~astropy.time.Time` or other (see below)\n+            This will be passed in as the first argument to\n+            the `~astropy.time.Time` initializer, so it can be anything that\n+            `~astropy.time.Time` will accept (including a `~astropy.time.Time`\n+            object).\n         \"\"\"\n         raise NotImplementedError()\n \n+    @u.quantity_input(horizon=u.deg)\n+    def can_see(self, time, target, horizon=0*u.degree, ret_altaz=False):","path":"astroplan/core.py","position":1048,"original_position":1048,"commit_id":"b86631b5883bdfa1e72bcf631912748e5f2d2c9b","original_commit_id":"b86631b5883bdfa1e72bcf631912748e5f2d2c9b","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":"One reason we really preferred `can_see` is because it's a method on the Observer class, so you could read it as `keck.can_see(Time.now(), vega)`. Otherwise you'd be calling `is_visible` on the observatory.","created_at":"2015-07-09T19:17:03Z","updated_at":"2015-07-09T19:17:03Z","html_url":"https://github.com/astroplanners/astroplan/pull/14#discussion_r34292389","pull_request_url":"https://api.github.com/repos/astroplanners/astroplan/pulls/14","_links":{"self":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/comments/34292389"},"html":{"href":"https://github.com/astroplanners/astroplan/pull/14#discussion_r34292389"},"pull_request":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/14"}}},"pull_request":{"url":"https://api.github.com/repos/astroplanners/astroplan/pulls/14","id":38716508,"html_url":"https://github.com/astroplanners/astroplan/pull/14","diff_url":"https://github.com/astroplanners/astroplan/pull/14.diff","patch_url":"https://github.com/astroplanners/astroplan/pull/14.patch","issue_url":"https://api.github.com/repos/astroplanners/astroplan/issues/14","number":14,"state":"open","locked":false,"title":"Rise/set/transit time calculations and convenience methods","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":"**Before beginning a code review/discussion, I recommend you [check out my notes](https://github.com/bmorris3/astroplan/wiki/Notes#on-risingsetting-methods)** on the organization of the methods that I've implemented in this PR.\r\n\r\nI took some liberties to get things up and running, and am prepared to make plenty of changes as discussion begins. \r\n\r\nHappy Pride weekend! :rainbow: ","created_at":"2015-06-27T22:24:13Z","updated_at":"2015-07-09T19:17:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5aa4c7ce196bdbf978ca597eecaca01a9eca6244","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/astroplanners/astroplan/pulls/14/commits","review_comments_url":"https://api.github.com/repos/astroplanners/astroplan/pulls/14/comments","review_comment_url":"https://api.github.com/repos/astroplanners/astroplan/pulls/comments{/number}","comments_url":"https://api.github.com/repos/astroplanners/astroplan/issues/14/comments","statuses_url":"https://api.github.com/repos/astroplanners/astroplan/statuses/b86631b5883bdfa1e72bcf631912748e5f2d2c9b","head":{"label":"bmorris3:rise_set","ref":"rise_set","sha":"b86631b5883bdfa1e72bcf631912748e5f2d2c9b","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-07-09T15:47:11Z","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":499,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"astroplanners:master","ref":"master","sha":"e0d3f70e79b1e5d9f181b300a316099d1cc62f69","user":{"login":"astroplanners","id":12143843,"avatar_url":"https://avatars.githubusercontent.com/u/12143843?v=3","gravatar_id":"","url":"https://api.github.com/users/astroplanners","html_url":"https://github.com/astroplanners","followers_url":"https://api.github.com/users/astroplanners/followers","following_url":"https://api.github.com/users/astroplanners/following{/other_user}","gists_url":"https://api.github.com/users/astroplanners/gists{/gist_id}","starred_url":"https://api.github.com/users/astroplanners/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astroplanners/subscriptions","organizations_url":"https://api.github.com/users/astroplanners/orgs","repos_url":"https://api.github.com/users/astroplanners/repos","events_url":"https://api.github.com/users/astroplanners/events{/privacy}","received_events_url":"https://api.github.com/users/astroplanners/received_events","type":"Organization","site_admin":false},"repo":{"id":34693515,"name":"astroplan","full_name":"astroplanners/astroplan","owner":{"login":"astroplanners","id":12143843,"avatar_url":"https://avatars.githubusercontent.com/u/12143843?v=3","gravatar_id":"","url":"https://api.github.com/users/astroplanners","html_url":"https://github.com/astroplanners","followers_url":"https://api.github.com/users/astroplanners/followers","following_url":"https://api.github.com/users/astroplanners/following{/other_user}","gists_url":"https://api.github.com/users/astroplanners/gists{/gist_id}","starred_url":"https://api.github.com/users/astroplanners/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/astroplanners/subscriptions","organizations_url":"https://api.github.com/users/astroplanners/orgs","repos_url":"https://api.github.com/users/astroplanners/repos","events_url":"https://api.github.com/users/astroplanners/events{/privacy}","received_events_url":"https://api.github.com/users/astroplanners/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/astroplanners/astroplan","description":"Observation planning package for astronomers","fork":false,"url":"https://api.github.com/repos/astroplanners/astroplan","forks_url":"https://api.github.com/repos/astroplanners/astroplan/forks","keys_url":"https://api.github.com/repos/astroplanners/astroplan/keys{/key_id}","collaborators_url":"https://api.github.com/repos/astroplanners/astroplan/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/astroplanners/astroplan/teams","hooks_url":"https://api.github.com/repos/astroplanners/astroplan/hooks","issue_events_url":"https://api.github.com/repos/astroplanners/astroplan/issues/events{/number}","events_url":"https://api.github.com/repos/astroplanners/astroplan/events","assignees_url":"https://api.github.com/repos/astroplanners/astroplan/assignees{/user}","branches_url":"https://api.github.com/repos/astroplanners/astroplan/branches{/branch}","tags_url":"https://api.github.com/repos/astroplanners/astroplan/tags","blobs_url":"https://api.github.com/repos/astroplanners/astroplan/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/astroplanners/astroplan/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/astroplanners/astroplan/git/refs{/sha}","trees_url":"https://api.github.com/repos/astroplanners/astroplan/git/trees{/sha}","statuses_url":"https://api.github.com/repos/astroplanners/astroplan/statuses/{sha}","languages_url":"https://api.github.com/repos/astroplanners/astroplan/languages","stargazers_url":"https://api.github.com/repos/astroplanners/astroplan/stargazers","contributors_url":"https://api.github.com/repos/astroplanners/astroplan/contributors","subscribers_url":"https://api.github.com/repos/astroplanners/astroplan/subscribers","subscription_url":"https://api.github.com/repos/astroplanners/astroplan/subscription","commits_url":"https://api.github.com/repos/astroplanners/astroplan/commits{/sha}","git_commits_url":"https://api.github.com/repos/astroplanners/astroplan/git/commits{/sha}","comments_url":"https://api.github.com/repos/astroplanners/astroplan/comments{/number}","issue_comment_url":"https://api.github.com/repos/astroplanners/astroplan/issues/comments{/number}","contents_url":"https://api.github.com/repos/astroplanners/astroplan/contents/{+path}","compare_url":"https://api.github.com/repos/astroplanners/astroplan/compare/{base}...{head}","merges_url":"https://api.github.com/repos/astroplanners/astroplan/merges","archive_url":"https://api.github.com/repos/astroplanners/astroplan/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/astroplanners/astroplan/downloads","issues_url":"https://api.github.com/repos/astroplanners/astroplan/issues{/number}","pulls_url":"https://api.github.com/repos/astroplanners/astroplan/pulls{/number}","milestones_url":"https://api.github.com/repos/astroplanners/astroplan/milestones{/number}","notifications_url":"https://api.github.com/repos/astroplanners/astroplan/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/astroplanners/astroplan/labels{/name}","releases_url":"https://api.github.com/repos/astroplanners/astroplan/releases{/id}","created_at":"2015-04-27T22:00:00Z","updated_at":"2015-07-09T05:17:38Z","pushed_at":"2015-07-09T15:48:27Z","git_url":"git://github.com/astroplanners/astroplan.git","ssh_url":"git@github.com:astroplanners/astroplan.git","clone_url":"https://github.com/astroplanners/astroplan.git","svn_url":"https://github.com/astroplanners/astroplan","homepage":"https://astroplan.readthedocs.org/","size":824,"stargazers_count":7,"watchers_count":7,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"open_issues_count":6,"forks":6,"open_issues":6,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/14"},"html":{"href":"https://github.com/astroplanners/astroplan/pull/14"},"issue":{"href":"https://api.github.com/repos/astroplanners/astroplan/issues/14"},"comments":{"href":"https://api.github.com/repos/astroplanners/astroplan/issues/14/comments"},"review_comments":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/14/comments"},"review_comment":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/astroplanners/astroplan/pulls/14/commits"},"statuses":{"href":"https://api.github.com/repos/astroplanners/astroplan/statuses/b86631b5883bdfa1e72bcf631912748e5f2d2c9b"}}}}