Source code for avni.mapping.ellipsoidal

#!/usr/bin/env python

#####################  IMPORT STANDARD MODULES   #########################

# python 3 compatibility
from __future__ import absolute_import, division, print_function

import multiprocessing
from joblib import Parallel, delayed
import numpy as np
import typing as tp
from math import cos, sin, tan, atan, atan2, degrees, radians

####################### IMPORT AVNI LIBRARIES  ###########################

from ..tools.trigd import atand,tand
from ..tools.common import convert2nparray
from ..f2py import ddelazgc # geolib library from NSW
from .. import constants

##########################################################################

[docs]def get_distaz(eplat: tp.Union[float,list,tuple,np.ndarray], eplon: tp.Union[float,list,tuple,np.ndarray], stlat: tp.Union[float,list,tuple,np.ndarray], stlon: tp.Union[float,list,tuple,np.ndarray], num_cores: int = 1) -> (tp.Union[np.ndarray,float], tp.Union[np.ndarray,float],tp.Union[np.ndarray,float]): """Get the distance and azimuths between pairs of positions in geographic coordinates This function first converts the queried station and source locations to geocentric coordinates. In practice, we projects all points from an ellipsoid of flatness (f) to a sphere of equatorial radius (a_e) though the geocentric conversion factor (W or geoco below). This is also called the parametric or reduced latitude conversion, introduced by Legendre and Bessel who solved problems for geodesics on the ellipsoid by transforming them to an equivalent problem for spherical geodesics by using this smaller geocentric latitude. The spherical law of cosines formula is then used to calculate distances on this spherical geodesic of radius a_e. This procedure stretches the angular distances between adjacent geographic latitudes nearer to the poles and equator, which imitates the behavior in an ellipsoid where adjacent latitudes become finely spaced. The equatorial radius is used instead of mean radius (R) for conversion of distances to km since this conversion is strictly valid for a sphere of radius a_e and in order to obtain accurate distances near the equator. Parameters ---------- eplat : tp.Union[float,list,tuple,np.ndarray] Latitudes of source location(s) in Geographic Coordinates eplon : tp.Union[float,list,tuple,np.ndarray] Longitudes of source location(s) in Geographic Coordinates stlat : tp.Union[float,list,tuple,np.ndarray] Latitudes of station location(s) in Geographic Coordinates stlon : tp.Union[float,list,tuple,np.ndarray] Longitudes of station location(s) in Geographic Coordinates num_cores : int, optional Number of cores to use in the calculation, by default 1 Returns ------- delta,azep,azst A tuple with elements as distance in degrees, azimuth from source(s), and backazimuth from station(s). :Authors: Raj Moulik (moulik@caa.columbia.edu) :Last Modified: 2023.02.16 5.00 """ geoco = constants.geoco.magnitude eplat = convert2nparray(eplat) eplon = convert2nparray(eplon) stlat = convert2nparray(stlat) stlon = convert2nparray(stlon) if not (len(stlat) == len(stlon) == len(eplat) == len(eplon)): raise ValueError('latitude and longitude need to be of same length') if len(eplat) > 1: # if the input is a list loop delta=[];azep=[];azst=[] # Standard checks on number of cores avail_cores = multiprocessing.cpu_count() if num_cores > avail_cores: raise ValueError("Number of cores requested ("+str(num_cores)+") is higher than available ("+str(avail_cores)+")") # Crate list of tuples of job arguments and pass to parallel using a helper routine job_args = [(atand(geoco*tand(item_lat)),eplon[jj],atand(geoco*tand(stlat[jj])),stlon[jj]) for jj, item_lat in enumerate(eplat)] temp=Parallel(n_jobs=num_cores)(delayed(delazgc_helper)(ii) for ii in job_args) for il in temp: delta.append(il[0]);azep.append(il[1]);azst.append(il[2]) else: elat=atand(geoco*tand(eplat)) elon=eplon slat=atand(geoco*tand(stlat)) slon=stlon delta,azep,azst = ddelazgc(elat,elon,slat,slon) return delta,azep,azst
[docs]def delazgc_helper(args): """A helper function to parallelize ddelazgc""" return ddelazgc(*args)
[docs]def geographic_to_geocentric(latin: float) -> float: """Convert a geographic latitude to geocentric latitude Parameters ---------- latin : float Input latitude in geographic coordinate Returns ------- float Output geocentric latitude :Authors: Raj Moulik (moulik@caa.columbia.edu) :Last Modified: 2023.02.16 5.00 """ xlat=latin fac = constants.geoco.magnitude theta = radians(90.0-xlat) theta = np.pi/2.-atan2(fac*cos(theta),sin(theta)) latout=90.0-degrees(theta) return latout
[docs]def geocentric_to_geographic(latin: float) -> float: """Convert a geocentric coordinate to geographic coordinate Parameters ---------- latin : float Input latitude in geocentric coordinate Returns ------- float Output geographic latitude :Authors: Raj Moulik (moulik@caa.columbia.edu) :Last Modified: 2023.02.16 5.00 """ xlat=latin fac = constants.geoco.magnitude if xlat != 0.: theta = radians(90.0-xlat) theta = atan(fac/tan(np.pi/2.-theta)) latout=90.0-degrees(theta) else: latout=xlat if xlat < 0.: latout = latout - 180. return latout
[docs]def inpolygon(latitude: tp.Union[float,list,tuple,np.ndarray], longitude: tp.Union[float,list,tuple,np.ndarray], polygon_latitude: tp.Union[list,tuple,np.ndarray], polygon_longitude: tp.Union[list,tuple,np.ndarray], num_cores: int = 1, orientation: str = 'anti-clockwise', threshold: float = 1E-6) -> tp.Union[np.ndarray,bool]: """Finds whether a (set of) point(s) is(are) within a closed polygon Parameters ---------- latitude,longitude : tp.Union[float,list,tuple,np.ndarray] Set of queried locations in Geographic Coordinates polygon_latitude,polygon_longitude : tp.Union[list,tuple,np.ndarray] Closed points that define the polygon. First and last points need to be the same. num_cores : int, optional Number of cores to use for the calculations, by default 1 orientation : str, optional clockwise or anti-clockwise orientation of points specified above, by default 'anti-clockwise' threshold : float, optional Limit to which the sum of azimuth check to (-)360 degrees is permitted to be defined as within the polygon, by default 1E-6 Returns ------- tp.Union[np.ndarray,bool] Logical array of bool(s) containing the same number of elements as latitude/longitude :Authors: Raj Moulik (moulik@caa.columbia.edu) :Last Modified: 2023.02.16 5.00 """ # convert to numpy arrays. Various checks latitude = convert2nparray(latitude) longitude = convert2nparray(longitude) if len(latitude) != len(longitude): raise ValueError('latitude and longitude need to be of same length') polylat = convert2nparray(polygon_latitude) polylon = convert2nparray(polygon_longitude) if len(polylat) != len(polylon): raise ValueError('polygon latitude and longitude need to be of same length') if (polygon_latitude[-1] != polygon_latitude[0]) or (polygon_longitude[-1] != polygon_longitude[0]): raise ValueError('polygon should be closed and therefore have same start and end points') nvertices = len(polylon) if orientation not in ['clockwise','anti-clockwise']: raise ValueError('orientation needs to be clockwise or anti-clockwise') # define the output array; assume everything is outside within = np.zeros_like(longitude,dtype=bool) # loop over all queries points for ii,lat in enumerate(latitude): lon = longitude[ii] # Get the azimuth of this location to all vertices of the polygon dist,azep,azst = get_distaz(np.repeat(lat,nvertices),np.repeat(lon,nvertices),polylat,polylon,num_cores=num_cores) # Since the polygons are anti-clockwise, check for -360 instead of 360 # 360 gives the plate for the anitpodal location in this case # Also 0 means the same as 360/-360 so that is also checked angle=0. for jj, azimuth in enumerate(azep): if jj > 0: add = azimuth - azold if add > 180.: add=add-360. if add < -180.: add=add+360. angle=angle+add azold = azimuth if orientation == 'anti-clockwise': if abs(angle+360) <= threshold or abs(angle) <= threshold: within[ii] = True elif orientation == 'clockwise': if abs(angle-360) <= threshold or abs(angle) <= threshold: within[ii] = True return within[0] if len(within)==1 else within