def det_profile(self, interpolate=True, resolution=5.0, itp_type="linear"): """Determines the elevation profile Searches the closest tiles in the topo data grid for both observer and endpoint and based on these two points the elevation profile is extracted using a :class:`LineOnGrid` object (which extracts altitudes from the topo data along the connection vector of the 2 points using 2D spline intepolation) Parameters ---------- interpolate : bool if True, the profile is interpolated to a certain horizontal resolution resolution : float desired grid resolution in m for interpolation. Interpolation is performed if :attr:`interpolate` is True and if the actual resolution of the topo data is smaller than input, else, nothing is done itp_type : str interpolation type (e.g. "linear", "cubic") Returns ------- """ data = self.topo_data idx_lon_0 = argmin(abs(data.lons - self.observer.lon.decimal_degree)) idx_lat_0 = argmin(abs(data.lats - self.observer.lat.decimal_degree)) idx_lon_1 = argmin(abs(data.lons - self.endpoint.lon.decimal_degree)) idx_lat_1 = argmin(abs(data.lats - self.endpoint.lat.decimal_degree)) self.line = l = LineOnGrid(idx_lon_0, idx_lat_0, idx_lon_1, idx_lat_1) z = l.get_line_profile(data.data) self._observer_topogrid = GeoPoint(data.lats[idx_lat_0],\ data.lons[idx_lon_0], topo_data = data) self._endpoint_topogrid = GeoPoint(data.lats[idx_lat_1],\ data.lons[idx_lon_1], topo_data = data) dists = linspace(0, self.dist_hor, l.length + 1) if interpolate: try: res0 = (dists[1] - dists[0]) * 1000 fac = int(ceil(res0 / resolution)) if fac > 1: fz = interp1d(dists, z, kind=itp_type) dists = linspace(0, self.dist_hor, l.length * fac) z = fz(dists) except Exception as e: warn("Failed to perform interpolation of retrieved elevation " "profile. Error msg: %s" %repr(e)) self.dists = dists self.profile = z
def find_viewing_direction(meas_geometry, draw_result=True): """Correct viewing direction using location of Etna SE crater. Defines location of Etna SE crater within images (is plotted into current plume onband image of dataset) and uses its geo location to retrieve the camera viewing direction :param meas_geometry: :class:`MeasGeometry` object """ # Position of SE crater in the image (x, y) se_crater_img_pos = [806, 736] # Geographic position of SE crater (extracted from Google Earth) # The GeoPoint object (geonum library) automatically retrieves the altitude # using SRTM data se_crater = GeoPoint(37.747757, 15.002643, name="SE crater") print("Retrieved altitude SE crater (SRTM): %s" % se_crater.altitude) # The following method finds the camera viewing direction based on the # position of the south east crater. new_elev, new_azim, _, basemap =\ meas_geometry.find_viewing_direction(pix_x=se_crater_img_pos[0], pix_y=se_crater_img_pos[1], # for uncertainty estimate pix_pos_err=100, geo_point=se_crater, draw_result=draw_result, update=True) # overwrite settings print("Updated camera azimuth and elevation in MeasGeometry, new values: " "elev = %.1f, azim = %.1f" % (new_elev, new_azim)) return meas_geometry, basemap
def create_test_data(self): """Create exemplary test data set""" source = GeoPoint(37.751005, 14.993435, name="Etna") instrument = GeoPoint(37.765755, 15.016696, name="Observatory") self.add_geo_points(source, instrument) self.set_borders_from_points() plume = GeoVector3D(azimuth=83, dist_hor=self.magnitude, elevation=0, anchor=source, name="plume") view_dir = GeoVector3D(azimuth=160, dist_hor=self.magnitude, elevation=8, anchor=instrument, name="cfov") self.add_geo_vectors(plume, view_dir)
def new_geo_point(self, *args, **kwargs): """Create new geo_point and add to collection :param **kwargs: see :class:`GeoPoint` for initiation info """ try: self.add_geo_point(GeoPoint(*args, **kwargs)) except (TypeError, ValueError): return except: raise Exception(print_exc())
def find_view_dir(geom): """Perform a correction of the viewing direction using crater in img. :param MeasGeometry geom: measurement geometry :param str which_crater: use either "ne" (northeast) or "se" (south east) :return: - MeasGeometry, corrected geometry """ # Use position of NE crater in image posx, posy = 1051, 605 # pixel position of NE crate in image # Geo location of NE crater (info from Google Earth) ne_crater = GeoPoint(37.754788, 14.996673, 3287, name="NE crater") geom.find_viewing_direction(pix_x=posx, pix_y=posy, pix_pos_err=100, geo_point=ne_crater, draw_result=True) return geom
def show_coordinate(geo_point=None, lat_pt=None, lon_pt=None, extend_km=10.0, *args, **kwargs): """Draw overview map for a given point Parameters ---------- geo_point : GeoPoint Geographical location around which overview map is drawn lat_pt : float Latitude of geographical location around which overview map is drawn (only considered if :attr:`geo_point` is invalid) lon_pt : float Longitude of geographical location around which overview map is drawn (only considered if :attr:`geo_point` is invalid) extend_km : float map extend in km around considered geolocation *args : non-keyword arguments passed to :func:`plot_2d` of the :class:`GeoSetup` instance that is created in order to draw the map Returns ------- Map instance of :class:`geonum.Map` """ if not isinstance(geo_point, GeoPoint): try: geo_point = GeoPoint(lat=lat_pt, lon=lon_pt) except: raise TypeError("Invalid input, please provide information " "about location of GeoPoint") stp = GeoSetup(points=[geo_point]) stp.set_borders_from_points(extend_km=extend_km) m = stp.plot_2d(*args, **kwargs) return m
def set_borders_from_points(self, extend_km=1, to_square=True): """Set range of setup (lower left and upper right coordinates) considering all points in this collection :param float extend_km: extend range from the outermost points by this number in km :param float to_square (True): extend the shorter base side to the size of the longer one (quadratic range) """ lats, lons = self._all_lats_lons() if not len(lats) > 0: #print "Borders could not be initiated, no objects found..." return False lat_ll, lon_ll, lat_tr, lon_tr = (nanmin(lats), nanmin(lons), nanmax(lats), nanmax(lons)) pll, ptr = GeoPoint(lat_ll, lon_ll, 0.0), GeoPoint(lat_tr, lon_tr, 0.0) if to_square: v = ptr - pll add = (abs(v.dx) - abs(v.dy)) / 2 if add > 0: #E/W extend (dx) is smaller than N/S extend (dy) pll = pll.offset(azimuth=180, dist_hor=add) ptr = ptr.offset(azimuth=0, dist_hor=add) else: pll = pll.offset(azimuth=270, dist_hor=-add) ptr = ptr.offset(azimuth=90, dist_hor=-add) self.set_geo_point( "ll", pll.offset(azimuth=-135, dist_hor=float(extend_km), name="ll")) self.set_geo_point( "tr", ptr.offset(azimuth=45, dist_hor=float(extend_km), name="tr")) return True
"""geonum example script 1 Introduction into the geonum base classes GeoPoint and GeoVector3D """ from geonum.base import GeoPoint from SETTINGS import OPTPARSE from numpy import testing as npt from matplotlib.pyplot import show ### Create 2 GeoPoint objects # Summit region of Mt. Etna p1 = GeoPoint(lat=37.751005, lon=14.993435, altitude=3264.0, name="Etna") # Position of volcanological observatory of Mt. Etna p2 = GeoPoint(37.765755, 15.016696, name="Observatory") # Print info (string represenation of both points") print(("Point1: %s, point 2: %s" % (p1, p2))) # Get and print the connection vector of both points connection_vector = p2 - p1 print(connection_vector) # Import script options (options, args) = OPTPARSE.parse_args() # If applicable, do some tests. This is done only if TESTMODE is active: # testmode can be activated globally (see SETTINGS.py) or can also be # activated from the command line when executing the script using the
class ElevationProfile(object): """Class for calculating elevation profiles The profile is calculated between two geo point objects using a provided topographic data grid which must cover the range spanned by both points Parameters ---------- topo_data : TopoData topography data object observer : :obj:`GeoPoint` or :obj:`LatLon` starting point of profile endpoint : :obj:`GeoPoint` or :obj:`LatLon` stop point of profile interpolate : bool if True, the profile is interpolated to a certain horizontal resolution resolution : float desired grid resolution in m for interpolation. Interpolation is performed if :attr:`interpolate` is True and if the actual resolution of the topo data is smaller than input, else, nothing is done itp_type : str interpolation type (e.g. "linear", "cubic") """ # ============================================================================= # def __new__(cls, topo_data, observer, endpoint, interpolate=True, # resolution=5.0, itp_type="linear"): # """These objects strictly can only be created with the right input, # # For input specs see __init__ method # """ # if not isinstance(topo_data, TopoData): # raise ValueError("ElevationProfile instance could not be created\n" # "Wrong input type: topo_data, need TopoData object...") # for p in [observer, endpoint]: # if not any([p.type() == x for x in ["LatLon", "GeoPoint"]]): # raise ValueError ("ElevationProfile instance could not be " # "created\nWrong input type: observer/endpoint, need GeoPoint " # "or LatLon object...") # elif not topo_data.includes_coordinate(p.lat.decimal_degree, # p.lon.decimal_degree): # raise ValueError ("ElevationProfile instance could not be " # "created\nWrong input point not covered by input topodata") # # return super(ElevationProfile, cls).__new__(cls, topo_data, observer, # endpoint, interpolate, # resolution, # itp_type) # ============================================================================= def __init__(self, topo_data, observer, endpoint, interpolate=True, resolution=5.0, itp_type="linear"): self.topo_data = topo_data self.observer = observer #: coordinate of observer (start of profile) self.endpoint = endpoint #: coordinate of endpoint of profile self._observer_topogrid = None #: closest to observer on topo data grid self._endpoint_topogrid = None #: closest to endpoint in topo data grid # In the following parameters the results will be stored self.line = None self.profile = None self.dists = None try: self.det_profile(interpolate, resolution, itp_type) except Exception as e: warn("Failed to compute elevation profile. Error msg: %s" %repr(e)) @property def dist_hor(self): """Returns the horizontal distance between the 2 points""" return (self._endpoint_topogrid - self._observer_topogrid).dist_hor @property def azimuth(self): """Returns the azimuth angle of the profile""" return (self._endpoint_topogrid - self._observer_topogrid).azimuth @property def resolution(self): """Get profile x (distance) resolution (averaged from distance array) .. note:: Only works if profile was already determined """ return abs((self.dists[1:] - self.dists[:-1]).mean()) @property def gradient(self): """Return gradient of profile Uses numpy function ``gradient`` """ return gradient(self.profile) @property def slope(self): """Returns slope of profile Determines dx and dy arrays and returns slope vector:: slope = dy / dx = gradient(self.profile) / (gradient(self.dists) * 1000.0) """ return gradient(self.profile) / (gradient(self.dists) * 1000) def slope_angles(self, decimal_degrees=True): """Returns slope angle of profile (in each sample point) :param bool decimal_degrees: rad or degrees (default True) """ a = tan(self.slope) if decimal_degrees: a = rad2deg(a) return a def slope_angle(self, dist): """Returns slope angle of profile at input distance :param float dist: distance in km """ idx = argmin(abs(self.dists - dist)) return self.slope_angles()[idx] def det_profile(self, interpolate=True, resolution=5.0, itp_type="linear"): """Determines the elevation profile Searches the closest tiles in the topo data grid for both observer and endpoint and based on these two points the elevation profile is extracted using a :class:`LineOnGrid` object (which extracts altitudes from the topo data along the connection vector of the 2 points using 2D spline intepolation) Parameters ---------- interpolate : bool if True, the profile is interpolated to a certain horizontal resolution resolution : float desired grid resolution in m for interpolation. Interpolation is performed if :attr:`interpolate` is True and if the actual resolution of the topo data is smaller than input, else, nothing is done itp_type : str interpolation type (e.g. "linear", "cubic") Returns ------- """ data = self.topo_data idx_lon_0 = argmin(abs(data.lons - self.observer.lon.decimal_degree)) idx_lat_0 = argmin(abs(data.lats - self.observer.lat.decimal_degree)) idx_lon_1 = argmin(abs(data.lons - self.endpoint.lon.decimal_degree)) idx_lat_1 = argmin(abs(data.lats - self.endpoint.lat.decimal_degree)) self.line = l = LineOnGrid(idx_lon_0, idx_lat_0, idx_lon_1, idx_lat_1) z = l.get_line_profile(data.data) self._observer_topogrid = GeoPoint(data.lats[idx_lat_0],\ data.lons[idx_lon_0], topo_data = data) self._endpoint_topogrid = GeoPoint(data.lats[idx_lat_1],\ data.lons[idx_lon_1], topo_data = data) dists = linspace(0, self.dist_hor, l.length + 1) if interpolate: try: res0 = (dists[1] - dists[0]) * 1000 fac = int(ceil(res0 / resolution)) if fac > 1: fz = interp1d(dists, z, kind=itp_type) dists = linspace(0, self.dist_hor, l.length * fac) z = fz(dists) except Exception as e: warn("Failed to perform interpolation of retrieved elevation " "profile. Error msg: %s" %repr(e)) self.dists = dists self.profile = z def get_altitudes_view_dir(self, elev_angle, view_above_topo_m=1.5): """Get vector containing altitudes for a viewing direction The viewing direction is specified by the azimuth angle of the connection vector between observer and endpoint, the elevation angle needs to be specified via the input parameters, and the start point (first elevation value) corresponds to the altitude at the observer position plus an offset in m which can be specified. :param float elev_angle: elevation angle of viewing direction :param float view_above_topo_m (1.5): altitude offset of start point in m :return: - vector with altitude values (same length as ``self.profile``) """ return (1000 * tan(radians(elev_angle)) * self.dists + self.profile[0] + view_above_topo_m) def find_horizon_elev(self, elev_start=0.0, elev_stop=60.0, step_deg=0.1, **kwargs): """Find first elevation angle which does not intersect with topo :param float elev_start: start search elevation angle :param float elev_stop: stop search elevation angle :param float step_deg: angle step for search (coarser is faster) :param **kwargs: additional keyword agruments passed to :func:`get_first_intersection` """ elevs = arange(elev_start, elev_stop + step_deg, step_deg) elev_sects = [] dists_sects = [] for elev in elevs: (dist, dist_err, intersect, view_elevations, _) = self.get_first_intersection(elev, **kwargs) if dist is None: return elev, elev_sects, dists_sects else: dists_sects.append(dist), elev_sects.append(elev) raise Exception("Unexpected exception..") def get_first_intersection(self, elev_angle, view_above_topo_m=1.5, min_dist=None, local_tolerance=3, plot=False): """Finds first intersection of a viewing direction with topography Start point of the viewing vector is the observer position (or more accurately, the center position of the closest topography tile in the topography data set). The relative altitude of the start point with respect to the topography altitude at the observer position can be controlled on input (arg ``view_above_topo_m``) as well as the elevation angle of the viewing direction. The azimuth angle corresponds to the profile azimuth (i.e. azimuth between observer position and endpoint of this profile). The signal analysed for finding the intersection is a vector containing relative elevation positions of the viewing direction with respect to the profile altitude:: diff_signal = self.get_altitudes_view_dir - self.profile The first intersection of the viewing direction with the topography is identified by the first zero crossing of this ``diff_signal``. :param float elev_angle: elevation angle of viewing direction (in decimal degrees) :param int view_above_topo_m: altitude offset of start point (``observer``) in m (default: 1.5) :param float min_dist: minimum distance (in km) of first considered intersection with topography from observer. If None, use 1% of distance between ``observer`` and ``endpoint``. :param float local_tolerance: tolerance factor to estimate distance uncertainty based on topo grid resolution (default: 3) :param bool plot: creates a plot of the profile including the intersection information """ if min_dist == None: min_dist = self.dist_hor*.01 max_diff = self.resolution * 1000 view_elevations = self.get_altitudes_view_dir(elev_angle, view_above_topo_m) #: determine the difference signal diff_signal = view_elevations - self.profile #: First condition: consider only points in certain distance from observer cond1 = self.dists > min_dist #: Second condition: consider only "close to zero" points (this might be #: redundant, I'll leave it for now because it works) cond2 = abs(diff_signal) < max_diff #: create array with all distances matching the 2 conditions dists_0 = self.dists[cond1 * cond2] dist, dist_err, intersect = None, None, None #: relax condition 2 if nothing was found if not len(dists_0) > 0: max_diff = self.resolution * 1000 * local_tolerance cond2 = abs(diff_signal) < max_diff dists_0 = self.dists[cond1 * cond2] try: #: get all diff vals matching the 2 conditions diff_vals = diff_signal[cond1 * cond2] #: get the index of the first zero crossing first_idx = where(diff(sign(diff_vals)))[0][0] #: get distance and local tolerance value dist, tol = dists_0[first_idx], self.resolution * local_tolerance #: make mask to access all distances within tolerance range cond4 = logical_and(dist - tol <= dists_0, dist + tol >= dists_0) #: estimate distance error from standard deviation of all #: distances within tolerance range dist_err = dists_0[cond4].std() #: create geopoint corresponding to intersection intersect = self._observer_topogrid.offset(self.azimuth, dist) #: set the altitude of this point (retrieved from profile) intersect.altitude = self.get_altitude_at_distance(dist) # / 1000. except IndexError as e: print(("No intersections could be detected, err: %s" %repr(e))) ax = None if plot: ax = self._plot_intersect_search_result(view_elevations, dist) if dist == None: dist = nan ax.set_title("Azim: %.1f, Elev: %.1f, Intersect @ " "dist =%.1f km" %(self.azimuth, elev_angle, dist)) return dist, dist_err, intersect, view_elevations, ax def _plot_intersect_search_result(self, view_elevations, dist=None): ax = self.plot() ax.plot(self.dists, view_elevations, label = "Viewing direction") try: ax.axvline(dist, ls = "--", label = "Intersection") except: pass ax.legend(loc="best", fancybox=True, framealpha=0.4) return ax def get_altitude_at_distance(self, dist): """Returns altitude at a ceratain distance from observer :param float dist: horizontal distance from obsever along profile """ idx = argmin(abs(self.dists - dist)) return self.profile[idx] @property def start_point(self): """Return position of observer""" return self.observer @property def min(self): """Return minimum altitude in profile""" return nanmin(self.profile) @property def max(self): """Return maximum altitude in profile""" return nanmax(self.profile) @property def alt_range(self): """Return altitude range of profile """ return self.max - self.min def __call__(self, dist): """Returns altitude at a certain distance :param float dist: distance in km """ return self.get_altitude_at_distance(dist) def plot(self, ax = None): """Plot the profile into an axis object :param ax: matplotlib axis object """ if ax is None: fig, ax = subplots(1,1) ax.fill_between(self.dists, self.profile, facecolor="#994d00", alpha=0.20) ax.set_xlabel("Distance [km]") ax.set_ylabel("Altitude [m]") ax.set_ylim([self.min - .1 * self.alt_range, self.max + .1 * self.alt_range]) return ax
def test_GeoPoint(): """Test basic arithmetic operations on GeoPoints.""" assert (GeoPoint(1, 1))