def sunset( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Calculates sunset time (the time in the evening when the sun is a 0.833 degrees below the horizon. This is to account for refraction.) :param date: The date for which to calculate the sunset time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which sunset occurs. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.sunset(observer, date, self.tzinfo) else: return astral.sun.sunset(observer, date)
def night( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Calculates the night time (the time between astronomical dusk and astronomical dawn of the next day) :param date: The date for which to calculate the start of the night time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: A tuple containing the start and end times """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.night(observer, date, self.tzinfo) else: return astral.sun.night(observer, date)
def midnight(self, date: datetime.date = None, local: bool = True) -> datetime.datetime: """Calculates the solar midnight (the time when the sun is at its lowest point.) :param date: The date for which to calculate the midnight time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :returns: The date and time at which the solar midnight occurs. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude) if local: return astral.sun.midnight(observer, date, self.tzinfo) else: return astral.sun.midnight(observer, date)
def rahukaalam( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Calculates the period of rahukaalam. :param date: The date for which to calculate the rahukaalam period. A value of ``None`` uses the current date. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. :param observer_elevation: Elevation of the observer in metres above the location. :return: Tuple containing the start and end times for Rahukaalam. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.rahukaalam(observer, date, self.tzinfo) else: return astral.sun.rahukaalam(observer, date)
def sunrise_sunset(latitude: float, longitude: float, date: date) -> Tuple[datetime, datetime]: """Get sunrise and sunset.""" return cast( Tuple[datetime, datetime], sun.daylight(Observer(latitude, longitude), date), )
def is_daylight(location): """True if it's daylight at the location.""" now = datetime.now(timezone(timedelta(hours=-10))) lat = float(location['latitude']) lon = float(location['longitude']) obs = Observer(latitude=lat, longitude=lon) today = now.date() s = sun(obs, date=today) return s['sunrise'] <= now < s['sunset']
def getCurrentSchedule() -> WeeklySchedule: '''Current HoP weekly schedule.''' # Observer at HoP: Rochester_HoP,USA,43°09'N,77°23'W,US/Eastern,170 # Note: elevation (in meters) is just a guess based on ROC airport. We may adjust it. hop_observer = Observer(latitude=43.1606355, longitude=-77.3883843, elevation=170) result = WeeklySchedule(hop_observer) result.addEvent(calendar.SUNDAY, ScheduleEvent(time(16, 45), time(19, 0))) result.addEvent(calendar.TUESDAY, ScheduleEvent(time(18, 30), time(22, 0))) result.addEvent(calendar.WEDNESDAY, ScheduleEvent(time(18, 45), time(21, 0))) result.addEvent(calendar.FRIDAY, ScheduleEvent(time(18, 45), time(21, 0))) return result
def time_at_elevation( self, elevation: float, date: datetime.date = None, direction: SunDirection = SunDirection.RISING, local: bool = True, ) -> datetime.datetime: """Calculate the time when the sun is at the specified elevation. Note: This method uses positive elevations for those above the horizon. Elevations greater than 90 degrees are converted to a setting sun i.e. an elevation of 110 will calculate a setting sun at 70 degrees. :param elevation: Elevation in degrees above the horizon to calculate for. :param date: The date for which to calculate the elevation time. If no date is specified then the current date will be used. :param direction: Determines whether the time is for the sun rising or setting. Use ``SunDirection.RISING`` or ``SunDirection.SETTING``. Default is rising. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :returns: The date and time at which dusk occurs. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) if elevation > 90.0: elevation = 180.0 - elevation direction = SunDirection.SETTING observer = Observer(self.latitude, self.longitude, 0.0) if local: return astral.sun.time_at_elevation(observer, elevation, date, direction, self.tzinfo) else: return astral.sun.time_at_elevation(observer, elevation, date, direction)
def draw_sun(self, pos, width): observer = Observer(latitude=self.settings.position['lat'], longitude=self.settings.position['lon']) tz = tzlocal() s = sun(observer, tzinfo=tz) rise_time = s['sunrise'].strftime(self.settings.time_format) set_time = s['sunset'].strftime(self.settings.time_format) self.display.draw_icon_text_centered(pos, width // 2, self.res.sunrise, rise_time, self.res.tiny_font, self.display.RED, self.display.BLACK) self.display.draw_icon_text_centered( (pos[0] + width // 2, pos[1]), width // 2, self.res.sunrise, set_time, self.res.tiny_font, self.display.RED, self.display.BLACK)
def golden_hour( self, direction: SunDirection = SunDirection.RISING, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Returns the start and end times of the Golden Hour when the sun is traversing in the specified direction. This method uses the definition from PhotoPills i.e. the golden hour is when the sun is between 4 degrees below the horizon and 6 degrees above. :param direction: Determines whether the time is for the sun rising or setting. Use ``SunDirection.RISING`` or ``SunDirection.SETTING``. Default is rising. :param date: The date for which to calculate the times. :param local: True = Times to be returned in location's time zone; False = Times to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :return: A tuple of the date and time at which the Golden Hour starts and ends. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.golden_hour(observer, date, direction, self.tzinfo) else: return astral.sun.golden_hour(observer, date, direction)
def solar_azimuth( self, dateandtime: datetime.datetime = None, observer_elevation: Elevation = 0.0, ) -> float: """Calculates the solar azimuth angle for a specific date/time. :param dateandtime: The date and time for which to calculate the angle. :returns: The azimuth angle in degrees clockwise from North. """ if dateandtime is None: dateandtime = astral.sun.now(self.tzinfo) elif not dateandtime.tzinfo: dateandtime = self.tzinfo.localize(dateandtime) observer = Observer(self.latitude, self.longitude, observer_elevation) dateandtime = dateandtime.astimezone(pytz.utc) # type: ignore return astral.sun.azimuth(observer, dateandtime)
def twilight( self, date: datetime.date = None, direction: SunDirection = SunDirection.RISING, local: bool = True, observer_elevation: Elevation = 0.0, ): """Returns the start and end times of Twilight in the UTC timezone when the sun is traversing in the specified direction. This method defines twilight as being between the time when the sun is at -6 degrees and sunrise/sunset. :param direction: Determines whether the time is for the sun rising or setting. Use ``astral.SUN_RISING`` or ``astral.SunDirection.SETTING``. :param date: The date for which to calculate the times. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :return: A tuple of the UTC date and time at which twilight starts and ends. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.twilight(observer, date, direction, self.tzinfo) else: return astral.sun.twilight(observer, date, direction)
def dusk( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Calculates the dusk time (the time in the evening when the sun is a certain number of degrees below the horizon. By default this is 6 degrees but can be changed by setting the :attr:`solar_depression` property.) :param date: The date for which to calculate the dusk time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which dusk occurs. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.dusk(observer, date, self.solar_depression, self.tzinfo) else: return astral.sun.dusk(observer, date, self.solar_depression)
def sunrise_sunset(date, lat, lon): """ Calculate sunrise and sunset times in utc for given date, lat and lon. Parameters ---------- date : datetime.date Date in yyyy-mm-dd. lat : float Latitude. lon : float Longitude. Returns ------- sunrise : datetime Sunrise time. sunset : datetime Sunset time. """ obs = Observer(latitude=lat, longitude=lon, elevation=0.0) sunrise = sun.sunrise(observer=obs, date=date) sunset = sun.sunset(observer=obs, date=date) return sunrise, sunset
def sun( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> dict: """Returns dawn, sunrise, noon, sunset and dusk as a dictionary. :param date: The date for which to calculate the times. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: Dictionary with keys ``dawn``, ``sunrise``, ``noon``, ``sunset`` and ``dusk`` whose values are the results of the corresponding methods. """ if local and self.timezone is None: raise ValueError( "Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.sun(observer, date, self.solar_depression, self.tzinfo) else: return astral.sun.sun(observer, date, self.solar_depression)
def test_bad_longitude(self): with pytest.raises(ValueError): Observer(1, "o", 1)
def test_bad_elevation(self): with pytest.raises(ValueError): Observer(1, 1, "o")
def test_latitude_outside_limits(self): obs = Observer(90.1, 0, 0) assert obs.latitude == 90.0 obs = Observer(-90.1, 0, 0) assert obs.latitude == -90.0
def test_longitude_outside_limits(self): obs = Observer(0, 180.1, 0) assert obs.longitude == 180.0 obs = Observer(0, -180.1, 0) assert obs.longitude == -180.0
def test_default(self): obs = Observer() assert obs.latitude == 51.4733 assert obs.longitude == -0.0008333 assert obs.elevation == 0.0
def main(df, loc_main_outputs): print("starting script 2 (Database summary)") today = str(date.today()) # Create new pandas data frame for processed output. df_pro = pd.DataFrame(df) # Add new columns, populated with 'nan'. df_pro = pd.concat([ df_pro, pd.DataFrame(columns=[ 'binomial_species', 'species_short', 'plot_area_from_field_length', 'solar_elevation', 'solar_noon_elevation', 'solar_elevation_offset', 'solar_noon_utc', 'minutes_offset_from_solar_noon' ]) ], sort=False) # Extract three-character site code from survey_code and add new column. df_pro['site_code'] = df_pro['survey_code'].str[-3:] # Concatenate genus and species into binomial species and species_short. df_pro['binomial_species'] = df_pro['plot_genus'] + " " + \ df_pro['plot_species'] df_pro['species_short'] = df_pro['plot_genus'].str[0] + ". " + \ df_pro['plot_species'] # Calculate harvest plot area from side length df_pro['plot_area_from_field_length'] = (df_pro['plot_side_length']**2) # Calculate solar parameters (elevation and timing) using the Astral library for index_label, row in df_pro.iterrows(): latitude = row[ 'SiteLatitude'] # Define latitude parameter for observation. longitude = row[ 'SiteLongitude'] # Define longitude parameter for observation. elevation = row[ 'SiteElevation'] # Define elevation parameter for observation (supports more advanced sun calculations in future) # noqa survey_datetime = row['SurveyDateTimeUTC'] location = Observer(latitude, longitude, elevation) # specify observer location for Astral. solar_noon = astral.sun.noon( location, survey_datetime) # Calculate time of solar noon. solar_elevation = astral.sun.elevation( location, survey_datetime) # Calculate solar elevation during survey. solar_elevation = round(solar_elevation, 2) # Tidy elevation value. solar_noon_elevation = astral.sun.elevation( location, solar_noon) # Calculate solar elevation at noon. solar_noon_elevation = round(solar_noon_elevation, 2) solar_elevation_offset = solar_noon_elevation - solar_elevation solar_elevation_offset = round(solar_elevation_offset, 2) # Determine the absolute number of seconds between the survey and solar noon, and convert into minutes. time_offset = abs(survey_datetime - solar_noon).seconds / 60 time_offset = round(time_offset, 0) # Write new values to dataframe df_pro.at[index_label, 'solar_elevation'] = solar_elevation df_pro.at[index_label, 'solar_noon_elevation'] = solar_noon_elevation df_pro.at[index_label, 'solar_elevation_offset'] = solar_elevation_offset df_pro.at[index_label, 'solar_noon_utc'] = solar_noon df_pro.at[index_label, 'minutes_offset_from_solar_noon'] = time_offset # Create new dataframe of observations sampled at peak biomass df_pro_peak = pd.DataFrame(df_pro[df_pro['PeakBiomass'] > 0]) # Summarize database n_of_surveys = df['survey_code'].nunique( ) # Returns the number of unique survey codes. list_of_surveys = list(set( df['survey_code'])) # Returns a list of unique survey codes. list_of_surveys.sort() # Sorts list into ascending order list_of_surveys_sep = '\n'.join( list_of_surveys) # Add line returns to list of surveys. n_of_sites = df_pro['SiteName'].nunique( ) # Returns the number of unique sites. n_of_surveys_peak = df_pro_peak['survey_code'].nunique( ) # Returns n of unique survey codes. # list_of_surveys_peak = list(set(df_pro_peak['survey_code'])) # Returns list of unique survey codes. # list_of_surveys_peak.sort() # Sorts list into ascending order # list_of_surveys_peak_sep = '\n'.join(list_of_surveys_peak) # Add line returns to list of surveys. n_of_contributing_teams = df['Investigators'].nunique( ) # Returns the number of contributing teams. n_of_observations = df['RecordID'].nunique( ) # Returns n of observations in the data set. n_of_observations_peak = sum( df_pro_peak['PeakBiomass'] ) # Returns n of observations in the data set. sum_of_harvested_biomass = round( (df['AGB'].sum()) / 1000, 2) # Returns sum of harvested biomass in g. sum_of_harvested_peak_biomass = round( df.loc[df['PeakBiomass'] > 0, ['AGB'][0]].sum() / 1000, 2) n_of_sites_peak = df_pro_peak['site_code'].nunique( ) # Returns the number of sites with peak observations. mean_offset_from_solar_noon = df_pro_peak[ 'minutes_offset_from_solar_noon'].sum() / len( df_pro_peak['minutes_offset_from_solar_noon']) # noqa mean_offset_from_solar_noon = round(mean_offset_from_solar_noon, 0) max_offset_from_solar_noon = df_pro_peak[ 'minutes_offset_from_solar_noon'].max() # n_of_edited_plots = database['manual_filtering_required'].values.sum() # noqa # Count the number of edited point clouds n_of_edited_plots_peak = df_pro_peak[ 'manual_filtering_required'].values.sum( ) # noqa # Count the number of edited point clouds n_of_igbp = df['IGBP_class'].nunique( ) # count number of IGBP classes sampled n_of_igbp_peak = df_pro_peak['IGBP_class'].nunique( ) # count number of IGBP classes sampled # Plant Functional_Type summaries. # list_of_groups = list(set(database_pro['plant_functional_type'].dropna())) n_of_graminoid_plots = len( df_pro[(df_pro['plant_functional_type'] == 'Graminoid')]) # noqa n_of_shrub_plots = len( df_pro[(df_pro['plant_functional_type'] == 'Shrub')]) n_of_forb_plots = len(df_pro[(df_pro['plant_functional_type'] == 'Forb')]) n_of_tree_plots = len(df_pro[(df_pro['plant_functional_type'] == 'Tree')]) n_of_fern_plots = len(df_pro[(df_pro['plant_functional_type'] == 'Fern')]) n_of_succulent_plots = len( df_pro[(df_pro['plant_functional_type'] == 'Succulent')]) # noqa n_of_graminoid_plots_peak = len(df_pro_peak[(df_pro_peak.AGB > 0) & ( df_pro_peak.plant_functional_type == 'Graminoid')]) # noqa n_of_shrub_plots_peak = len( df_pro_peak[(df_pro_peak.AGB > 0) & (df_pro_peak.plant_functional_type == 'Shrub')]) # noqa n_of_forb_plots_peak = len( df_pro_peak[(df_pro_peak.AGB > 0) & (df_pro_peak.plant_functional_type == 'Forb')]) # noqa n_of_tree_plots_peak = len( df_pro_peak[(df_pro_peak.AGB > 0) & (df_pro_peak.plant_functional_type == 'Tree')]) # noqa n_of_fern_plots_peak = len( df_pro_peak[(df_pro_peak.AGB > 0) & (df_pro_peak.plant_functional_type == 'Fern')]) # noqa n_of_succulent_plots_peak = len(df_pro_peak[(df_pro_peak.AGB > 0) & ( df_pro_peak.plant_functional_type == 'Succulent')]) # noqa n_of_bryophyte_plots_peak = len(df_pro_peak[(df_pro_peak.AGB > 0) & ( df_pro_peak.plant_functional_type == 'Bryophyte')]) # noqa # Summarise sampled families list_of_families = sorted(list(set( df['plot_family'].dropna()))) # Returns a list of unique families. remove_list = ['TBC', 'Unknown'] # Create list of 'species' to remove. list_of_families = [i for i in list_of_families if i not in remove_list ] # Remove invalid 'families' from list n_of_families = len( list_of_families) # Returns the number of unique records. list_of_families_sep = '\n'.join( list_of_families) # Add line returns to list. # Summarise sampled families for peak biomass list_of_families_peak = sorted( list(set(df_pro_peak['plot_family'].dropna()))) remove_list = ['TBC', 'Unknown'] # Create list of 'species' to remove. list_of_families_peak = [ i for i in list_of_families_peak if i not in remove_list ] n_of_families_peak = len( list_of_families_peak) # Returns the number of unique records. list_of_families_peak_sep = '\n'.join( list_of_families_peak) # Add line returns to list. # Summarise sampled species list_of_species = sorted(list(set(df_pro['binomial_species'].dropna())) ) # noqa # Returns list of unique species/groups. remove_list = ['TBC TBC', 'Unknown spp.'] # Create list of 'species' to remove. list_of_species = [i for i in list_of_species if i not in remove_list ] # Remove invalid 'species' from list n_of_species = len( list_of_species) # Returns the number of unique records. # noqa list_of_species_sep = '\n'.join( list_of_species) # Add line returns to list. # Summarise sampled species for peak biomass list_of_species_peak = sorted( list(set(df_pro_peak['binomial_species'].dropna())) ) # noqa # Returns list of unique species/groups. remove_list = ['TBC TBC', 'Unknown spp.'] # Create list of 'species' to remove. list_of_species_peak = [ i for i in list_of_species_peak if i not in remove_list ] # noqa # Remove invalid 'species' from list n_of_species_peak = len( list_of_species_peak) # Returns the number of unique records. list_of_species_peak_sep = '\n'.join( list_of_species_peak) # Add line returns to list. # Summary of wind conditions. wind_all_mean = round(sum(df['wind_speed']) / len(df['wind_speed']), 2) wind_all_max = round(max(df['wind_speed']), 2) wind_all_min = round(min(df['wind_speed']), 2) wind_all_mean_peak = round( sum(df_pro_peak['wind_speed']) / len(df_pro_peak['wind_speed']), 2) wind_all_max_peak = round(max(df_pro_peak['wind_speed']), 2) wind_all_min_peak = round(min(df_pro_peak['wind_speed']), 2) # Summary of species that were sub-sampled for determination of moisture content, for reporting in the methods. # Filter dataset by peak biomass = True and 'EntirePlantDried' = False temp = df_pro[(df_pro['PeakBiomass'] > 0) & (df_pro['EntirePlantDried'] == 0)] # Extract list of species, sorted alphabetically. species_dry_subsample = sorted(list(set( temp['binomial_species'].dropna()))) # Insert 'and', if needed, and remove quotation marks from list. if len(species_dry_subsample) > 1: species_dry_subsample.insert(-1, "and") species_dry_subsample = (', '.join(species_dry_subsample[:-2]) + ' ' + ' '.join(species_dry_subsample[-2:])) else: species_dry_subsample = ', '.join(list(species_dry_subsample)) # Summarise drones used for sampling. n_of_drones = df['drone_platform'].nunique() n_of_drones_peak = df_pro_peak['drone_platform'].nunique() list_of_drones = sorted(list(set(df['drone_platform'].dropna()))) list_of_drones = ', '.join( list(list_of_drones)) # Remove quotation marks from list. list_of_drones_peak = sorted( list(set(df_pro_peak['drone_platform'].dropna()))) list_of_drones_peak = ', '.join( list(list_of_drones_peak)) # Remove quotation marks from list. # Summarise cameras used for sampling. n_of_cameras = df['camera_sensor'].nunique() n_of_cameras_peak = df_pro_peak['camera_sensor'].nunique() list_of_cameras = sorted(list(set(df['camera_sensor'].dropna()))) list_of_cameras = ', '.join( list(list_of_cameras)) # Remove quotation marks from list. list_of_cameras_peak = sorted( list(set(df_pro_peak['camera_sensor'].dropna()))) list_of_cameras_peak = ', '.join( list(list_of_cameras_peak)) # Remove quotation marks from list. # Output summary report. outf = open(loc_main_outputs + "Report 3 - project summary.txt", "w+") outf.write("Drone Allometry Experiment: Summary Report" + '\n') outf.write("Andrew Cunliffe <*****@*****.**>" + '\n') outf.write("Generated on " + today + '\n' + '\n') outf.write( '---------------------------------------------------------------------------------' + '\n') outf.write( 'For the drone allometry project, focussing on seasonal biomass peaks, we have sampled ' + str(n_of_observations_peak) + ' individual harvest plots, from ' + str(n_of_sites_peak) + ' sites with ' + str(n_of_surveys_peak) + ' individual photogrammetric reconstructions' + '\n' + '\n') outf.write( str(sum_of_harvested_peak_biomass) + ' kg of aboveground biomass has been measured.' + '\n' + '\n') outf.write('Sampling was undertaken using ' + str(n_of_drones_peak) + ' types of drone: ' + str(list_of_drones_peak) + '.' + '\n' + '\n') outf.write('Sampling was undertaken using ' + str(n_of_cameras_peak) + ' types of camera: ' + str(list_of_cameras_peak) + '.' + '\n' + '\n') outf.write( 'For the largest taxa (' + str(species_dry_subsample) + '), freshly harvested biomass was weighed ' + 'in the field and representative sub-samples were then dried to determine the moisture content ' + 'for each partition (Cunliffe et al., 2020).' + '\n' + '\n') outf.write( 'In a few instances where plot infrastructure (e.g., posts or flags) were visible in the point cloud ' + '(n=' + str(n_of_edited_plots_peak) + ' plots), these points were manually assigned to a noise class ' + 'and excluded from canopy height calculations.' + '\n' + '\n') outf.write( 'On average (across all harvest plots), surveys were conducted ' + str(mean_offset_from_solar_noon) + ' minutes from solar noon. The maximum gap between the survey and solar noon was ' + str(max_offset_from_solar_noon) + ' minutes.' + '\n' + '\n') outf.write('Across peak biomass surveys, the mean wind speed was ' + str(wind_all_mean_peak) + ' m s-1 (ranging from ' + str(wind_all_min_peak) + ' to ' + str(wind_all_max_peak) + ' m s-1).' + '\n' + '\n') outf.write('Sampling has occurred in ' + str(n_of_igbp_peak) + ' IGBP classes. ') outf.write('Sampled taxa include ' + str(n_of_species_peak) + ' species:' + '\n') outf.write(str(n_of_shrub_plots_peak) + ' shrub plots' + '\n') outf.write(str(n_of_graminoid_plots_peak) + ' graminoid plots' + '\n') outf.write(str(n_of_succulent_plots) + ' succulent plots' + '\n') outf.write(str(n_of_forb_plots_peak) + ' forb plots' + '\n') outf.write(str(n_of_tree_plots_peak) + ' tree plots' + '\n') outf.write(str(n_of_succulent_plots_peak) + ' succulent plots' + '\n') outf.write(str(n_of_bryophyte_plots_peak) + ' bryophyte plots' + '\n') outf.write(str(n_of_fern_plots_peak) + ' fern plots' + '\n' + '\n') outf.write('Families: (N = ' + str(n_of_families_peak) + ')' + '\n') outf.write(str(list_of_families_peak_sep) + '\n' + '\n') outf.write('Species (N = ' + str(n_of_species_peak) + '):' + '\n') outf.write(str(list_of_species_peak_sep) + '\n' + '\n') outf.write( '---------------------------------------------------------------------------------' + '\n') outf.write( 'In total (including samples from non-seasonal biomass peak), we have sampled ' + str(n_of_observations) + ' individual harvest plots, from ' + str(n_of_sites) + ' sites, consisting of ' + str(n_of_surveys) + ' surveys contributed by ' + str(n_of_contributing_teams) + ' teams.' + '\n') outf.write('Sampling has occurred in ' + str(n_of_igbp) + ' IGBP classes.') outf.write('Sampled taxa include ' + str(n_of_species) + ' species:' + '\n') outf.write('In total ' + str(sum_of_harvested_biomass) + ' kg of dry biomass has been surveyed and then harvested.' + '\n') outf.write( 'Across all harvest plots and surveys, the mean wind speed was ' + str(wind_all_mean) + ' m s-1 (ranging from ' + str(wind_all_min) + ' to ' + str(wind_all_max) + ' m s-1).' + '\n') outf.write('Sampling was undertaken using ' + str(n_of_drones) + ' types of drone/sensor. (' + str(list_of_drones) + ')' + '\n') outf.write('\n') outf.write('Sampled taxa include:' + '\n') outf.write(str(n_of_shrub_plots) + ' shrub plots' + '\n') outf.write(str(n_of_graminoid_plots) + ' graminoid plots' + '\n') outf.write(str(n_of_succulent_plots) + ' succulent plots' + '\n') outf.write(str(n_of_forb_plots) + ' forb plots' + '\n') outf.write(str(n_of_fern_plots) + ' fern plots' + '\n') outf.write(str(n_of_tree_plots) + ' tree plots' + '\n') outf.write('\n') outf.write('Species (N = ' + str(n_of_species) + '):' + '\n') outf.write(str(list_of_species_sep) + '\n' + '\n') outf.write('Families: (N = ' + str(n_of_families) + ')' + '\n') outf.write(str(list_of_families_sep) + '\n' + '\n') outf.write('\n') outf.write( "---------------------------------------------------------------------------------" + '\n') outf.write('There are ' + str(n_of_surveys) + ' survey codes in use.' + '\n') outf.write( '(Date of survey (YYYYMMDD), Investigator initials (_XX), and three-digit site code (_XXX)), these are:' + '\n') outf.write(list_of_surveys_sep + '\n' + '\n') outf.write( "---------------------------------------------------------------------------------" + '\n') outf.write('END OF REPORT') outf.close() # Generate survey description table: # Subset only peak biomass surveys survey_summary = df[df.PeakBiomass] # Subset columns to keep. col_keep_list = [ 'survey_code', 'SiteLatitude', 'SiteLongitude', 'SiteElevation', 'MAT', 'MAP', 'KoppenCC', 'IGBP_class', 'wind_speed', 'sky_conditions_code', 'drone_platform', 'camera_sensor' ] survey_summary = pd.DataFrame(survey_summary[col_keep_list]) # Drop duplicate rows from dataframe. survey_summary = survey_summary.drop_duplicates(['survey_code'], keep='last') # Export summary of surveys. survey_summary.to_csv(loc_main_outputs + 'summary of surveys.csv', na_rep='NA', index=False) # noqa return df_pro
def test_Elevation_Above85Degrees(): d = datetime.datetime(2001, 6, 21, 13, 11, 0) assert sun.elevation(Observer(86, 77.2), d) == pytest.approx(23.102501151619506, abs=0.001)
if config.has_option('global', 'serial'): ports = config['global']['serial'].split(',') useSerial = True if debug: print('DEBUG: pySerial version: ' + serial.__version__) for port in ports: ser[port] = serial.Serial() ser[port].port = port ser[port].baudrate = 9600 ser[port].timeout = 1 for ind in ser: print('using COM port: ' + str(ser[ind].port)) # Initialize astronomical calculator and timezone finder sun.solar_depression = 'civil' location = Observer() timeZoneFinder = timezonefinder.TimezoneFinder() try: # infinite loop while True: # check if we are connected to iracing check_iracing() # if we are, then process data if state.ir_connected: loop() # sleep for 1 second # maximum you can use is 1/60 # cause iracing update data with 60 fps
def check_iracing(): if state.ir_connected and not (ir.is_initialized and ir.is_connected): state.ir_connected = False # don't forget to reset all your in State variables state.date_time = -1 state.tick = 0 state.latitude = -1 state.longitude = -1 state.elevation = -1 state.timezone = '' state.mqttdict = {} # Close serial port to buttonbox for ind in ser: if ser[ind].is_open: ser[ind].close() # we are shut down ir library (clear all internal variables) ir.shutdown() print('irsdk disconnected') mqtt_publish('state', 0) elif not state.ir_connected: # Check if a dump file should be used to startup IRSDK if config.has_option('global', 'simulate'): is_startup = ir.startup(test_file=config['global']['simulate']) print('starting up using dump file: ' + str(config['global']['simulate'])) else: is_startup = ir.startup() if debug: print('DEBUG: starting up with simulation') if is_startup and ir.is_initialized and ir.is_connected: state.ir_connected = True # Check need and open serial connection if config.has_option('global', 'serial'): for ind in ser: try: ser[ind].open() print('Serial port ' + ind + ' open') if debug: print('DEBUG: ' + str(ser[ind])) except Exception: print('Unable to open port ' + ser[ind].port + '. Serial communication is disabled') print('irsdk connected') if state.mqttConnected: mqtt_publish('state', 1) # Get geographical track information and track timezone for # astronomical calculations state.latitude = float( str(ir['WeekendInfo']['TrackLatitude']).rstrip(' m')) state.longitude = float( str(ir['WeekendInfo']['TrackLongitude']).rstrip(' m')) state.elevation = float( str(ir['WeekendInfo']['TrackAltitude']).rstrip(' m')) closestTimezone = timeZoneFinder.closest_timezone_at( lng=state.longitude, lat=state.latitude) state.timezone = pytz.timezone(closestTimezone) location = Observer(state.latitude, state.longitude, state.elevation) print('Location: ', location) locationInfo = LocationInfo(ir['WeekendInfo']['TrackCity'], ir['WeekendInfo']['TrackCountry'], state.timezone, state.latitude, state.longitude) print('LocationInfo: ', locationInfo)
#!/usr/bin/python3 from fronius2influx import Fronius2Influx from influxdb_client import InfluxDBClient from astral import Observer import pytz city = Observer(50.118890, 10.675173, 15) client = InfluxDBClient.from_config_file('../conf/config.ini') bucket = 'grafana' tz = pytz.timezone("Europe/Berlin") endpoints = [ 'http://10.0.0.210/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DataCollection=3PInverterData&DeviceId=1', 'http://10.0.0.210/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DataCollection=CommonInverterData&DeviceId=1', 'http://10.0.0.210/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DataCollection=MinMaxInverterData&DeviceId=1', 'http://10.0.0.210/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0', 'http://10.0.0.210/solar_api/v1/GetLoggerInfo.cgi' ] z = Fronius2Influx(client, endpoints, bucket, city, tz) z.IGNORE_SUN_DOWN = False z.run()
def observer(self) -> Observer: return Observer(self.latitude, self.longitude, 0.0)
# The order of the pixel colors - RGB or GRB. Some NeoPixels have red and green reversed! # For RGBW NeoPixels, simply change the ORDER to RGBW or GRBW. ORDER = neopixel.GRB #Create pixels object pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.03, auto_write=False, pixel_order=ORDER) #Create sun object for Oxford today longitude = -1.2577 lattitude = 51.7520 s = sun(Observer(lattitude, longitude), date=datetime.today(), dawn_dusk_depression=6.0) #Print sun times for info print((f'Dawn: {s["dawn"]}\n' f'Sunrise: {s["sunrise"]}\n' f'Noon: {s["noon"]}\n' f'Sunset: {s["sunset"]}\n' f'Dusk: {s["dusk"]}\n')) #Obtain sun times as UNIX timestamps dawn = s["dawn"].timestamp() sunrise = s["sunrise"].timestamp() sunset = s["sunset"].timestamp() dusk = s["dusk"].timestamp()
def add_solar_variable(obj, latitude=None, longitude=None, solar_angle=0., dawn_dusk=False): """ Calculate solar times depending on location on earth. Astral 2.2 is recommended for best performance and for the dawn/dusk feature as it seems like the dawn calculations are wrong with earlier versions. Parameters ---------- obj : act object ACT object latitude : str Latitude variable, default will look for matching variables in object longitude : str Longitude variable, default will look for matching variables in object solar_angle : float Number of degress to use for dawn/dusk calculations dawn_dusk : boolean If set to True, will add values 2 (dawn) and 3 (dusk) to the solar variable Returns ------- obj : act object ACT object """ variables = list(obj.keys()) # Get coordinate variables if latitude is None: latitude = [s for s in variables if "latitude" in s] if len(latitude) == 0: latitude = [s for s in variables if "lat" in s] if len(latitude) == 0: raise ValueError( "Latitude variable not set and could not be discerned from the data" ) if longitude is None: longitude = [s for s in variables if "longitude" in s] if len(longitude) == 0: longitude = [s for s in variables if "lon" in s] if len(longitude) == 0: raise ValueError( "Longitude variable not set and could not be discerned from the data" ) # Get lat/lon variables lat = obj[latitude[0]].values lon = obj[longitude[0]].values # Set the the number of degrees the sun must be below the horizon # for the dawn/dusk calculation. Need to do this so when the calculation # sends an error it is not going to be an inacurate switch to setting # the full day. if ASTRAL: astral.solar_depression = solar_angle else: a = astral.Astral() a.solar_depression = 0. # If only one lat/lon value then set up the observer location # for Astral. If more than one, it will get set up in the loop if lat.size == 1 and ASTRAL: loc = Observer(latitude=lat, longitude=lon) # Loop through each time to ensure that the sunrise/set calcuations # are correct for each time and lat/lon if multiple results = [] time = obj['time'].values for i in range(len(time)): # Set up an observer if multiple lat/lon if lat.size > 1: if ASTRAL: loc = Observer(latitude=lat[i], longitude=lon[i]) else: s = a.sun_utc(pd.to_datetime(time[i]), lat[i], lon[i]) elif ASTRAL is False: s = a.sun_utc(pd.to_datetime(time[i]), float(lat), float(lon)) # Get sunrise and sunset if ASTRAL: sr = sunrise(loc, pd.to_datetime(time[i])) ss = sunset(loc, pd.to_datetime(time[i])) else: sr = s['sunrise'] ss = s['sunset'] # Set longname longname = 'Daylight indicator; 0-Night; 1-Sun' # Check to see if dawn/dusk calculations can be performed before preceeding if dawn_dusk: try: if ASTRAL: dwn = dawn(loc, pd.to_datetime(time[i])) dsk = dusk(loc, pd.to_datetime(time[i])) else: if lat.size > 1: dsk = a.dusk_utc(pd.to_datetime(time[i]), lat[i], lon[i]) dwn = a.dawn_utc(pd.to_datetime(time[i]), lat[i], lon[i]) else: dsk = a.dusk_utc(pd.to_datetime(time[i]), float(lat), float(lon)) dwn = a.dawn_utc(pd.to_datetime(time[i]), float(lat), float(lon)) except ValueError: print( 'Dawn/Dusk calculations are not available at this location' ) dawn_dusk = False if dawn_dusk and ASTRAL: # If dawn_dusk is True, add 2 more indicators longname += '; 2-Dawn; 3-Dusk' # Need to ensure if the sunset if off a day to grab the previous # days value to catch the early UTC times if ss.day > sr.day: if ASTRAL: ss = sunset( loc, pd.to_datetime(time[i] - np.timedelta64(1, 'D'))) dsk = dusk( loc, pd.to_datetime(time[i] - np.timedelta64(1, 'D'))) else: if lat.size > 1: dsk = a.dusk_utc( pd.to_datetime(time[i]) - np.timedelta64(1, 'D'), lat[i], lon[i]) s = a.sun_utc( pd.to_datetime(time[i]) - np.timedelta64(1, 'D'), lat[i], lon[i]) else: dsk = a.dusk_utc( pd.to_datetime(time[i]) - np.timedelta64(1, 'D'), float(lat), float(lon)) s = a.sun_utc( pd.to_datetime(time[i]) - np.timedelta64(1, 'D'), float(lat), float(lon)) ss = s['sunset'] if dwn <= pd.to_datetime(time[i], utc=True) < sr: results.append(2) elif ss <= pd.to_datetime(time[i], utc=True) < dsk: results.append(3) elif not (dsk <= pd.to_datetime(time[i], utc=True) < dwn): results.append(1) else: results.append(0) else: if dwn <= pd.to_datetime(time[i], utc=True) < sr: results.append(2) elif sr <= pd.to_datetime(time[i], utc=True) < ss: results.append(1) elif ss <= pd.to_datetime(time[i], utc=True) < dsk: results.append(3) else: results.append(0) else: if ss.day > sr.day: if ASTRAL: ss = sunset( loc, pd.to_datetime(time[i] - np.timedelta64(1, 'D'))) else: s = a.sun_utc( pd.to_datetime(time[i]) - np.timedelta64(1, 'D'), lat, lon) ss = s['sunset'] results.append( int(not (ss < pd.to_datetime(time[i], utc=True) < sr))) else: results.append( int(sr < pd.to_datetime(time[i], utc=True) < ss)) # Add results to object and return obj['sun_variable'] = ('time', np.array(results), { 'long_name': longname, 'units': ' ' }) return obj
options.add_argument("-t", "--tzname", help="Timezone name") options.add_argument("latitude", type=float, help="Location latitude (float)") options.add_argument("longitude", type=float, help="Location longitude (float)") options.add_argument("elevation", nargs="?", type=float, default=0.0, help="Elevation in metres (float)") args = options.parse_args() loc = LocationInfo(args.name, args.region, args.tzname, args.latitude, args.longitude) obs = Observer(args.latitude, args.longitude, args.elevation) kwargs: Dict[str, Any] = {} kwargs["observer"] = obs if args.date is not None: try: kwargs["date"] = datetime.datetime.strptime(args.date, "%Y-%m-%d").date() except: # noqa: E0722 kwargs["date"] = datetime.date.today() sun_as_str = {} format_str = "%Y-%m-%dT%H:%M:%S" if args.tzname is None: tzinfo = pytz.utc
def test_Azimuth_Above85Degrees(): d = datetime.datetime(2001, 6, 21, 13, 11, 0) assert sun.azimuth(Observer(86, 77.2), d) == pytest.approx(276.2148, abs=0.001)