Skip to content

Instantly share code, notes, and snippets.

@GISConsortium
Last active March 1, 2022 06:27
Show Gist options
  • Save GISConsortium/c15e2afc1ec3eba04614cc375575defc to your computer and use it in GitHub Desktop.
Save GISConsortium/c15e2afc1ec3eba04614cc375575defc to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File: i_space_assignment.py
# Description: This program provides the elevation angle and distance of horizon from an observer
# Developer Name: Nitin Kandpal
# Date Created: 28/02/2022
import pandas as pd
import logging
import math
import os
import sys
class Log:
"""
This class logs the error, warning and info produced during execution of i_space_assignment code.
The write_log function is static function of this class, and returns the logger,
log is created inside the current working directory.
"""
@staticmethod
def write_log():
logger = logging.getLogger('assignment')
log_dir = os.path.join(os.getcwd(), 'log')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
log_file_path = os.path.join(log_dir, 'assignment.log')
# Create handlers
error_handler = logging.FileHandler(log_file_path)
error_handler.setLevel(logging.DEBUG)
# Define format for logging and add it to handlers
error_string_format = logging.Formatter('%(asctime)s - %(lineno)s - %(levelname)s - %(message)s')
error_handler.setFormatter(error_string_format)
logger.addHandler(error_handler)
return logger
class PlanetUtility:
"""
This class is for calculating elevation angle and distance to horizon from a point of observation.
The compute_horizon_distance_from_observer function is the public interface for this class.
The instance variable planet_radius_meters stores the radius of planetary feature in meters.
"""
def __init__(self, radius):
"""
This is a constructor that assigns the object variable with the radius
of the planetary feature.
Parameters
----------
radius : float/int
radius of the planetary feature in meters.
Returns
-------
None.
"""
self.planet_radius_meters = radius
@staticmethod
def _calculate_elevation_angle(series):
"""
Parameters
----------
series : pandas series
dataframe containing perpendicular distance and base w.r.t. observer
Returns
-------
elevation_angle : float/int
Elevation angle in degrees.
"""
perpendicular = series[2]
base = series[1]
radian = math.atan(perpendicular / base)
elevation_angle = PlanetUtility._convert_radian_2_degree(radian)
return elevation_angle
@staticmethod
def _convert_degree_2_radians(angle):
"""
Parameters
----------
angle : float/int
angle in degrees.
Returns
-------
angle_radian : float/int
angle in radian.
"""
angle_radian = math.radians(angle)
return angle_radian
@staticmethod
def _convert_radian_2_degree(radian):
"""
Parameters
----------
radian : float/int
Angle in radian.
Returns
-------
float/int
Angle in degree.
"""
angle_degree = radian * (180 / math.pi)
return angle_degree
@staticmethod
def _calculate_hypotenuse(perpendicular, base):
"""
Parameters
----------
perpendicular : float/int
Length of the perpendicular edge of the triangle.
base : float/int
Length of the base of the triangle.
Returns
-------
hypotenuse : float/int
Length of the hypotenuse of the triangle.
"""
hypotenuse = math.sqrt(perpendicular ** 2 + base ** 2)
return hypotenuse
def __calculate_distance_of_visible_horizon(self):
"""
This function provides the distance of horizon
from object standing 1 meter above the ground
using pythagoras theorem. It will provide the
distance within which the elevation need to
be checked.
Returns
-------
horizon_distance : float
Distance to the horizon from an object standing 1 m above the
ground.
"""
horizon_distance = math.sqrt((self.planet_radius_meters + 1) ** 2 - self.planet_radius_meters ** 2)
return math.ceil(horizon_distance)
def compute_horizon_distance_from_observer(self, elevation_file_path, index_of_observer=0,
sampling_distance=350, height_observer=3):
"""
This function calculates the minimum elevation angle needed to view space,
it also calculates the distance of horizon from the observer.
Parameters
----------
elevation_file_path : string
Input path for the elevation file, it has a single column that
contains series of lunar elevations taken along a straight line in
Mare Frigoris.
index_of_observer : integer, optional
It is the row number at which the height of observer is present in
the provided elevation file. The default is 0.
sampling_distance : int/float, optional
It is the distance interval in which the elevations are measured. Its
unit is in meters. The default is 350.
height_observer : int/float, optional
Height of the observer above the ground. The default is 3.
Returns
-------
distance_to_horizon : float/int
calculate the distance of horizon from the observer
"""
elev_profile = pd.read_csv(elevation_file_path, header=None,
skiprows=index_of_observer, names=["elevation"])
'''processing the elevation points that are at a distance of visible
horizon from the observer, and to ensure accuracy we will
take distance buffer of +1000m'''
distance_visible_horizon = self.__calculate_distance_of_visible_horizon() + 1000
# assigning distance from observer to each elevation profile
lst_distance_from_observer = []
for i in range(0, len(elev_profile)):
lst_distance_from_observer.append(i * sampling_distance)
elev_profile["distance_from_observer"] = lst_distance_from_observer
# getting the elevation profile of the observer
elev_of_ground = elev_profile.loc[elev_profile["distance_from_observer"] == 0, 'elevation'][0]
# updating the elevation profile of the observer
elev_of_observer = elev_of_ground + height_observer
elev_profile.loc[(elev_profile["distance_from_observer"] == 0), 'elevation'] = elev_of_observer
# identifying the difference in the elevation w.r.t observer
elev_profile['elev_diff_observer'] = elev_profile['elevation'].to_list() - elev_of_observer
'''filtering out the features that are within the distance of the visible horizon
and their height is more than the observer height'''
elev_profile = elev_profile.loc[(elev_profile["distance_from_observer"] <= distance_visible_horizon) & (
elev_profile['elev_diff_observer'] > 0)]
# calculating the elevation angle
elev_profile["elev_angle"] = elev_profile.apply(PlanetUtility._calculate_elevation_angle, axis=1)
# Identifying the feature within the visible horizon and has the highest elevation angle
target_elev_angle = \
elev_profile.loc[elev_profile["elev_angle"] == max(elev_profile["elev_angle"]), "elev_angle"].to_list()[0]
target_perpendicular_dist = \
elev_profile.loc[
elev_profile["elev_angle"] == max(elev_profile["elev_angle"]), "elev_diff_observer"].to_list()[
0]
target_dist_from_observer = elev_profile.loc[
elev_profile["elev_angle"] == max(elev_profile["elev_angle"]), "distance_from_observer"].to_list()[0]
distance_to_horizon = PlanetUtility._calculate_hypotenuse(target_perpendicular_dist, target_dist_from_observer)
print("Elevation angle of horizon :{elev_angle} degree;Distance to horizon :{distance_horizon} meters".format(
distance_horizon=round(distance_to_horizon, 2), elev_angle=round(target_elev_angle, 2)))
return target_elev_angle, distance_to_horizon
if __name__ == "__main__":
logger = Log.write_log()
try:
moon = PlanetUtility(radius=1737400)
print(moon.compute_horizon_distance_from_observer(elevation_file_path = r"C:\Users\nitin\Downloads\I-SPACE\elevationProfile.txt"))
#Elevation angle of horizon :0.24 degree;Distance to horizon :2800.02 meters
except Exception as ex:
print(ex)
logger.error(ex)
sys.exit(ex)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment