Skip to content

geography

GeoLocator

GeoLocator()

Stores a starting GPS point and azimuth, then computes pylon GPS/Lambert93 coordinates on demand.

The starting point must be set via set_starting_gps() or set_starting_lambert93() before calling get_gps() or get_lambert93(). No computed arrays are cached; every call to get_gps() / get_lambert93() recomputes from the stored starting point and the provided arrays.

Source code in src/mechaphlowers/entities/geography.py
253
254
255
256
def __init__(self) -> None:
    self._latitude_0: float | None = None
    self._longitude_0: float | None = None
    self._azimuth_0: float | None = None

get_gps

get_gps(
    line_angles_degrees: ndarray, span_length: ndarray
) -> tuple[ndarray, ndarray]

Compute GPS coordinates for all pylons.

Parameters:

Name Type Description Default

line_angles_degrees

ndarray

Line angle array in degrees, anti-clockwise.

required

span_length

ndarray

Span length array in meters (last value is NaN).

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: (latitudes, longitudes) in decimal degrees.

Source code in src/mechaphlowers/entities/geography.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
def get_gps(
    self,
    line_angles_degrees: np.ndarray,
    span_length: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    """Compute GPS coordinates for all pylons.

    Args:
        line_angles_degrees (np.ndarray): Line angle array in degrees, anti-clockwise.
        span_length (np.ndarray): Span length array in meters (last value is NaN).

    Returns:
        tuple[np.ndarray, np.ndarray]: (latitudes, longitudes) in decimal degrees.
    """
    self._check_gps_available()

    # Make a defensive copy so that downstream functions cannot mutate the caller's array.
    line_angles_copy = line_angles_degrees.copy()

    return get_gps_from_arrays(
        self._latitude_0,  # type: ignore[arg-type]
        self._longitude_0,  # type: ignore[arg-type]
        self._azimuth_0,  # type: ignore[arg-type]
        line_angles_copy,
        span_length,
    )

get_lambert93

get_lambert93(
    line_angles_degrees: ndarray, span_length: ndarray
) -> tuple[ndarray, ndarray]

Compute Lambert 93 coordinates for all pylons.

Parameters:

Name Type Description Default

line_angles_degrees

ndarray

Line angle array in degrees, anti-clockwise.

required

span_length

ndarray

Span length array in meters (last value is NaN).

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: (easting, northing) in Lambert 93 meters.

Source code in src/mechaphlowers/entities/geography.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
def get_lambert93(
    self,
    line_angles_degrees: np.ndarray,
    span_length: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    """Compute Lambert 93 coordinates for all pylons.

    Args:
        line_angles_degrees (np.ndarray): Line angle array in degrees, anti-clockwise.
        span_length (np.ndarray): Span length array in meters (last value is NaN).

    Returns:
        tuple[np.ndarray, np.ndarray]: (easting, northing) in Lambert 93 meters.
    """
    lats, lons = self.get_gps(line_angles_degrees, span_length)
    return gps_to_lambert93(lats, lons)

set_starting_gps

set_starting_gps(
    latitude_0: float, longitude_0: float, azimuth_0: float
) -> None

Set the starting GPS point and azimuth for the section.

Parameters:

Name Type Description Default

latitude_0

float

Latitude of the first support in decimal degrees.

required

longitude_0

float

Longitude of the first support in decimal degrees.

required

azimuth_0

float

Azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.

required
Source code in src/mechaphlowers/entities/geography.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def set_starting_gps(
    self,
    latitude_0: float,
    longitude_0: float,
    azimuth_0: float,
) -> None:
    """Set the starting GPS point and azimuth for the section.

    Args:
        latitude_0 (float): Latitude of the first support in decimal degrees.
        longitude_0 (float): Longitude of the first support in decimal degrees.
        azimuth_0 (float): Azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.
    """
    self._latitude_0 = latitude_0
    self._longitude_0 = longitude_0
    self._azimuth_0 = azimuth_0

set_starting_lambert93

set_starting_lambert93(
    easting: float, northing: float, azimuth_0: float
) -> None

Set the starting point from Lambert 93 coordinates and azimuth.

Converts the Lambert 93 easting/northing to GPS (WGS84) then stores the result.

Parameters:

Name Type Description Default

easting

float

Lambert 93 easting coordinate in meters.

required

northing

float

Lambert 93 northing coordinate in meters.

required

azimuth_0

float

Azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.

required
Source code in src/mechaphlowers/entities/geography.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def set_starting_lambert93(
    self,
    easting: float,
    northing: float,
    azimuth_0: float,
) -> None:
    """Set the starting point from Lambert 93 coordinates and azimuth.

    Converts the Lambert 93 easting/northing to GPS (WGS84) then stores the result.

    Args:
        easting (float): Lambert 93 easting coordinate in meters.
        northing (float): Lambert 93 northing coordinate in meters.
        azimuth_0 (float): Azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.
    """
    lat, lon = lambert93_to_gps(np.float64(easting), np.float64(northing))
    self.set_starting_gps(float(lat), float(lon), azimuth_0)

geo_info_from_gps

geo_info_from_gps(
    lats: ndarray, lons: ndarray
) -> SupportGeoInfo

Create a list of support geo info from a list of gps points. Args: gps_points: A list of gps points. Returns: A list of support geo info.

Source code in src/mechaphlowers/entities/geography.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def geo_info_from_gps(lats: np.ndarray, lons: np.ndarray) -> SupportGeoInfo:
    """
    Create a list of support geo info from a list of gps points.
    Args:
        gps_points: A list of gps points.
    Returns:
        A list of support geo info.
    """
    from mechaphlowers.data.geography.elevation import gps_to_elevation

    elevations = gps_to_elevation(lats, lons)
    lambert_93_tuples = gps_to_lambert93(lats, lons)
    distances = haversine(lats[:-1], lons[:-1], lats[1:], lons[1:], unit="deg")
    bearings = gps_to_bearing_two_points(
        lats[:-1], lons[:-1], lats[1:], lons[1:], unit="deg"
    )
    directions = bearing_to_direction(-bearings)

    return SupportGeoInfo(
        latitude=lats,
        longitude=lons,
        elevation=elevations,
        distance_to_next=distances,
        bearing_to_next=bearings,
        direction_to_next=directions,
        lambert_93=lambert_93_tuples,
    )

get_azimuth_from_gps

get_azimuth_from_gps(
    lats: ndarray,
    lons: ndarray,
    unit: Literal['rad', 'deg'] = 'rad',
) -> ndarray

Get azimuth of spans using gps coordinates of supports

Parameters:

Name Type Description Default

lats

ndarray

array of latitudes of the supports

required

lons

ndarray

array of longitudes of the supports

required

unit

Literal['rad', 'deg']

Select the unit to use for both inputs and output. Defaults to "rad"

'rad'

Returns:

Type Description
ndarray

np.ndarray: Bearing angle in degrees from north, anti clockwise (in radians by default)

Source code in src/mechaphlowers/entities/geography.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def get_azimuth_from_gps(
    lats: np.ndarray,
    lons: np.ndarray,
    unit: Literal["rad", "deg"] = "rad",
) -> np.ndarray:
    """Get azimuth of spans using gps coordinates of supports

    Args:
        lats (np.ndarray): array of latitudes of the supports
        lons (np.ndarray): array of longitudes of the supports
        unit (Literal["rad", "deg"]): Select the unit to use for both inputs and output. Defaults to "rad"

    Returns:
        np.ndarray: Bearing angle in degrees from north, anti clockwise (in radians by default)
    """

    return gps_to_bearing_two_points(
        lats[:-1], lons[:-1], lats[1:], lons[1:], unit
    )

get_azimuth_from_line_angles

get_azimuth_from_line_angles(
    line_angle: ndarray,
    first_span_azimuth: float,
    input_unit: str = 'deg',
    output_unit: str = 'deg',
) -> ndarray

Compute azimuth of all spans.

Parameters:

Name Type Description Default

line_angle

ndarray

line angle array in anticlockwise direction. Array comes from SectionArray.

required

first_span_azimuth

float

azimuth of the first span. Anticlockwise: 90° towards west

required

input_unit

str

unit of line_angle and line_angle. Defaults to "deg".

'deg'

output_unit

str

unit of the output. Defaults to "deg".

'deg'

Returns:

Type Description
ndarray

np.ndarray: array of azimuth. Length of array is number of supports.

Source code in src/mechaphlowers/entities/geography.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def get_azimuth_from_line_angles(
    line_angle: np.ndarray,
    first_span_azimuth: float,
    input_unit: str = "deg",
    output_unit: str = "deg",
) -> np.ndarray:
    """Compute azimuth of all spans.

    Args:
        line_angle (np.ndarray): line angle array in anticlockwise direction. Array comes from SectionArray.
        first_span_azimuth (float): azimuth of the first span. Anticlockwise: 90° towards west
        input_unit (str, optional): unit of line_angle and line_angle. Defaults to "deg".
        output_unit (str, optional): unit of the output. Defaults to "deg".

    Returns:
        np.ndarray: array of azimuth. Length of array is number of supports.
    """
    if len(line_angle) == 0:
        return np.array([])
    # first value of line_angles is set to 0 to avoid unexpected behaviour.
    # now azimuth is truly the orientation of the first span
    line_angle_copy = line_angle.copy()
    line_angle_copy[0] = 0.0
    azimuth = np.cumsum(line_angle_copy) + first_span_azimuth
    return Q_(azimuth, input_unit).to(output_unit).m

get_dist_and_angles_from_gps

get_dist_and_angles_from_gps(
    latitudes_deg: ndarray,
    longitudes_deg: ndarray,
    unit_output_angles: Literal[
        'rad', 'deg', 'grad'
    ] = 'deg',
) -> tuple[ndarray, ndarray]

Compute distances and angles between supports using latitudes and longitudes.

Parameters:

Name Type Description Default

latitudes_deg

ndarray

array of latitudes in decimal degrees

required

longitudes_deg

ndarray

array of longitudes in decimal degrees

required

unit_output_angles

Literal['rad', 'deg', 'grad']

unit of the output angles. Defaults to "deg".

'deg'

Raises:

Type Description
ValueError

If latitudes and longitudes arrays have different lengths, or unit_output_angles is not valid.

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: tuple (distance, angles) distance is in meters and angles is anti-clockwise

Source code in src/mechaphlowers/entities/geography.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def get_dist_and_angles_from_gps(
    latitudes_deg: np.ndarray,
    longitudes_deg: np.ndarray,
    unit_output_angles: Literal["rad", "deg", "grad"] = "deg",
) -> tuple[np.ndarray, np.ndarray]:
    """Compute distances and angles between supports using latitudes and longitudes.

    Args:
        latitudes_deg (np.ndarray): array of latitudes in decimal degrees
        longitudes_deg (np.ndarray): array of longitudes in decimal degrees
        unit_output_angles (Literal["rad", "deg", "grad"], optional): unit of the output angles. Defaults to "deg".

    Raises:
        ValueError: If latitudes and longitudes arrays have different lengths, or unit_output_angles is not valid.

    Returns:
        tuple[np.ndarray, np.ndarray]: tuple (distance, angles) distance is in meters and angles is anti-clockwise
    """
    if len(latitudes_deg) != len(longitudes_deg):
        raise ValueError("latitudes and longitudes must have the same length")
    if unit_output_angles not in ["rad", "deg", "grad"]:
        raise ValueError(
            "unit_output_angles must be one of 'rad', 'deg', 'grad'"
        )
    lats_rolled_rad = np.radians(latitudes_deg[1:])
    lons_rolled_rad = np.radians(longitudes_deg[1:])

    lats_rad = np.radians(latitudes_deg[:-1])
    lons_rad = np.radians(longitudes_deg[:-1])
    distances = haversine(lats_rad, lons_rad, lats_rolled_rad, lons_rolled_rad)
    distances = np.append(distances, np.nan)

    # first and last angles are not computed
    bearings_rad = gps_to_bearing_two_points(
        lats_rad, lons_rad, lats_rolled_rad, lons_rolled_rad
    )
    # convert bearing to angles relative between supports
    angles_rad = np.diff(bearings_rad)
    angles_rad = convert_angle_unsigned_to_signed(angles_rad)
    angles_rad = np.concatenate(([0], angles_rad, [0]))

    angles_correct_unit = (
        Q_(angles_rad, "rad").to(unit_output_angles).magnitude
    )
    return distances, angles_correct_unit

get_dist_and_angles_from_lambert

get_dist_and_angles_from_lambert(
    lambert_east: ndarray,
    lambert_north: ndarray,
    unit_output_angles: Literal[
        'rad', 'deg', 'grad'
    ] = 'deg',
) -> tuple[ndarray, ndarray]

Compute distances and angles between supports using lambert coordinates.

Parameters:

Name Type Description Default

lambert_east

ndarray

Lambert 93 Easting coordinate.

required

lambert_north

ndarray

Lambert 93 Northing coordinate.

required

unit_output_angles

Literal['rad', 'deg', 'grad']

unit of the output angles. Defaults to "deg".

'deg'

Raises:

Type Description
ValueError

If lambert east and north arrays have different lengths, or unit_output_angles is not valid.

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: tuple (distance, angles) distance is in meters and angles is anti-clockwise

Source code in src/mechaphlowers/entities/geography.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def get_dist_and_angles_from_lambert(
    lambert_east: np.ndarray,
    lambert_north: np.ndarray,
    unit_output_angles: Literal["rad", "deg", "grad"] = "deg",
) -> tuple[np.ndarray, np.ndarray]:
    """Compute distances and angles between supports using lambert coordinates.

    Args:
        lambert_east (np.ndarray): Lambert 93 Easting coordinate.
        lambert_north (np.ndarray): Lambert 93 Northing coordinate.
        unit_output_angles (Literal["rad", "deg", "grad"], optional): unit of the output angles. Defaults to "deg".

    Raises:
        ValueError: If lambert east and north arrays have different lengths, or unit_output_angles is not valid.

    Returns:
        tuple[np.ndarray, np.ndarray]: tuple (distance, angles) distance is in meters and angles is anti-clockwise
    """
    if len(lambert_east) != len(lambert_north):
        raise ValueError(
            "lambert_east and lambert_north must have the same length"
        )
    if unit_output_angles not in ["rad", "deg", "grad"]:
        raise ValueError(
            "unit_output_angles must be one of 'rad', 'deg', 'grad'"
        )
    latitudes_deg, longitudes_deg = lambert93_to_gps(
        lambert_east, lambert_north
    )

    return get_dist_and_angles_from_gps(
        latitudes_deg, longitudes_deg, unit_output_angles
    )

get_gps_from_arrays

get_gps_from_arrays(
    start_lat_deg: float,
    start_lon_deg: float,
    azimuth_deg: float,
    line_angles_degrees: ndarray,
    span_length: ndarray,
) -> tuple[ndarray, ndarray]

Gets arrays of line angle and span length, and starting point data.

Builds iteratively all the gps points using the input arrays.

Input and output are in degrees. This function converts in radians in order to use reverse_haversine_float()

span_length and line_angles_degrees come from SectionArray. Their data is based on support view. Therefore: - last value of span_length is np.nan - first and last value are only relevant for support orientation, and not considered for this computation

Parameters:

Name Type Description Default

start_lat_deg

float

latitude of the first point

required

start_lon_deg

float

longitude of the first point

required

azimuth_deg

float

azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.

required

line_angles_degrees

ndarray

line angle array (data from SectionArray), in degrees, anti-clockwise

required

span_length

ndarray

span length array (data from SectionArray)

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: (lat, lon) two arrays of GPS coordinates. Angles in degrees

Source code in src/mechaphlowers/entities/geography.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def get_gps_from_arrays(
    start_lat_deg: float,
    start_lon_deg: float,
    azimuth_deg: float,
    line_angles_degrees: np.ndarray,
    span_length: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
    """Gets arrays of line angle and span length, and starting point data.

    Builds iteratively all the gps points using the input arrays.

    Input and output are in degrees. This function converts in radians in order to use reverse_haversine_float()

    span_length and line_angles_degrees come from SectionArray. Their data is based on support view.
    Therefore:
    - last value of span_length is np.nan
    - first and last value are only relevant for support orientation, and not considered for this computation

    Args:
        start_lat_deg (float): latitude of the first point
        start_lon_deg (float): longitude of the first point
        azimuth_deg (float): azimuth of the first span in degrees, anti-clockwise. 0 means North, 90 means West.
        line_angles_degrees (np.ndarray): line angle array (data from SectionArray), in degrees, anti-clockwise
        span_length (np.ndarray): span length array (data from SectionArray)

    Returns:
        tuple[np.ndarray, np.ndarray]: (lat, lon) two arrays of GPS coordinates. Angles in degrees
    """
    current_lat_rad = np.radians(start_lat_deg)
    current_lon_rad = np.radians(start_lon_deg)
    lat_array_rad = [current_lat_rad]
    lon_array_rad = [current_lon_rad]

    bearings_rad = get_azimuth_from_line_angles(
        line_angles_degrees, azimuth_deg, input_unit="deg", output_unit="rad"
    )
    # Deliberate choice to not take into account the last angle: refers to the angle with the next section
    for index in range(len(line_angles_degrees) - 1):
        # Build the current point using the previous one, length and angle
        current_lat_rad, current_lon_rad = reverse_haversine_float(
            current_lat_rad,
            current_lon_rad,
            bearings_rad[index],
            span_length[index],
        )
        lat_array_rad.append(current_lat_rad)
        lon_array_rad.append(current_lon_rad)
    return np.degrees(lat_array_rad), np.degrees(lon_array_rad)