# Source code for mpas_tools.transects

```import numpy
from shapely.geometry import LineString, Point

[docs]def subdivide_great_circle(x, y, z, maxRes, earthRadius):
"""
Subdivide each segment of the transect so the horizontal resolution
approximately matches the requested resolution

Uses a formula for interpolating unit vectors on the sphere from
https://en.wikipedia.org/wiki/Slerp

Parameters
----------
x : numpy.ndarray
The Cartesian x coordinate of a transect, where the number of segments
is ``len(x) - 1``.  ``x``, ``y`` and ``z`` are of the same length.
y : numpy.ndarray
The Cartesian y coordinate of the transect
z : numpy.ndarray
The Cartesian z coordinate of the transect

maxRes : float
The maximum allowed spacing in m after subdivision

The radius of the Earth in m

Returns
-------
xOut : numpy.ndarray
The Cartesian x values of the transect subdivided into segments with
segment length at most ``maxRes``.  All the points in ``x``, ``y`` and
``z`` are guaranteed to be included.

yOut : numpy.ndarray
The Cartesian y values of the subdivided transect

zOut : numpy.ndarray
The Cartesian y values of the subdivided transect

dIn : numpy.ndarray
The distance along the transect before subdivision

dOut : numpy.ndarray
The distance along the transect after subdivision

"""

angularDistance = angular_distance(x=x, y=y, z=z)

nSegments = numpy.maximum(
(dx / maxRes + 0.5).astype(int), 1)

dIn = numpy.zeros(x.shape)
dIn[1:] = numpy.cumsum(dx)

frac = []
outIndices = []
delta = []
for index in range(len(dIn) - 1):
n = nSegments[index]
frac.extend(numpy.arange(0, n)/n)
outIndices.extend(index*numpy.ones(n, int))
delta.extend(angularDistance[index]*numpy.ones(n))
frac.append(1.)
outIndices.append(len(dIn) - 2)
delta.append(angularDistance[-1])

frac = numpy.array(frac)
delta = numpy.array(delta)
outIndices = numpy.array(outIndices)

a = numpy.ones(delta.shape)
b = numpy.zeros(delta.shape)

xOut = a*x[outIndices] + b*x[outIndices+1]
yOut = a*y[outIndices] + b*y[outIndices+1]
zOut = a*z[outIndices] + b*z[outIndices+1]

dOut = (-frac + 1.)*dIn[outIndices] + frac*dIn[outIndices+1]

return xOut, yOut, zOut, dIn, dOut

"""
Cartesian transect points to great-circle distance

Parameters
----------
x : numpy.ndarray
The Cartesian x coordinate of a transect
y : numpy.ndarray
The Cartesian y coordinate of the transect
z : numpy.ndarray
The Cartesian z coordinate of the transect

Returns
-------
distance : numpy.ndarray
The distance along the transect
"""
distance = numpy.zeros(x.shape)
for segIndex in range(len(x)-1):
transectv0 = Vector(x[segIndex], y[segIndex], z[segIndex])
transectv1 = Vector(x[segIndex+1], y[segIndex+1], z[segIndex+1])

distance[segIndex+1] = distance[segIndex] + \

return distance

[docs]def subdivide_planar(x, y, maxRes):
"""
Subdivide each segment of the transect so the horizontal resolution
approximately matches the requested resolution

Uses a formula for interpolating unit vectors on the sphere from
https://en.wikipedia.org/wiki/Slerp

Parameters
----------
x : numpy.ndarray
The planar x coordinate of a transect, where the number of segments
is ``len(x) - 1``

y : numpy.ndarray
The planar y coordinates of the transect, the same length as ``x``

maxRes : float
The maximum allowed spacing in m after subdivision

Returns
-------
xOut : numpy.ndarray
The x coordinate of the transect, subdivided into segments with length
at most ``maxRes``.  All the points in ``x`` are guaranteed to be
included.

yOut : numpy.ndarray
The y coordinate of the transect.  All the points in ``y`` are
guaranteed to be included.

dIn : numpy.ndarray
The distance along the transect before subdivision

dOut : numpy.ndarray
The distance along the transect after subdivision
"""

dx = numpy.zeros(len(x)-1)
for index in range(len(x)-1):
start = Point(x[index], y[index])
end = Point(x[index+1], y[index+1])
segment = LineString([start, end])
dx[index] = segment.length

nSegments = numpy.maximum(
(dx / maxRes + 0.5).astype(int), 1)

dIn = numpy.zeros(x.shape)
dIn[1:] = numpy.cumsum(dx)

frac = []
outIndices = []
for index in range(len(dIn) - 1):
n = nSegments[index]
frac.extend(numpy.arange(0, n)/n)
outIndices.extend(index*numpy.ones(n, int))
frac.append(1.)
outIndices.append(len(dIn) - 2)

frac = numpy.array(frac)
outIndices = numpy.array(outIndices)

xOut = (-frac + 1.)*x[outIndices] + frac*x[outIndices+1]
yOut = (-frac + 1.)*y[outIndices] + frac*y[outIndices+1]
dOut = (-frac + 1.)*dIn[outIndices] + frac*dIn[outIndices+1]

return xOut, yOut, dIn, dOut

"""Convert from lon/lat to Cartesian x, y, z"""

if degrees:
x = earth_radius * numpy.cos(lat) * numpy.cos(lon)
y = earth_radius * numpy.cos(lat) * numpy.sin(lon)
return x, y, z

[docs]def cartesian_to_lon_lat(x, y, z, earth_radius, degrees):
"""Convert from  Cartesian x, y, z to lon/lat"""
lon = numpy.arctan2(y, x)
if degrees:
return lon, lat

[docs]def angular_distance(x=None, y=None, z=None, first=None, second=None):
"""
Compute angular distance between points on the sphere, following:
https://en.wikipedia.org/wiki/Great-circle_distance

Parameters
----------
x : numpy.ndarray, optional
The Cartesian x coordinate of a transect, where the number of segments
is ``len(x) - 1``.  ``x``, ``y`` and ``z`` are of the same length and
all must be present if ``first`` and ``second`` are not provided.

y : numpy.ndarray, optional
The Cartesian y coordinate of the transect

z : numpy.ndarray, optional
The Cartesian z coordinate of the transect

first : mpas_tools.transect.Vector, optional
The start points of each segment of the transect, where the
``x``, ``y``, and ``z`` attributes of each vector are numpy.ndarray
objects.

second : mpas_tools.transect.Vector, optional
The end points of each segment of the transect

Returns
-------
angularDistance : numpy.ndarray
The angular distance (in radians) between segments of the transect.
"""
if first is None or second is None:
first = Vector(x[0:-1], y[0:-1], z[0:-1])
second = Vector(x[1:], y[1:], z[1:])

angularDistance = numpy.arctan2(_mag(_cross(first, second)),
_dot(first, second))

return angularDistance

[docs]def intersects(a1, a2, b1, b2):
"""
Based on https://stackoverflow.com/a/26669130/7728169
Determine if the great circle arc from ``a1`` to ``a2`` intersects that
from ``b1`` to ``b2``.

Parameters
----------
a1 : mpas_tools.transects.Vector
Cartesian coordinates of the end point of a great circle arc.
The types of the attributes ``x``, ``y``, and ``z`` must either be
``numpy.arrays`` of identical size for all 4 vectors (in which case
intersections are found element-wise), or scalars for
at least one of either ``a1`` and ``a2`` or ``b1`` and ``b2``.

a2 : mpas_tools.transects.Vector
Cartesian coordinates of the other end point of a great circle arc.

b1 : mpas_tools.transects.Vector
Cartesian coordinates of an end point of a second great circle arc.

b2 : mpas_tools.transects.Vector
Cartesian coordinates of the other end point of the second great circle
arc.

Returns
-------
intersect : numpy.ndarray
A boolean array of the same size as ``a1`` and ``a2`` or ``b1`` and
``b2``, whichever is greater, indicating if the particular pair of arcs
intersects
"""

[docs]def intersection(a1, a2, b1, b2):
"""
Based on https://stackoverflow.com/a/26669130/7728169
Find the intersection point as a unit vector between great circle arc from
``a1`` to ``a2`` and from ``b1`` to ``b2``.  The arcs should have already
have been found to intersect by calling ``intersects()``

Parameters
----------
a1 : mpas_tools.transects.Vector
Cartesian coordinates of the end point of a great circle arc.
The types of the attributes ``x``, ``y``, and ``z`` must either be
``numpy.arrays`` of identical size for all 4 vectors (in which case
intersections are found element-wise), or scalars for
at least one of either ``a1`` and ``a2`` or ``b1`` and ``b2``.

a2 : mpas_tools.transects.Vector
Cartesian coordinates of the other end point of a great circle arc.

b1 : mpas_tools.transects.Vector
Cartesian coordinates of an end point of a second great circle arc.

b2 : mpas_tools.transects.Vector
Cartesian coordinates of the other end point of the second great circle
arc.
Returns
-------
points : mpas_tools.transects.Vector
An array of Cartesian points *on the unit sphere* indicating where the
arcs intersect
"""
points = _cross(_cross(a1, a2), _cross(b1, b2))
s = numpy.sign(_det(a1, b1, b2))/_mag(points)
points = Vector(s*points.x,  s*points.y, s*points.z)
return points

[docs]class Vector:
"""
A class for representing Cartesian vectors with ``x``, ``y`` and ``z``
components that are either ``float`` or ``numpy.array`` objects of identical
size.
"""
[docs]    def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z

"""
Based on https://stackoverflow.com/a/26669130/7728169
Determines if the great circle segment determined by (a1, a2)
straddles the great circle determined by (b1, b2)

Parameters
----------
a1, a2, b1, b2 : Vector
Cartesian coordinates of the end points of two great circle arcs.
The types of the attributes ``x``, ``y``, and ``z`` must either be
``numpy.arrays`` of identical size for all 4 vectors (in which case
intersections are found element-wise), or scalars for
at least one of either the ``a``s or the ``b``s.

Returns
-------
A boolean array of the same size as the ``a``s or the ``b``s, whichever
is greater, indicating if the great circle segment determined by
(a1, a2) straddles the great circle determined by (b1, b2)
"""
return _det(a1, b1, b2) * _det(a2, b1, b2) < 0

def _dot(v1, v2):
"""The dot product between two ``Vector`` objects ``v1`` and ``v2``"""
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z

def _cross(v1, v2):
"""The cross product between two ``Vector`` objects ``v1`` and ``v2``"""
return Vector(v1.y * v2.z - v1.z * v2.y,
v1.z * v2.x - v1.x * v2.z,
v1.x * v2.y - v1.y * v2.x)

def _det(v1, v2, v3):
"""The determinant of the matrix defined by the three ``Vector`` objects"""
return _dot(v1, _cross(v2, v3))

def _mag(v):
"""The magnitude of the ``Vector`` object ``v``"""
return numpy.sqrt(_dot(v, v))
```