def add_obsairmass_df_comp_obs(df_comp_obs, site_dict, df_comps, df_images): observer = Observer(longitude=site_dict['longitude'] * u.deg, latitude=site_dict['latitude'] * u.deg, elevation=site_dict['elevation'] * u.m) df_comp_obs['ObsAirmass'] = None skycoord_dict = { comp_id: SkyCoord(ra=ra_deg * u.deg, dec=dec_deg * u.deg) for (comp_id, ra_deg, dec_deg ) in zip(df_comps.index, df_comps['RA_deg'], df_comps['Dec_deg']) } altaz_frame_dict = { filename: observer.altaz(Time(jd_mid, format='jd')) for (filename, jd_mid) in zip(df_images['FITSfile'], df_images['JD_mid']) } print('ObsAirmasses: ', end='', flush=True) done_count = 0 for obs, filename, comp_id in zip(df_comp_obs.index, df_comp_obs['FITSfile'], df_comp_obs['CompID']): alt = skycoord_dict[comp_id].transform_to( altaz_frame_dict[filename]).alt.value df_comp_obs.loc[obs, 'ObsAirmass'] = 1.0 / sin(alt / DEGREES_PER_RADIAN) done_count += 1 if done_count % 100 == 0: print('.', end='', flush=True) print('\nObsAirmasses written to df_comp_obs:', str(len(df_comp_obs)))
def add_obsairmass_df_mp_obs(df_mp_obs, site_dict, df_images): observer = Observer(longitude=site_dict['longitude'] * u.deg, latitude=site_dict['latitude'] * u.deg, elevation=site_dict['elevation'] * u.m) df_mp_obs['ObsAirmass'] = None skycoord_dict = { filename: SkyCoord(ra=ra_deg * u.deg, dec=dec_deg * u.deg) for (filename, ra_deg, dec_deg) in zip(df_mp_obs['FITSfile'], df_mp_obs['RA_deg_mid'], df_mp_obs['Dec_deg_mid']) } altaz_frame_dict = { filename: observer.altaz(Time(jd_mid, format='jd')) for (filename, jd_mid) in zip(df_images['FITSfile'], df_images['JD_mid']) } for obs, filename in zip(df_mp_obs.index, df_mp_obs['FITSfile']): alt = skycoord_dict[filename].transform_to( altaz_frame_dict[filename]).alt.value df_mp_obs.loc[obs, 'ObsAirmass'] = 1.0 / sin(alt / DEGREES_PER_RADIAN) print('ObsAirmasses written to df_mp_obs:', str(len(df_mp_obs)))
class Station(object): """Defines an astronomical station (antenna). A station is defined by some names and codenames, coordinates, and its sensitivity for the radio bands (wavelengths) it can observe. Apart of the metadata related to the station, it allows to compute the altitude/azimuth, elevation, or simply when a source is visible from the station for a given time range. """ def __init__(self, name: str, codename: str, network: str, location: coord.EarthLocation, freqs_sefds: dict, min_elevation=20 * u.deg, fullname: str = None, all_networks: str = None, country: str = '', diameter: str = '', real_time: bool = False): """Initializes a station. Inputs - name : str Name of the observer (the station that is going to observe). If it contains undercores (_), they will be converted to blank spaces. - codename : str A short code (accronym) for the name of the station. It is meant to follow the standard approach from the EVN: an (often) two-letter code unique for each station. - network : str Name of the network to which the station belongs (e.g. EVN). - location : astropy.coordinates.EarthLocation Position of the station on Earth in (x,y,z) gecentric coordinates. - freqs_sefds : dict Dictionary with all frequencies the station can observe as keys of the dictionary, and the values representing the system equivalent flux density (SEFD; in Jansky units) at each frequency. Although the key format is in principle free, we recommend to use the syntax 'XXcm' (str type). This will be then consistent with the default station catalog. - min_elevation : Quantity [OPTIONAL] Minimum elevation that the station can reach to observe a source. If no units (astropy.units) provided, degrees are assumed. By default it 20 degrees. It does not support an azimuth-dependent elevation limits. It must be >= 0. - fullname : str [OPTIONAL] Full name of the station. If not given, same as `name` is assumed. It can be used to expand the full name if an abbreviation is typically used for the name. For example, name: VLA, fullname: Karl G. Jansky Very Large Array. - all_networks : str [OPTIONAL] Networks where the station can participate (free style string). - country : str [OPTIONAL] Country where the station is located. - diameter : str [OPTIONAL] Diameter of the station (free format string). We recommend a syntax of e.g. '30 m' for normal single-dish antennas, and in case of interferometers it can have a form like '25 x 20 m', meaning that the station is composed of 25 antennas of 20 m each. - real_time : bool [OPTIONAL] If the station can participate in real-time observations (e.g. e-EVN), False by default. """ # Some sanity checks for a_var, a_var_name in zip((name, codename, network, country, diameter), \ ('name', 'codename', 'network', 'country', 'diameter')): assert isinstance(a_var, str), f"'{a_var_name}' must be a str." assert min_elevation >= 0.0, "'min_elevation' must be >= 0." assert isinstance(fullname, str) or fullname is None assert isinstance(all_networks, str) or fullname is None assert type(real_time) is bool, "'real_time' must be a bool." self.observer = Observer(name=name.replace('_', ' '), location=location) self._codename = codename self._network = network self._freqs_sefds = freqs_sefds assert isinstance(min_elevation, float) or isinstance(min_elevation, int) \ or isinstance(min_elevation, u.Quantity), \ "'min_elevation' must be either a float, int, or an astropy.units.Quantity object." if isinstance(min_elevation, float) or isinstance(min_elevation, int): self._min_elev = min_elevation * u.deg else: # isinstance(min_elevation, u.Quantity): assert min_elevation.unit.is_equivalent(u.deg), \ "'min_elevation' must have angular units (e.g. degrees)" self._min_elev = min_elevation self._fullname = name if fullname is None else fullname self._all_networks = network if all_networks is None else all_networks self._country = country self._diameter = diameter self._real_time = real_time @property def name(self) -> str: """Name of the station. """ return self.observer.name @property def codename(self) -> str: """Codename of the station (typically a two-letter accronym). """ return self._codename @property def fullname(self) -> str: """Full name of the station. If not specified, it can be the same as 'name'. """ return self._fullname @property def network(self) -> str: """Name of the network to which the station belongs. """ return self._network @property def all_networks(self) -> str: """Name of all networks to which the station belongs. If not specified it can be the same as 'network'. """ return self._all_networks @property def country(self) -> str: """Country where this station is located. It can be an empty string if not specified. """ return self._country @property def diameter(self) -> str: """String representing the diameter of the station, and/or how many antennas compose the station in case of connected-interferometers. """ return self._diameter @property def real_time(self) -> bool: """If the station can participate in real-time observations (e.g. e-EVN). """ return self._real_time @property def location(self) -> coord.EarthLocation: """Location of the station as an astropy.coordinates.EarthLocation object. """ return self.observer.location @property def bands(self): """Observing bands the station can observe. Returns a dict_keys object with all bands in a string format as introduced in the freqs_sefd attribute when the Station was created. """ return self._freqs_sefds.keys() @property def sefds(self) -> dict: """Returns a dictionary with the system equivalent flux density (SEFDs) for each of the frequencies the station can observe (given as keys). """ return self._freqs_sefds @property def min_elevation(self) -> u.Quantity: """Minimum elevation the station can observe a source. Returns an astropy.units.Quantity (i.e. number with units). """ return self._min_elev def elevation(self, obs_times: Time, target: FixedTarget) -> coord.angles.Latitude: """Returns the elevation of the target source as seen by the Station during obs_times. Inputs - obs_times : astropy.time.Time Time to compute the elevation of the source (either a single timestamp or an array of times). - target : astroplan.FixedTarget Target to observe. Output - elevations : astropy.coordinates.angles.Latitute Elevation of the source at the given obs_times. """ return self.observer.altaz(obs_times, target).alt def altaz(self, obs_times: Time, target: FixedTarget) -> coord.sky_coordinate.SkyCoord: """Returns the altaz coordinates of the target source for the given observing times. Inputs - obs_times : astropy.time.Time Time to compute the elevation of the source (either a single timestamp or an array of times). - target : astroplan.FixedTarget Target coordinates to observe. Output - altaz : astropy.coordinates.sky_coordinate.SkyCoord Altitude and azimuth of the source for each given time. """ return self.observer.altaz(obs_times, target) def is_visible(self, obs_times: Time, target: FixedTarget) -> tuple: """Returns when the target source is visible for this station at the given times. Inputs - obs_times : astropy.time.Time Time to compute the elevation of the source (either a single timestamp or an array of times). - target : astroplan.FixedTarget Target coordinates to observe. If None, the target would be assumed to be visible at all times. Output - visible : tuple Tuple containing the indexes of obs_times when the target source is visible from the station. Therefore obs_times[visible] would return only those times. """ if target is None: return (np.arange(2), ) elevations = self.elevation(obs_times, target) return np.where(elevations >= self.min_elevation) def has_band(self, band: str) -> bool: """Returns if the Station can observed the given band `the_band`. Inputs - band : str A string representing an observing band, following the same syntax as used when the station was initialized and the bands where defined in the keys of the freqs_sefds attribute. Output - bool whenever the station has the given observing band. """ return band in self.bands def sefd(self, band: str) -> float: """Returns the system equivalent flux density (SEFD) of the Station at the given band, in Jansky (Jy) units. Input - band : str A string representing an observing band, following the same syntax as used when the station was initialized and the bands where defined in the keys of the freqs_sefds attribute. Output - SEFD : float The SEFD at the given band, in Jy units. Exception - It may raise KeyError if the given band is not available for this station. """ return self._freqs_sefds[band] def __str__(self): return f"<{self.codename}>" def __repr__(self): return f"<Station: {self.codename}>"
class ATCA_obs: ''' ATCA_obs(targets, tzinfo='Australia/Sydney', portal_htmls=[]) Help with schedule the observation params: -------- targets (FixTarget or list): a single FixTarget or a list of FixTarget Objects tzinfo (str): The timezone of the observer (check `pytz.all_timezones` for all acceptable timezones) portal_htmls (list): If provided, load the ATCA schedule from the webpages. an empty list by default example: -------- import astropy.units as u from astroplan import FixedTarget from astropy.coordinates import SkyCoord from datetime import datetime, timedelta portal_htmls = ['./test/Week1.html', './test/Week2.html'] target = [FixedTarget(name='src1', coord=SkyCoord(233.3333,-66.6666,unit=u.deg)), FixedTarget(name='src2', coord=SkyCoord(66.6666,-23.3333,unit=u.deg))] t = datetime.fromisoformat('2020-10-28') obs = ATCA_obs(target, tzinfo='Australia/Sydney', portal_htmls=portal_htmls) fig, ax = obs.plot_target_altitude_with_schedule(t, duration=timedelta(days=10), dateformat='%D-%H:%M') fig, ax = obs.plot_single_observability_heatmap(t, days=7, target_index=0) attributes: -------- targets: a list of FixedTarget objects need to be observed calibrator: a list of FixedTarget objects observer: an Observer object for ATCA utcoffset: a timedelta object shows the offset between the timezone of the observer and UTC tzinfo: timezone for the observer portal_sched: the formatted schedule from portal_htmls - created from ATCA_sched.schedules portal_utcoffset: a timedelta object shows the offset between the timezone of the ATCA schedule and UTC portal_tz: ATCA portal timezone functions: -------- plot_target_altitude: plot targets altitude vs time plot_target_altitude_with_schedule: plot targets altitude vs time, with green time shaded in green plot_single_observability_heatmap: plot observability for a single source in a heatmap ''' def __init__(self, targets, tzinfo='Australia/Sydney', portal_htmls=[]): ### target and calibrators if not isinstance(targets, Sequence): targets = [targets] self.targets = targets self.calibrator = [FixedTarget(name='1934-638', coord=SkyCoord('19h39m25.026s -63d42m45.63s')), FixedTarget(name='0823-500', coord=SkyCoord('08h25m26.869s -50d10m38.49s'))] ### observer ATCA_loc = (149.5501388*u.deg,-30.3128846*u.deg,237*u.m) location = EarthLocation.from_geodetic(*ATCA_loc) self.observer = Observer(name='ATCA', location=location, timezone=timezone('Australia/Sydney')) time_now = datetime.now(timezone(tzinfo)) self.utcoffset = time_now.utcoffset() self.tzinfo = tzinfo if len(portal_htmls) == 0: self.portal_tz = None self.portal_sched = None else: atca_scheds = ATCA_sched(portal_htmls) self.portal_tz = atca_scheds.timezone self.portal_sched = atca_scheds.schedules ### portal utcoffset portal_now = datetime.now(timezone(self.portal_tz)) self.portal_utcoffset = portal_now.utcoffset() def plot_target_altitude(self, start_time, duration=timedelta(days=1), horizon=12, dateformat='%H:%M', **style_kwargs): ''' plot_target_altitude(start_time, duration=timedelta(days=1), horizon=12, dateformat='%H:%M', **style_kwargs) plot targets altitude vs time params: -------- start_time (datetime.datetime): the time to start the plot duration (datetime.timedelta): the length of the plot, 1 day by default horizon (int or float): telescope horizon in degree, 12 by default dateformat (str): time label formatting, "%H:%M" by default style_kwargs: arguments passed to matplotlib.pyplot.plot_date() returns: -------- fig, ax ''' ### plot style if style_kwargs is None: style_kwargs = {} style_kwargs = dict(style_kwargs) style_kwargs.setdefault('linestyle', '-') style_kwargs.setdefault('linewidth', 2) style_kwargs.setdefault('fmt', '-') ### convert time to series of time time_num = (duration.days + 1)*1000 + 1 time_series = start_time + np.linspace(0,1,time_num)*duration ### covert times to UTC to calculate the alt/az time_utcs = time_series - self.utcoffset fig = plt.figure(figsize=(8*duration.total_seconds()/(24*3600), 6)) ax = fig.add_subplot(111) for target in self.targets: alts = self.observer.altaz(time_utcs, target).alt.value if target.name: ax.plot_date(time_series, alts, label=target.name, **style_kwargs) else: ax.plot_date(time_series, alts, **style_kwargs) for calibrator in self.calibrator: alts = self.observer.altaz(time_utcs, calibrator).alt.value if calibrator.name == '1934-638': ax.plot_date(time_series, alts, label=calibrator.name, fmt='black', lw=3, ls=':') else: ax.plot_date(time_series, alts, label=calibrator.name, fmt='black', lw=1, ls=':') ax.axhline(y=horizon, color='red', ls=':', lw=2) date_formatter = dates.DateFormatter(dateformat) ax.xaxis.set_major_formatter(date_formatter) plt.setp(ax.get_xticklabels(), rotation=30, ha='right') ax.set_xlim(dates.date2num(time_series[0]), dates.date2num(time_series[-1])) ax.set_ylim(0,90) ax.legend() ax.set_xlabel(f'TIME FROM {start_time.year}-{start_time.month}-{start_time.day} [{self.tzinfo}]') return fig, ax def plot_target_altitude_with_schedule(self, start_time, duration=timedelta(days=1), horizon=12, dateformat='%H:%M', **style_kwargs): ''' plot_target_altitude_with_schedule(start_time, duration=timedelta(days=1), horizon=12, dateformat='%H:%M', **style_kwargs) plot targets altitude vs time, with green time shaded in green params: -------- start_time (datetime.datetime): the time to start the plot duration (datetime.timedelta): the length of the plot, 1 day by default horizon (int or float): telescope horizon in degree, 12 by default dateformat (str): time label formatting, "%H:%M" by default style_kwargs: arguments passed to matplotlib.pyplot.plot_date() returns: -------- fig, ax ''' if not self.portal_sched: return self.plot_target_altitude(start_time, duration, horizon, dateformat, **style_kwargs) fig, ax = self.plot_target_altitude(start_time, duration, horizon, dateformat, **style_kwargs) ### convert time to series of time time_num = (duration.days + 1)*1000 + 1 time_series = start_time + np.linspace(0,1,time_num)*duration ### read schedules for obs_day in self.portal_sched: for project_row in self.portal_sched[obs_day]: if project_row[-1]: block_start = datetime.fromisoformat(obs_day) + timedelta(hours=float(project_row[0][0].split(':')[0]), minutes=float(project_row[0][0].split(':')[1])) block_start = block_start + self.utcoffset - self.portal_utcoffset block_end = datetime.fromisoformat(obs_day) + timedelta(hours=float(project_row[0][1].split(':')[0]), minutes=float(project_row[0][1].split(':')[1])) block_end = block_end + self.utcoffset - self.portal_utcoffset ax.axvspan(xmin=dates.date2num(block_start), xmax=dates.date2num(block_end), color='green', alpha=0.1) ax.set_xlim(dates.date2num(time_series[0]), dates.date2num(time_series[-1])) return fig, ax def _greentime_bool(self, time): ''' _greentime_bool(time) check if time is in during the green time slot params: -------- time (datetime.datetime): the time to be checked returns: -------- None if there is no schedule file for that day; True if it is in the green time slot; False if not ''' if not self.portal_sched: return None ### transfer time to portal timezone time = time - self.utcoffset + self.portal_utcoffset time_date = f'{time.year}-{time.month:0>2}-{time.day:0>2}' if time_date not in self.portal_sched: return None for project in self.portal_sched[time_date]: project_start = datetime.fromisoformat(time_date) + timedelta(hours=float(project[0][0].split(':')[0]), minutes=float(project[0][0].split(':')[1])) project_end = datetime.fromisoformat(time_date) + timedelta(hours=float(project[0][1].split(':')[0]), minutes=float(project[0][1].split(':')[1])) if time >= project_start and time < project_end: if project[-1]: return True return False return None def plot_single_observability_heatmap(self, start_time, days=7, target_index=0, horizon=12): ''' plot_single_observability_heatmap(start_time, days=7, target_index=0, horizon=12) plot observability for a single source in a heatmap - we consider two factors: one is green time constrain, the other is altitude constrain params: -------- start_time (datetime.datetime): time to start the plot. MAKE SURE start at 00:00 or there will be a bug days (int): number of days to plot, 7 by default target_index (int): index of the target of interest - specify which target to plot horizon (int or float): telescope horizon in degree, 12 by default returns: -------- fig, ax ''' fig = plt.figure(figsize=(16,2*int(days))) for day in range(days): observability = np.zeros((2,48)) ax = fig.add_subplot(int(days),1,day+1) day_time_start = start_time + timedelta(days=day) for i in range(48): block_time = day_time_start + timedelta(minutes=30) * i block_time_utc = block_time - self.utcoffset target_alt = self.observer.altaz(block_time_utc, self.targets[target_index]).alt.value if target_alt < horizon: observability[0][i] = 0 else: observability[0][i] = 1 is_green_time = self._greentime_bool(block_time) if is_green_time == None: observability[1][i] = np.nan else: observability[1][i] = float(is_green_time) extent = [-0.5, 47.5, -0.5, 1.5] ax.imshow(observability, extent=extent, origin='lower') ax.set_yticks(range(0, 2)) ax.set_xticks(range(0, 48,2)) ax.set_yticklabels(['Altitude Constrain','Greentime Constrain']) ax.set_xticklabels([f"{i:0>2}:00" for i in range(24)]) ax.set_xticks(np.arange(extent[0], extent[1]), minor=True) ax.set_yticks(np.arange(extent[2], extent[3]), minor=True) ax.grid(which='minor', color='w', linestyle='-', linewidth=2) ax.set_xlabel(f'Observability in {day_time_start.year}-{day_time_start.month}-{day_time_start.day} [{self.tzinfo}]') # fig.suptitle(self.targets[target_index].name) # plt.tight_layout() return fig, ax
from astropy.time import Time from astroplan import Observer #from astroplan import FixedTarget #Mars = FixedTarget.from_name("mars") #observer-specific coordinates mitlat=42.3601*u.degree mitlong=-71.0942*u.degree radome_elevation=100*u.m #roughly 100m above sea level time = Time.now() #print time #time =Time('2018-08-22 15:31:06') #define an observer and an altaz frame radome_observer=Observer(latitude=mitlat,longitude=mitlong, elevation=radome_elevation, name='radome', timezone='US/Eastern') altaz_frame=radome_observer.altaz(time) icrs_frame=ICRS() galactic_frame=Galactic() #print radome_observer.sun_altaz(time) def update_altaz(): time=Time.now() altaz_frame=radome_observer.altaz(time) return altaz_frame def get_time(): time=Time.now() return time
def get_altaz(obj_name, ipt_lon, ipt_lat, t=None): # for html scrapping # from lxml import html # from bs4 import BeautifulSoup # to place requests import requests import json import astropy.units as u from astropy.time import Time from astropy.coordinates import SkyCoord, EarthLocation, Angle, Latitude, Longitude from astroplan import FixedTarget, Observer from astroquery.simbad import Simbad as simbad import ephem if t == None: t = Time.now() ## Set up the observer obs_el = 100 * u.m loc = EarthLocation.from_geodetic(ipt_lon, ipt_lat, obs_el) my_site = Observer(name="My_Site", location=loc) obs_lat = my_site.location.latitude obs_lon = my_site.location.longitude # observer for pyephem ephem_site = ephem.Observer() ephem_site.lon, ephem_site.lat = str(obs_lon.deg), str(obs_lat.deg) ephem_site.date = ephem.Date(str(t.decimalyear)) ##Get the object # Check for planet-hood. # if planet: resolve the individual planet with pyephem. # else if satellite or ISS (or TIANGONG) scrap the appropriate websites and return info # else query simbad ############ # Put in an auto-correct for kids ############ # just make it lower case for now obj_name = obj_name.lower() if obj_name in ["sun", "mercury", "venus", "moon", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"]: if obj_name == "sun": my_planet = ephem.Sun() elif obj_name == "mercury": my_planet = ephem.Mercury() elif obj_name == "venus": my_planet = ephem.Venus() elif obj_name == "moon": my_planet = ephem.Moon() elif obj_name == "mars": my_planet = ephem.Mars() elif obj_name == "jupiter": my_planet = ephem.Jupiter() elif obj_name == "saturn": my_planet = ephem.Saturn() elif obj_name == "uranus": my_planet = ephem.Uranus() elif obj_name == "neptune": my_planet = ephem.Neptune() elif obj_name == "pluto": my_planet = ephem.Pluto() my_planet.compute(ephem_site) az = my_planet.az * 180 / 3.1415926535 alt = my_planet.alt * 180 / 3.1415926535 # here coded for just ISS but for all satellites we should have similar setups, probably poll site elif obj_name == "iss": # try a request for the iss from the open notify site. Gives current json data page = requests.get("http://api.open-notify.org/iss-now.json") issdata = page.json() tstamp = issdata["timestamp"] isslat = issdata["iss_position"]["latitude"] isslon = issdata["iss_position"]["longitude"] # there are issues with just this amount of data as you do not know the altitude of the object # here we fix it to 350 km issheight = 350 * u.km isslat = Latitude(isslat, unit=u.deg) isslon = Longitude(isslon, unit=u.deg) # there are issues however as this data does NOT contain the altitude so lets try scrapping the html # the issue with fullissdata is that it contains information in NASA style units (M50 Cartesian & M50 Keplerian) page = requests.get( "http://spaceflight.nasa.gov/realdata/sightings/SSapplications/Post/JavaSSOP/orbit/ISS/SVPOST.html" ) # fullissdata=html.fromstring(page.text) # there are also other satellites liseted in, issue is parsing the information as I do not know what each field contains # the issue here is that all sat data contains unknown units and uncertain which entries contain useful information page = requests.get("http://www.celestrak.com/NORAD/elements/stations.txt") allsatdata = page.text c = SkyCoord(isslon, isslat, issheight) my_target = FixedTarget(name="ISS", coord=c) az = my_site.altaz(t, my_target).az.deg alt = my_site.altaz(t, my_target).alt.deg else: try: q = simbad.query_object(obj_name) c = SkyCoord(q["RA"][0], q["DEC"][0], unit=(u.hourangle, u.deg)) my_star = FixedTarget(name="my_star", coord=c) az = my_site.altaz(t, my_star).az.deg alt = my_site.altaz(t, my_star).alt.deg except: print("Couldn't find Object in Database") alt, az = 0, 0 return alt, az
# Define a target tgt = FixedTarget(name='S5', ra='14:20:00.00', dec='48:00:00.00') # Combine a list of constraints to run on Observer, FixedTarget, and time to # determine the observability of target constraints = is_observable(constraint_list, obs, tgt, time_obs) # Test only a single constraint: constraints = is_observable(AboveAirmass(1.2), obs, tgt, time_obs) # `constraints` will be a boolean where True=observable. For a list of # targets, observatories, or times, `constraints` may be a booleans array # We will eventually need a more complicated method that minimizes a cost # function when optimizing an observing schedule given the results of # `is_observable`. # ====================================================== # Other useful calculations wrt an observer and a target #======================================================= # calculate the distance in alt and az degrees between two targets at # the given time (e.g. to calculate slew time) sf = FixedTarget(name='Sf', ra='09:40:00.00', dec='43:00:00.00') sm = FixedTarget(name='Sm', ra='10:30:00.00', dec='36:00:00.00') # Coordinate arithmetic gives separations in RA, Dec, alt, az dra, ddec = sf.ra - sm.ra, sf.dec - sm.dec dalt = obs.altaz(sf, time_obs).alt - obs.altaz(sm, time_obs).alt dazt = obs.altaz(sf, time_obs).az - obs.altaz(sm, time_obs).az
def createPicture(name, times): rcParams['font.size'] = 13 rcParams['lines.linewidth'] = 3 rcParams['lines.markersize'] = 0 rcParams['grid.linestyle'] = '--' rcParams['axes.titlepad'] = 13 rcParams['xtick.direction'] = 'in' rcParams['ytick.direction'] = 'in' rcParams['xtick.top'] = True rcParams['ytick.right'] = True rcParams['font.family'] = 'serif' rcParams['mathtext.fontset'] = 'dejavuserif' rc('legend', fontsize=13) rc('xtick.major', size=5, width=1.5) rc('ytick.major', size=5, width=1.5) rc('xtick.minor', size=3, width=1) rc('ytick.minor', size=3, width=1) star_name = name star_style = {'linestyle': '--', 'linewidth': 4, 'color': 'tomato'} star = FixedTarget.from_name(star_name) longitude_kgo = 42.6675 * u.deg latitude_kgo = 43.73611 * u.deg elevation_kgo = 2112 * u.m kgo = Observer(longitude=longitude_kgo, latitude=latitude_kgo, elevation=elevation_kgo, name="KGO", timezone="Europe/Moscow") # start_time = Time('2020-01-01 '+ times) # end_time = Time('2020-02-01 '+ times) # delta_t = end_time - start_time # observe_time = start_time + delta_t*np.linspace(0, 1, 32) observe_time = Time(times) # sunset_tonight = kgo.sun_set_time(observe_time, which="nearest") # sunrise_tonight = kgo.sun_rise_time(observe_time, which="nearest") # star_rise = list(map(lambda observe_time : kgo.target_rise_time(observe_time, star) + 5*u.minute, observe_time)) # star_set = list(map(lambda observe_time: kgo.target_set_time(observe_time, star) + 5*u.minute, observe_time)) # sunset_tonight = list(map(lambda observe_time: kgo.sun_set_time(observe_time, which="nearest"), observe_time)) # sunrise_tonight = list(map(lambda observe_time: kgo.sun_rise_time(observe_time, which="nearest"), observe_time)) visible_time = observe_time + np.linspace(-10, +10, 25) * u.hour #visible_time = start + (end - start)*np.linspace(0, 1, 25) stars_alts = kgo.altaz(visible_time, star).alt sun_alts = kgo.sun_altaz(visible_time).alt moon_coord = kgo.moon_altaz(visible_time) star_coord = kgo.altaz(visible_time, star) angle = moon_coord.separation(star_coord) moon_star = angle.deg #print(stars_alts) #t = Time(visible_time, format='iso', scale='utc') # start = Time(list(map(lambda x,y: np.max([x, y]), sunset_tonight, star_rise))) # end = Time(list(map(lambda x,y: np.min([x,y]), sunrise_tonight, star_set))) #visible_time = (end-start) #time_final = abs(visible_time.value*24) locator = mdates.MonthLocator() fmt = mdates.DateFormatter('%b') canvas = FigureCanvasAgg(plt.figure(1)) plt.figure(figsize=(8, 7)) plt.subplot(211) plt.plot_date(visible_time.plot_date, stars_alts, linestyle='-.', color='mediumslateblue', label=star_name) plt.plot_date(visible_time.plot_date, sun_alts, linestyle='-.', color='gold', label='Sun') plt.ylim(0, np.max([stars_alts, sun_alts]) + 5) plt.ylabel('Altitude, degrees') plt.legend(shadow=True, loc="best") plt.gcf().autofmt_xdate() plt.grid() plt.subplot(212) plt.plot_date(visible_time.plot_date, moon_star, linestyle='-', color='slategrey', label='star_name' + '\n' + times) plt.ylabel('Moon-Star angle, degrees') plt.gcf().autofmt_xdate() plt.grid() data = BytesIO() plt.savefig(data, format='png') return data.getvalue()
# Combine a list of constraints to run on Observer, FixedTarget, and time to # determine the observability of target constraints = is_observable(constraint_list, obs, t1, time_obs) # Test only a single constraint: constraints = is_observable(AirmassRange(1.2), obs, t1, time_obs) # AirmassRange can accept two bounding airmasses, assumes single argument is # an upper limit, lower limit = 1. # `constraints` will be a boolean where True=observable. For a list of # targets, observatories, or times, `constraints` may be a booleans array # We will eventually need a more complicated method that minimizes a cost # function when optimizing an observing schedule given the results of # `is_observable`. # ====================================================== # Other useful calculations wrt an observer and a target #======================================================= # calculate the distance in alt and az degrees between two targets at # the given time (e.g. to calculate slew time) sf = FixedTarget(SkyCoord('09d40m00.00s', '43d00m00.00s'), name='Sf') sm = FixedTarget(SkyCoord('10d30m00.00s', '36d00m00.00s'), name='Sm') # Coordinate arithmetic gives separations in RA, Dec, alt, az dra, ddec = sf.ra - sm.ra, sf.dec - sm.dec dalt = obs.altaz(time_obs, sf).alt - obs.altaz(time_obs, sm).alt dazt = obs.altaz(time_obs, sf).az - obs.altaz(time_obs, sm).az
g = GoogleV3() t_start = datetime(2017, 12, 10, 21, 38) #in UTC t_end = datetime(2017, 12, 10, 22, 8) cities = pd.read_csv('simplemaps-worldcities-basic.csv') cities = cities[cities['pop'] > 1000000].reset_index() cities['name'] = cities['city_ascii'] + ', ' + cities['iso3'] alts = [] azs = [] messages = [] print('Calculating') for row in tqdm(cities.itertuples(), total=len(cities)): lat, lon = row.lat, row.lng obs = Observer(location=EarthLocation.from_geodetic(lon*u.deg, lat*u.deg)) altaz = obs.altaz(t_start, Kepler) alt = altaz.alt.deg az = altaz.az.deg direc = az_to_coord(az) tz = g.timezone((lat, lon)) t0 = tz.fromutc(t_start) t1 = tz.fromutc(t_end) alts.append(alt) azs.append(az) if alt < 0.: message = 'Kepler will be to the %s,'%(direc) + '<p>' +'%.0f degrees below the horizon'%(-alt) else: message = 'Kepler will be to the %s,'%(direc) + '<p>' + '%.0f degrees above the horizon'%(alt) message += '<br>The image will be taken between %02d:%02d and %02d:%02d local time on December %dth'%(t0.hour, t0.minute, t1.hour, t1.minute, t1.day) message += '<br>We hope you\'ll join us in waving to Kepler!' messages.append([message])
class Station(object): def __init__(self, name, codename, network, location, freqs_sefds, min_elevation=20 * u.deg, fullname=None, all_networks=None, country='', diameter=''): """Initializes a station. The given name must be the name of the station that observes, with the typical 2-letter format used in the EVN (with exceptions). Inputs - name : str Name of the observer (the station that is going to observe). - codename : str A code for the name of the station. It can be the same as name. - network : str Name of the network to which the station belongs. - location : EarthLocation Position of the observer on Earth. - freqs_sefds : dict Dictionary with all frequencies the station can observe, and as values the SEFD at each frequency. - min_elevation : Quantity Minimum elevation that the station can observe a source. If no units provided, degrees are assumed. By default it 20 degrees. - fullname : str [OPTIONAL] Full name of the station. If not given, `name` is assumed. - all_networks : str [OPTIONAL] Networks where the station can participate (free style). - country : str [OPTIONAL] Country where the station is placed. """ self.observer = Observer(name=name.replace('_', ' '), location=location) self._codename = codename self._network = network self._freqs_sefds = freqs_sefds if (type(min_elevation) is float) or (type(min_elevation) is int): self._min_elev = min_elevation * u.deg else: self._min_elev = min_elevation if fullname is None: self._fullname = name else: self._fullname = fullname if all_networks is None: self._all_networks = network else: self._all_networks = all_networks self._country = country self._diameter = diameter @property def name(self): """Name of the station. """ return self.observer.name @property def codename(self): """Codename of the station (typically a two-letter accronym). """ return self._codename @property def fullname(self): return self._fullname @property def network(self): """Name of the network to which the station belongs. """ return self._network @property def all_networks(self): """Name of all networks to which the station belongs. """ return self._all_networks @property def country(self): return self._country @property def diameter(self): return self._diameter @property def location(self): """Location of the station in EarthLocation type. """ return self.observer.location @property def bands(self): """Observing bands the station can observe. """ return self._freqs_sefds.keys() @property def sefds(self): """Returns a dictionary with the SEFDs for each of the frequencies the station can observe (given as keys). """ return self._freqs_sefds @property def min_elevation(self): """Minimum elevation the station can observe a source. """ return self._min_elev def elevation(self, obs_times, target): """Returns the elevation of the source as seen by the Station during obs_times. Inputs - obs_times : astropy.time.Time Time to compute the elevation of the source (either single time or a list of times). - target : astroplan.FixedTarget Target to observe. Output - elevations : ndarray Elevation of the source at the given obs_times """ # source_altaz = source_coord.transform_to(coord.AltAz(obstime=obs_times, # location=self.location)) # return source_altaz.alt return self.observer.altaz(obs_times, target).alt def altaz(self, obs_times, target): """Returns the altaz coordinates of the target for the given observing times. """ return self.observer.altaz(obs_times, target) def is_visible(self, obs_times, target): """Return if the source is visible for this station at the given times. """ elevations = self.elevation(obs_times, target) return np.where(elevations >= self.min_elevation) def has_band(self, band): """Returns if the Station can observed the given band `the_band`. """ return band in self.bands def sefd(self, band): """Returns the SEFD of the Station at the given band. """ return self._freqs_sefds[band] def __str__(self): return f"<{self.codename}>" def __repr__(self): return f"<stations.Station: {self.codename}>"
for i in (l): moon_locs.append(SkyCoord(radec2[i][0], radec2[i][1], frame='icrs')) #Get seperations in degrees sep = [] for i in moon_locs: sep.append(target_coord.separation(i).deg) #making them numpy arrays sep = np.array(sep) #sep ################################################################################################## #Airmasses at observatory 1 airmass_obs1 = obs1.altaz(dt, target).secz masked_airmass_obs1 = np.ma.array(airmass_obs1, mask=airmass_obs1 < 1) #Airmasses at observatory 2 airmass_obs2 = obs2.altaz(dt, target).secz masked_airmass_obs2 = np.ma.array(airmass_obs2, mask=airmass_obs2 < 1) #Creating a dictionary for the dataframe dk = { 'datetimes': dt, 'obs1_airmass': masked_airmass_obs1, 'obs2_airmass': masked_airmass_obs2 } #dk
class Simulation(object): """A class to encapsulate an SDSS-5 simulation """ def __init__(self, plan, observatory, idx=1, schedule="normal", redo_exp=True): if (observatory == 'apo'): timezone = "US/Mountain" fclear = 0.5 elev = 2788 self.telescope = { "alt": 30, "az": 90, "par_angle": 0, "alt_slew": 1.5, "az_slew": 2.0, "rot_slew": 2.0 } self.obsCheck = apoCheck self.moveTelescope = self.moveSloanTelescope if (observatory == 'lco'): timezone = "US/Eastern" fclear = 0.7 elev = 2134 self.telescope = {"ra": 0, "dec": -30} self.obsCheck = lcoCheck self.moveTelescope = self.moveDuPontTelescope self.redo_exp = redo_exp self.obsHist = { "lst": list(), "ra": list(), "bright": list(), "field_pk": list(), "weather": list(), "mjd": list() } self.scheduler = roboscheduler.scheduler.Scheduler( observatory=observatory, schedule=schedule) self.weather = observesim.weather.Weather( mjd_start=self.scheduler.start, mjd_end=self.scheduler.end, seed=idx, fclear=fclear) self.observatory = Observer(longitude=self.scheduler.longitude * u.deg, latitude=self.scheduler.latitude * u.deg, elevation=elev * u.m, name=observatory, timezone=timezone) self.scheduler.initdb(designbase=plan) self.field_ra = self.scheduler.fields.racen self.field_dec = self.scheduler.fields.deccen self.field_pk = self.scheduler.fields.pk cadencelist = self.scheduler.fields.cadencelist.cadences cadences = self.scheduler.fields.cadence self.nom_duration = np.float32(15. / 60. / 24.) self.cals = np.float32(3. / 60. / 24.) self.observe = observesim.observe.Observe(defaultExp=self.nom_duration, cadencelist=cadencelist, cadences=cadences) self.bossReadout = np.float32(70. / 60. / 60. / 24.) self.curr_mjd = np.float32(1e9) self.coord = SkyCoord(self.field_ra * u.deg, self.field_dec * u.deg) self.slews = list() self.slew_mjds = list() self.slew_alt = list() self.slew_az = list() self.slew_rot = list() self.slew_ra = list() self.slew_dec = list() self.hit_lims = 0 self.redo_apg = 0 self.redo_r = 0 self.redo_b = 0 def moveDuPontTelescope(self, mjd, fieldidx): next_ra, next_dec = self.field_ra[fieldidx], self.field_dec[fieldidx] ra_slew = np.abs(next_ra - self.telescope["ra"]) dec_slew = np.abs(next_dec - self.telescope["dec"]) if ra_slew > 180: ra_slew = 360 - ra_slew assert ra_slew > 0, "forgot circular math? ra" dec_time = decTime(dec_slew) ra_time = raTime(ra_slew) self.telescope["ra"] = self.field_ra[fieldidx] self.telescope["dec"] = self.field_dec[fieldidx] return max([dec_time, ra_time]), ra_slew, dec_slew def moveSloanTelescope(self, mjd, fieldidx): altaz = self.observatory.altaz(Time(mjd, format="mjd"), self.coord[fieldidx]) alt = altaz.alt.deg az = altaz.az.deg angle = self.observatory.parallactic_angle(Time(mjd, format="mjd"), self.coord[fieldidx]).deg alt_slew = np.abs(alt - self.telescope["alt"]) az_slew = np.abs(az - self.telescope["az"]) if az_slew > 180: az_slew = 360 - az_slew assert az_slew > 0, "forgot circular math? az" rot_slew = np.abs(angle - self.telescope["par_angle"]) if rot_slew > 180: rot_slew = 360 - rot_slew assert rot_slew > 0, "forgot circular math? rot" alt_time = alt_slew / self.telescope["alt_slew"] az_time = az_slew / self.telescope["az_slew"] rot_time = rot_slew / self.telescope["rot_slew"] self.telescope["alt"] = alt self.telescope["az"] = az self.telescope["par_angle"] = angle return max([alt_time, az_time, rot_time]), alt_slew, az_slew, rot_slew def siteObs(self, fieldidx, mjd): """Check observability issues at site, e.g. zenith at APO or enclosure, etc for any number of mjds, e.g. for a whole observing window """ try: len(mjd) except TypeError: mjd = np.array([mjd]) try: len(fieldidx) except TypeError: fieldidx = np.array([fieldidx]) altaz = self.observatory.altaz(Time(mjd, format="mjd"), self.coord[fieldidx], grid_times_targets=True) # altaz shape = (fields x mjds) alt = altaz.alt.deg.flatten() az = altaz.az.deg.flatten() res = self.obsCheck(alt, az) good = res.reshape((len(fieldidx), len(mjd))) # axis 1 is along fields, I guess... return np.all(good, axis=1) def bright(self, mjd=None): if mjd is None: mjd = self.curr_mjd skybrightness = self.scheduler.skybrightness(mjd) return skybrightness > 0.35 def nextField(self): # dark time or brighttime? to guess at how long we need for obs if not self.bright(): airmass_weight = 1.05 else: airmass_weight = 0.05 # integer division floors; no partial exposures maxExp = int((self.nextchange - self.curr_mjd) // (self.nom_duration * 1.3**airmass_weight)) if maxExp == 0: # self.curr_mjd = self.curr_mjd + self.nom_duration return -1, 1, True field_pk, nexposures = self.scheduler.nextfield(mjd=self.curr_mjd, maxExp=maxExp) # assert fieldid is not None, f"can't schedule {self.curr_mjd}, {self.bright()}" if (field_pk is not None): fieldidx = np.where(self.field_pk == field_pk)[0] site_check = self.siteObs(fieldidx, [ self.curr_mjd + n * (self.nom_duration) for n in range(nexposures) ]) # maxTime = self.nextchange - self.curr_mjd maxTime = maxExp * self.nom_duration if not site_check: field_idxs, nexps = self.scheduler.nextfield(mjd=self.curr_mjd, maxExp=maxExp, returnAll=True) obs_fields = self.siteObs(field_idxs, [ self.curr_mjd + n * (self.nom_duration) for n in range(nexposures) ]) field_idxs = field_idxs[obs_fields] nexps = nexps[obs_fields] if len(field_idxs) == 0: # print("all fields collide with something :( ") # print(obs_fields) self.hit_lims += 1. / 20 return -1, 1. / 20, False fieldidx, nexposures = sortFields(field_idxs, nexps, self.nom_duration, maxTime=maxTime) if fieldidx == -1: # print("baawaaaaaahhhahahaa :( ") # self.curr_mjd = self.curr_mjd + self.nom_duration/20 return -1, 1. / 20, False field_pk = int(self.field_pk[fieldidx]) return field_pk, nexposures, False else: # if not self.bright(): # assert False, f"{self.curr_mjd} ugh" return -1, 1, False def bookKeeping(self, fieldidx, i=-1): """figure out SN and keep track, etc """ alt, az = self.scheduler.radec2altaz(mjd=self.curr_mjd, ra=self.field_ra[fieldidx], dec=self.field_dec[fieldidx]) airmass = 1 / np.cos(np.pi * (90 - alt) / 180.) if alt < 20: print(i, alt, az, self.curr_mjd, fieldidx, "TOO LOW!!") if alt < 0: print("booooooooo") # assert False, "ugh" result = self.observe.result( mjd=self.curr_mjd, field_pk=self.field_pk[fieldidx], airmass=airmass, epochidx=self.scheduler.fields.icadence[fieldidx]) duration = result["duration"] if duration < 0 or np.isnan(duration): print("HOOOWWWOWOWOWOWW") print(i, alt, az, self.curr_mjd, field_pk) self.curr_mjd = self.curr_mjd + duration + self.bossReadout # move telescope for tracking self.moveTelescope(self.curr_mjd, fieldidx) self.obsHist["lst"].append(self.scheduler.lst(self.curr_mjd)[0]) self.obsHist["ra"].append(self.field_ra[fieldidx]) self.obsHist["bright"].append(self.bright()) self.obsHist["field_pk"].append(self.field_pk[fieldidx]) self.obsHist["weather"].append(False) self.obsHist["mjd"].append(self.curr_mjd) return result def observeField(self, field_pk, nexposures): fieldidx = int(np.where(self.field_pk == field_pk)[0]) slewtime, *axes = self.moveTelescope(self.curr_mjd, fieldidx) self.slews.append(int(slewtime)) self.slew_mjds.append(int(self.curr_mjd)) if len(axes) == 3: self.slew_alt.append(float(axes[0])) self.slew_az.append(float(axes[1])) self.slew_rot.append(float(axes[2])) self.slew_ra.append(np.nan) self.slew_dec.append(np.nan) else: self.slew_ra.append(float(axes[0])) self.slew_dec.append(float(axes[1])) self.slew_alt.append(np.nan) self.slew_az.append(np.nan) self.slew_rot.append(np.nan) # slewtime is in seconds... self.curr_mjd = self.curr_mjd + self.cals + np.float32( slewtime / 60. / 60. / 24.) field_exp_count = nexposures for i in range(nexposures): # each "exposure" is a design if (self.curr_mjd > self.nextchange): oops = (self.curr_mjd - self.nextchange) * 24 * 60 if oops > 5: print("NOOOO! BAD!", oops) # print(i, nexposures, self.telescope) continue res = self.bookKeeping(fieldidx, i=i) if self.bright(): if res["apgSN2"] < 100 and self.redo_exp: field_exp_count += 1 self.redo_apg += 1 self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=False) res = self.bookKeeping(fieldidx, i=i) self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) else: self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) else: if (res["rSN2"] < 0.2 or res["bSN2"] < 0.2) and self.redo_exp: field_exp_count += 1 if res["rSN2"] < 0.2: self.redo_r += 1 else: self.redo_b += 1 self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=False) res = self.bookKeeping(fieldidx, i=i) self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) else: self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) if self.bright(): ap_tot = np.sum( self.scheduler.observations.apgSN2[-1 * field_exp_count:]) # print(f"{nexposures} {field_exp_count} {len(self.scheduler.observations.apgSN2[-1*field_exp_count:])}") # print(f"AP SN {ap_tot:7.1f} VS {300 * nexposures}") if ap_tot < 650 * nexposures and self.redo_exp: self.redo_apg += 1 self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=False) res = self.bookKeeping(fieldidx, i=i) self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) else: r_tot = np.sum(self.scheduler.observations.rSN2[-1 * field_exp_count:]) b_tot = np.sum(self.scheduler.observations.bSN2[-1 * field_exp_count:]) # print(f"{nexposures} {field_exp_count} {len(self.scheduler.observations.bSN2[-1*field_exp_count:])}") # print(f"B SN {b_tot:7.1f} VS {2.5 * nexposures} \nR SN {r_tot:7.1f} VS {5 * nexposures}") if (b_tot < 1.4 * nexposures or r_tot < 3.4 * nexposures) and self.redo_exp: if r_tot < 3.4 * nexposures: self.redo_r += 1 else: self.redo_b += 1 self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=False) res = self.bookKeeping(fieldidx, i=i) self.scheduler.update(field_pk=self.field_pk[fieldidx], result=res, finish=True) def observeMJD(self, mjd): mjd_evening_twilight = self.scheduler.evening_twilight(mjd) mjd_morning_twilight = self.scheduler.morning_twilight(mjd) self.curr_mjd = mjd_evening_twilight # int_mjd = int(self.curr_mjd) if mjd % 100 == 0: print("!!!!", mjd) # guesses = np.arange(0, 1, 0.05) self.nextchange = mjd_morning_twilight while (self.curr_mjd < mjd_morning_twilight and self.curr_mjd < self.scheduler.end_mjd()): # should we do this now? isclear, nextchange_weather = self.weather.clear(mjd=self.curr_mjd) onoff, nextchange_on = self.scheduler.on(mjd=self.curr_mjd) nextchange = np.min( np.array( [nextchange_weather, nextchange_on, mjd_morning_twilight])) if not isclear: # count = 0 # dur = float(nextchange - self.curr_mjd) while self.curr_mjd < nextchange: if nextchange - self.curr_mjd < self.nom_duration: self.curr_mjd = nextchange continue self.obsHist["lst"].append( self.scheduler.lst(self.curr_mjd)[0]) self.obsHist["ra"].append(-1) self.obsHist["bright"].append(self.bright()) self.obsHist["field_pk"].append(-1) self.obsHist["weather"].append(True) self.obsHist["mjd"].append(self.curr_mjd) self.curr_mjd += self.nom_duration + self.bossReadout + self.cals # count += 1 # print("WEATHER ", self.curr_mjd, f"night {night_len*24:.1f}, weather {dur*24:.1f}", count) elif (onoff != 'on'): self.curr_mjd = nextchange if self.nextchange - self.curr_mjd < self.nom_duration: self.curr_mjd = self.nextchange continue field_pk, nexposures, noTime = self.nextField() if field_pk == -1: if noTime: self.curr_mjd = self.curr_mjd + self.nom_duration continue # raise Exception() # print("skipped ", self.curr_mjd) self.obsHist["lst"].append( self.scheduler.lst(self.curr_mjd)[0]) self.obsHist["ra"].append(np.nan) self.obsHist["bright"].append(self.bright()) self.obsHist["field_pk"].append(-1) self.obsHist["weather"].append(False) self.obsHist["mjd"].append(self.curr_mjd) self.curr_mjd = self.curr_mjd + self.nom_duration continue self.observeField(field_pk, nexposures) # if mjd % 10 == 0: # self.scheduler.priorityLogger.write(name=str(mjd) + "-" + self.observatory.name) def lstToArray(self): assert len(self.obsHist["weather"]) == len( self.obsHist["lst"]), "lst tracking bad!" dtype = [('lst', np.float64), ('ra', np.float64), ('bright', np.bool_), ('field_pk', np.int32), ('weather', np.bool_), ('mjd', np.float64)] lstOut = np.zeros(len(self.obsHist["lst"]), dtype=dtype) lstOut["lst"] = np.array(self.obsHist["lst"]) lstOut["ra"] = np.array(self.obsHist["ra"]) lstOut["bright"] = np.array(self.obsHist["bright"]) lstOut["field_pk"] = np.array(self.obsHist["field_pk"]) lstOut["weather"] = np.array(self.obsHist["weather"]) lstOut["mjd"] = np.array(self.obsHist["mjd"]) return (lstOut) def slewsToArray(self): dtype = [('time', np.int32), ('mjd', np.int32), ('alt', np.float64), ('az', np.float64), ('rot', np.float64), ('ra', np.float64), ('dec', np.float64)] arrayOut = np.zeros(len(self.slews), dtype=dtype) arrayOut["time"] = np.array(self.slews) arrayOut["mjd"] = np.array(self.slew_mjds) arrayOut["alt"] = np.array(self.slew_alt) arrayOut["az"] = np.array(self.slew_az) arrayOut["rot"] = np.array(self.slew_rot) arrayOut["ra"] = np.array(self.slew_ra) arrayOut["dec"] = np.array(self.slew_dec) return (arrayOut)
def get_24hr_airmass(target, interval, airmass_limit): plot_data = [] start = Time(datetime.datetime.utcnow()) end = Time(start.datetime + datetime.timedelta(days=1)) time_range = time_grid_from_range( time_range = [start, end], time_resolution = interval*u.minute) time_plot = time_range.datetime fixed_target = FixedTarget(name = target.name, coord = SkyCoord( target.ra, target.dec, unit = 'deg' ) ) #Hack to speed calculation up by factor of ~3 sun_coords = get_sun(time_range[int(len(time_range)/2)]) fixed_sun = FixedTarget(name = 'sun', coord = SkyCoord( sun_coords.ra, sun_coords.dec, unit = 'deg' ) ) #Colors to match SNEx1 colors = { 'Siding Spring': '#3366cc', 'Sutherland': '#dc3912', 'Teide': '#8c6239', 'Cerro Tololo': '#ff9900', 'McDonald': '#109618', 'Haleakala': '#990099' } for observing_facility in facility.get_service_classes(): observing_facility_class = facility.get_service_class(observing_facility) sites = observing_facility_class().get_observing_sites() for site, site_details in sites.items(): observer = Observer( longitude = site_details.get('longitude')*u.deg, latitude = site_details.get('latitude')*u.deg, elevation = site_details.get('elevation')*u.m ) sun_alt = observer.altaz(time_range, fixed_sun).alt obj_airmass = observer.altaz(time_range, fixed_target).secz bad_indices = np.argwhere( (obj_airmass >= airmass_limit) | (obj_airmass <= 1) | (sun_alt > -18*u.deg) #between astro twilights ) obj_airmass = [np.nan if i in bad_indices else float(x) for i, x in enumerate(obj_airmass)] label = '({facility}) {site}'.format( facility = observing_facility, site = site ) plot_data.append( go.Scatter(x=time_plot, y=obj_airmass, mode='lines', name=label, marker=dict(color=colors.get(site))) ) return plot_data
def main(args=None): p = parser() opts = p.parse_args(args) # Late imports import operator import sys from astroplan import Observer from astroplan.plots import plot_airmass from astropy.coordinates import EarthLocation, SkyCoord from astropy.table import Table from astropy.time import Time from astropy import units as u from matplotlib import dates from matplotlib.cm import ScalarMappable from matplotlib.colors import Normalize from matplotlib.patches import Patch from matplotlib import pyplot as plt from tqdm import tqdm import pytz from ..io import fits from .. import moc from .. import plot # noqa from ..extern.quantile import percentile if opts.site is None: if opts.site_longitude is None or opts.site_latitude is None: p.error('must specify either --site or both ' '--site-longitude and --site-latitude') location = EarthLocation(lon=opts.site_longitude * u.deg, lat=opts.site_latitude * u.deg, height=(opts.site_height or 0) * u.m) if opts.site_timezone is not None: location.info.meta = {'timezone': opts.site_timezone} observer = Observer(location) else: if not ((opts.site_longitude is None) and (opts.site_latitude is None) and (opts.site_height is None) and (opts.site_timezone is None)): p.error('argument --site not allowed with arguments ' '--site-longitude, --site-latitude, ' '--site-height, or --site-timezone') observer = Observer.at_site(opts.site) m = fits.read_sky_map(opts.input.name, moc=True) # Make an empty airmass chart. t0 = Time(opts.time) if opts.time is not None else Time.now() t0 = observer.midnight(t0) ax = plot_airmass([], observer, t0, altitude_yaxis=True) # Remove the fake source and determine times that were used for the plot. del ax.lines[:] times = Time(np.linspace(*ax.get_xlim()), format='plot_date') theta, phi = moc.uniq2ang(m['UNIQ']) coords = SkyCoord(phi, 0.5 * np.pi - theta, unit='rad') prob = moc.uniq2pixarea(m['UNIQ']) * m['PROBDENSITY'] levels = np.arange(90, 0, -10) nlevels = len(levels) percentiles = np.concatenate((50 - 0.5 * levels, 50 + 0.5 * levels)) airmass = np.column_stack([ percentile(condition_secz(coords.transform_to(observer.altaz(t)).secz), percentiles, weights=prob) for t in tqdm(times) ]) cmap = ScalarMappable(Normalize(0, 100), plt.get_cmap()) for level, lo, hi in zip(levels, airmass[:nlevels], airmass[nlevels:]): ax.fill_between( times.plot_date, clip_verylarge(lo), # Clip infinities to large but finite values clip_verylarge(hi), # because fill_between cannot handle inf color=cmap.to_rgba(level), zorder=2) ax.legend([Patch(facecolor=cmap.to_rgba(level)) for level in levels], ['{}%'.format(level) for level in levels]) # ax.set_title('{} from {}'.format(m.meta['objid'], observer.name)) # Adapted from astroplan start = times[0] twilights = [ (times[0].datetime, 0.0), (observer.sun_set_time(Time(start), which='next').datetime, 0.0), (observer.twilight_evening_civil(Time(start), which='next').datetime, 0.1), (observer.twilight_evening_nautical(Time(start), which='next').datetime, 0.2), (observer.twilight_evening_astronomical(Time(start), which='next').datetime, 0.3), (observer.twilight_morning_astronomical(Time(start), which='next').datetime, 0.4), (observer.twilight_morning_nautical(Time(start), which='next').datetime, 0.3), (observer.twilight_morning_civil(Time(start), which='next').datetime, 0.2), (observer.sun_rise_time(Time(start), which='next').datetime, 0.1), (times[-1].datetime, 0.0), ] twilights.sort(key=operator.itemgetter(0)) for i, twi in enumerate(twilights[1:], 1): if twi[1] != 0: ax.axvspan(twilights[i - 1][0], twilights[i][0], ymin=0, ymax=1, color='grey', alpha=twi[1], linewidth=0) if twi[1] != 0.4: ax.axvspan(twilights[i - 1][0], twilights[i][0], ymin=0, ymax=1, color='white', alpha=0.8 - 2 * twi[1], zorder=3, linewidth=0) # Add local time axis timezone = (observer.location.info.meta or {}).get('timezone') if timezone: tzinfo = pytz.timezone(timezone) ax2 = ax.twiny() ax2.set_xlim(ax.get_xlim()) ax2.set_xticks(ax.get_xticks()) ax2.xaxis.set_major_formatter(dates.DateFormatter('%H:%M', tz=tzinfo)) plt.setp(ax2.get_xticklabels(), rotation=-30, ha='right') ax2.set_xlabel("Time from {} [{}]".format( min(times).to_datetime(tzinfo).date(), timezone)) if opts.verbose: # Write airmass table to stdout. times.format = 'isot' table = Table(masked=True) table['time'] = times table['sun_alt'] = np.ma.masked_greater_equal( observer.sun_altaz(times).alt, 0) table['sun_alt'].format = lambda x: '{}'.format(int(np.round(x))) for p, data in sorted(zip(percentiles, airmass)): table[str(p)] = np.ma.masked_invalid(data) table[str(p)].format = lambda x: '{:.01f}'.format(np.around(x, 1)) table.write(sys.stdout, format='ascii.fixed_width') # Show or save output. opts.output()
def get_sidereal_visibility(target, start_time, end_time, interval, airmass_limit): """ Uses astroplan to calculate the airmass for a sidereal target for each given interval between the start and end times. The resulting data omits any airmass above the provided limit (or default, if one is not provided), as well as any airmass calculated during the day (defined as between astronomical twilights). Important note: only works for sidereal targets! For non-sidereal visibility, see here: https://github.com/TOMToolkit/tom_nonsidereal_airmass :param start_time: start of the window for which to calculate the airmass :type start_time: datetime :param end_time: end of the window for which to calculate the airmass :type end_time: datetime :param interval: time interval, in minutes, at which to calculate airmass within the given window :type interval: int :param airmass_limit: maximum acceptable airmass for the resulting calculations :type airmass_limit: int :returns: A dictionary containing the airmass data for each site. The dict keys consist of the site name prepended with the observing facility. The values are the airmass data, structured as an array containing two arrays. The first array contains the set of datetimes used in the airmass calculations. The second array contains the corresponding set of airmasses calculated. :rtype: dict """ if target.type != 'SIDEREAL': msg = '\033[1m\033[91mAirmass plotting is only supported for sidereal targets\033[0m' logger.info(msg) empty_visibility = {} return empty_visibility if end_time < start_time: raise Exception('Start must be before end') if airmass_limit is None: airmass_limit = 10 body = FixedTarget(name=target.name, coord=SkyCoord(target.ra, target.dec, unit='deg')) visibility = {} sun, time_range = get_astroplan_sun_and_time(start_time, end_time, interval) for observing_facility in facility.get_service_classes(): observing_facility_class = facility.get_service_class( observing_facility) sites = observing_facility_class().get_observing_sites() for site, site_details in sites.items(): observer = Observer( longitude=site_details.get('longitude') * units.deg, latitude=site_details.get('latitude') * units.deg, elevation=site_details.get('elevation') * units.m) sun_alt = observer.altaz(time_range, sun).alt obj_airmass = observer.altaz(time_range, body).secz bad_indices = np.argwhere( (obj_airmass >= airmass_limit) | (obj_airmass <= 1) | (sun_alt > -18 * units.deg) # between astronomical twilights, i.e. sun is up ) obj_airmass = [ None if i in bad_indices else float(airmass) for i, airmass in enumerate(obj_airmass) ] visibility[f'({observing_facility}) {site}'] = ( time_range.datetime, obj_airmass) return visibility
def is_object_visible(celestial_obj: object, secz_max: float) -> tuple: """ Check if the object is visible in the set start and end times. :param celestial_obj: object to view (FixedTarget()) :param secz_max: Maximum viewing angle. :return: starting altitude, azimuth and ending altitude, azimuth. """ location = Observer( location=EarthLocation.from_geodetic( lon=(Const.LONGITUDE * u.deg), lat=(Const.LATITUDE * u.deg), height=(Const.ELEVATION * u.m) ), name="location", timezone="UTC", ) start_time = Time( f"{Const.START_YEAR}-" + f"{Const.START_MONTH}-" + f"{Const.START_DAY} " + f"{Const.START_TIME}", format="iso", ) end_time = Time( f"{Const.END_YEAR}-" + f"{Const.END_MONTH}-" + f"{Const.END_DAY} " + f"{Const.END_TIME}", format="iso", ) Logger.log(f"Checking sec(z) for {celestial_obj.name}.") start_secz = location.altaz(start_time, celestial_obj).secz end_secz = location.altaz(end_time, celestial_obj).secz start_altaz = location.altaz(start_time, celestial_obj) end_altaz = location.altaz(end_time, celestial_obj) try: if 0 < start_secz < secz_max: Logger.log( f"Found starting sec(z) = {start_secz} for {celestial_obj.name}." ) Logger.log( f"Zenith={start_altaz.zen} " + f"Altitiude={start_altaz.alt}" + f"Azimuth={start_altaz.az}" ) start_alt = start_altaz.alt start_az = start_altaz.az else: start_alt = "-" start_az = "-" if 0 < end_secz < secz_max: Logger.log(f"Found ending sec(z) = {end_secz} for {celestial_obj.name}.") Logger.log( f"Zenith={start_altaz.zen} " + f"Altitiude={start_altaz.alt}" + f"Azimuth={start_altaz.az}" ) end_alt = end_altaz.alt end_az = end_altaz.az else: end_alt = "-" end_az = "-" return start_alt, start_az, end_alt, end_az except ValueError as e: Logger.log(f"Could not find sec(z) for {celestial_obj.name}.", 40) Logger.log(str(e), 40) Logger.log(start_secz, 40) return "-", "-", "-", "-" return "-", "-", "-", "-"
def get_altaz(obj_name, ipt_lon, ipt_lat, t=None): #for html scrapping #from lxml import html #from bs4 import BeautifulSoup #to place requests import requests import json import astropy.units as u from astropy.time import Time from astropy.coordinates import SkyCoord, EarthLocation, Angle, Latitude, Longitude from astroplan import FixedTarget, Observer from astroquery.simbad import Simbad as simbad import ephem if t == None: t = Time.now() ## Set up the observer obs_el = 100 * u.m loc = EarthLocation.from_geodetic(ipt_lon, ipt_lat, obs_el) my_site = Observer(name='My_Site', location=loc) obs_lat = my_site.location.lat obs_lon = my_site.location.lon #observer for pyephem ephem_site = ephem.Observer() ephem_site.lon, ephem_site.lat = str(obs_lon.deg), str(obs_lat.deg) ephem_site.date = ephem.Date(str(t.decimalyear)) ##Get the object #Check for planet-hood. #if planet: resolve the individual planet with pyephem. #else if satellite or ISS (or TIANGONG) scrap the appropriate websites and return info #else query simbad ############ # Put in an auto-correct for kids ############ #just make it lower case for now obj_name = obj_name.lower() if obj_name in [ "sun", "mercury", "venus", "moon", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto" ]: if obj_name == "sun": my_planet = ephem.Sun() elif obj_name == "mercury": my_planet = ephem.Mercury() elif obj_name == "venus": my_planet = ephem.Venus() elif obj_name == "moon": my_planet = ephem.Moon() elif obj_name == "mars": my_planet = ephem.Mars() elif obj_name == "jupiter": my_planet = ephem.Jupiter() elif obj_name == "saturn": my_planet = ephem.Saturn() elif obj_name == "uranus": my_planet = ephem.Uranus() elif obj_name == "neptune": my_planet = ephem.Neptune() elif obj_name == "pluto": my_planet = ephem.Pluto() my_planet.compute(ephem_site) az = my_planet.az * 180 / 3.1415926535 alt = my_planet.alt * 180 / 3.1415926535 #here coded for just ISS but for all satellites we should have similar setups, probably poll site elif (obj_name == "iss"): #try a request for the iss from the open notify site. Gives current json data page = requests.get("http://api.open-notify.org/iss-now.json") issdata = page.json() tstamp = issdata['timestamp'] isslat = issdata['iss_position']['latitude'] isslon = issdata['iss_position']['longitude'] #there are issues with just this amount of data as you do not know the altitude of the object #here we fix it to 350 km issheight = 350 * u.km isslat = Latitude(isslat, unit=u.deg) isslon = Longitude(isslon, unit=u.deg) #there are issues however as this data does NOT contain the altitude so lets try scrapping the html #the issue with fullissdata is that it contains information in NASA style units (M50 Cartesian & M50 Keplerian) page = requests.get( "http://spaceflight.nasa.gov/realdata/sightings/SSapplications/Post/JavaSSOP/orbit/ISS/SVPOST.html" ) #fullissdata=html.fromstring(page.text) #there are also other satellites liseted in, issue is parsing the information as I do not know what each field contains #the issue here is that all sat data contains unknown units and uncertain which entries contain useful information page = requests.get( "http://www.celestrak.com/NORAD/elements/stations.txt") allsatdata = page.text c = SkyCoord(isslon, isslat, issheight) my_target = FixedTarget(name='ISS', coord=c) az = my_site.altaz(t, my_target).az.deg alt = my_site.altaz(t, my_target).alt.deg else: try: q = simbad.query_object(obj_name) c = SkyCoord(q["RA"][0], q["DEC"][0], unit=(u.hourangle, u.deg)) my_star = FixedTarget(name='my_star', coord=c) az = my_site.altaz(t, my_star).az.deg alt = my_site.altaz(t, my_star).alt.deg except: print("Couldn't find Object in Database") alt, az = 0, 0 return alt, az
def get_24hr_airmass(target, interval, airmass_limit): plot_data = [] start = Time(datetime.datetime.utcnow()) end = Time(start.datetime + datetime.timedelta(days=1)) time_range = time_grid_from_range(time_range=[start, end], time_resolution=interval * u.minute) time_plot = time_range.datetime fixed_target = FixedTarget(name=target.name, coord=SkyCoord(target.ra, target.dec, unit='deg')) #Hack to speed calculation up by factor of ~3 sun_coords = get_sun(time_range[int(len(time_range) / 2)]) fixed_sun = FixedTarget(name='sun', coord=SkyCoord(sun_coords.ra, sun_coords.dec, unit='deg')) for observing_facility in facility.get_service_classes(): if observing_facility != 'LCO': continue observing_facility_class = facility.get_service_class( observing_facility) sites = observing_facility_class().get_observing_sites() for site, site_details in sites.items(): observer = Observer(longitude=site_details.get('longitude') * u.deg, latitude=site_details.get('latitude') * u.deg, elevation=site_details.get('elevation') * u.m) sun_alt = observer.altaz(time_range, fixed_sun).alt obj_airmass = observer.altaz(time_range, fixed_target).secz bad_indices = np.argwhere( (obj_airmass >= airmass_limit) | (obj_airmass <= 1) | (sun_alt > -18 * u.deg) #between astro twilights ) obj_airmass = [ np.nan if i in bad_indices else float(x) for i, x in enumerate(obj_airmass) ] label = '({facility}) {site}'.format(facility=observing_facility, site=site) plot_data.append( go.Scatter( x=time_plot, y=obj_airmass, mode='lines', name=label, )) return plot_data
print(total_mins) #Total minutes for observation ############################################################################################################ #Loop through all ra and dec for ii,r in enumerate(ra): for jj,d in enumerate(dec): target_coord = SkyCoord(ra=r*u.deg, dec=d*u.deg) target = FixedTarget(coord=target_coord, name="source") #Airmasses at observatory 1 airmass_obs1=obs1.altaz(times, target).secz #calculate airmass for observatory 1 masked_airmass_obs1 = np.ma.array(airmass_obs1, mask=airmass_obs1 < 1)#mask airmasses for observatory 1 #Airmasses at observatory2 airmass_obs2=obs2.altaz(times,target).secz #calculate airmass for observatory 2 masked_airmass_obs2 = np.ma.array(airmass_obs2, mask=airmass_obs2 < 1)#mask airmasses for observatory 2 xc=obs1.is_night(times,horizon= -12*u.deg) #Times that it is night time at observatory 1 from time list cc=obs2.is_night(times,horizon= -12*u.deg) #Times that it is night time at observatory 2 from time list # For the case night only observation for observatory 1 and 2
def test_compare_azimuth_constraint_and_observer(): time = Time('2001-02-03 04:05:06') time_ranges = [ Time([time, time + 1 * u.hour]) + offset for offset in np.arange(0, 400, 100) * u.day ] for time_range in time_ranges: mmt = Observer(longitude=249.115*u.deg, latitude=31.6883*u.deg, \ elevation=2608*u.m, name="mmt", timezone="US/Arizona") targets = [vega, rigel, polaris] # Testing for easterly targets, from northeast (45) to southeast (135). min_az = Angle(45 * u.deg) max_az = Angle(135 * u.deg) # Same checks on azimuth inputs as in AzimuthConstraint. # Constrain the minimum and maximum azimuth angles to 0-360 range. min_az.wrap_at('360d', inplace=True) max_az.wrap_at('360d', inplace=True) # Handle the case of minimum azimuth having a larger value than the maximum azimuth. # This can occur when the azimuth range includes north (azimuth == 0). if min_az > max_az: max_az += 360 * u.deg # Check if each target meets azimth constraints using Observer always_from_observer = [ all([ min_az < mmt.altaz(time, target).az < max_az for time in time_grid_from_range(time_range) ]) for target in targets ] # Check if each target meets azimuth constraints using # is_always_observable and AzimuthConstraint always_from_constraint = is_always_observable(AzimuthConstraint( min_az, max_az), mmt, targets, time_range=time_range) assert all(always_from_observer == always_from_constraint) # Testing for northerly targets, from northwest (315) to northeast (45). min_az = Angle(315 * u.deg) max_az = Angle(45 * u.deg) # Same checks on azimuth inputs as in AzimuthConstraint. # Constrain the minimum and maximum azimuth angles to 0-360 range. min_az.wrap_at('360d', inplace=True) max_az.wrap_at('360d', inplace=True) # Handle the case of minimum azimuth having a larger value than the maximum azimuth. # This can occur when the azimuth range includes north (azimuth == 0). if min_az > max_az: max_az += 360 * u.deg # Check if each target meets azimth constraints using Observer always_from_observer = [ all([ min_az < mmt.altaz(time, target).az < max_az for time in time_grid_from_range(time_range) ]) for target in targets ] # Check if each target meets azimuth constraints using # is_always_observable and AzimuthConstraint always_from_constraint = is_always_observable(AzimuthConstraint( min_az, max_az), mmt, targets, time_range=time_range) assert all(always_from_observer == always_from_constraint)