From 08d0c8121bf481005bba6363c18e42c20b2e6c3e Mon Sep 17 00:00:00 2001 From: Ola Date: Thu, 22 Oct 2026 15:44:44 +0200 Subject: [PATCH] domains: maritime: clamp haversine input, and small fixes in AIS test data speed --- .../domains/maritime/ais/data_generator.py | 7 ++++--- .../domains/maritime/ais/vessel_types.py | 4 ++-- src/damast/domains/maritime/math/spatial.py | 18 ++++++++++++------ .../domains/maritime/math/statistical.py | 8 ++++---- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/damast/domains/maritime/ais/data_generator.py b/src/damast/domains/maritime/ais/data_generator.py index 7f9e487..8b4a658 100644 --- a/src/damast/domains/maritime/ais/data_generator.py +++ b/src/damast/domains/maritime/ais/data_generator.py @@ -1,7 +1,7 @@ import datetime from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from logging import INFO, Logger, getLogger -from random import choice, randint, random +from random import choice, randint, random, uniform from typing import Any import pandas as pd @@ -92,7 +92,7 @@ def generate_trajectory(min_size: int, max_size: int) -> list[list[Any]]: last_day = datetime.datetime(year=2022, month=12, day=31) start_time = randint(0, int(last_day.timestamp())) - sog_start = random() * SpeedOverGround.min_value / SpeedOverGround.max_value + sog_start = uniform(0, 0.5) * SpeedOverGround.max_value cog_start = random() * CourseOverGround.min_value / CourseOverGround.max_value heading_start = cog_start @@ -111,6 +111,7 @@ def generate_trajectory(min_size: int, max_size: int) -> list[list[Any]]: start_time += random() * 120.0 sog_start += random() * 4 - 2 + sog_start = min(max(sog_start, SpeedOverGround.min_value), SpeedOverGround.max_value) cog_start += random() * 1 - 0.5 heading_start = cog_start + random() / 10.0 @@ -208,4 +209,4 @@ def generate_anchorage_type_data(self) -> DataFrame: ais_test_data = AISTestData(number_of_trajectories=args.number_of_trajectories) ais_test_data.dataframe.export_hdf5(args.output) - _log.info("Written: {args.output}") + _log.info(f"Written: {args.output}") diff --git a/src/damast/domains/maritime/ais/vessel_types.py b/src/damast/domains/maritime/ais/vessel_types.py index 1106de1..34d9bec 100644 --- a/src/damast/domains/maritime/ais/vessel_types.py +++ b/src/damast/domains/maritime/ais/vessel_types.py @@ -39,7 +39,7 @@ def get_types(cls) -> list[VesselType]: return klasses @classmethod - def get_types_as_str(cls) -> list[VesselType]: + def get_types_as_str(cls) -> list[str]: return [x.typename() for x in cls.get_types()] @staticmethod @@ -79,7 +79,7 @@ def by_id(cls, *, @classmethod def to_id(cls, *, - klass: str | VesselType = None) -> int: + klass: str | VesselType | None = None) -> int: """ Get the id for a klass name or class type of VesselType. diff --git a/src/damast/domains/maritime/math/spatial.py b/src/damast/domains/maritime/math/spatial.py index cd9ed7d..0fe5151 100644 --- a/src/damast/domains/maritime/math/spatial.py +++ b/src/damast/domains/maritime/math/spatial.py @@ -66,7 +66,7 @@ def reverse_bearing(lat_1: npt.NDArray[np.float64], lon_1: npt.NDArray[np.float64], distance: npt.NDArray[np.float64], bearing: npt.NDArray[np.float64], - earth_radius_in_km: float = EARTH_RADIUS_IN_KM) -> npt.NDArray[np.float64]: + earth_radius_in_km: float = EARTH_RADIUS_IN_KM) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """ Compute the position of an object, given its initial position, the initial bearing, and the distance it will travel. @@ -89,7 +89,7 @@ def reverse_bearing(lat_1: npt.NDArray[np.float64], @njit -def decdeg2dms(dd: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]: +def decdeg2dms(dd: npt.NDArray[np.float64]) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]: """ Convert decimal degrees (dd) to sexagesimal degrees (degrees, minutes, seconds) @@ -155,10 +155,16 @@ def great_circle_distance(lat_1: npt.NDArray[np.float64], :param lon_2: A sequence of longitudes (in degrees), :math:`\\lambda_2` :returns: The great circle distance between sets of points """ - lon_1, lat_1, lon_2, lat_2 = map(np.radians, [lon_1, lat_1, lon_2, lat_2]) - return 2 * earth_radius_in_km * np.arcsin(np.sqrt(np.power(np.sin((lat_1 - lat_2) / 2), 2) - + np.cos(lat_1) * np.cos(lat_2) * np.power(np.sin((lon_1 - lon_2) / 2), - 2))) + lon_1 = np.radians(lon_1) + lat_1 = np.radians(lat_1) + lon_2 = np.radians(lon_2) + lat_2 = np.radians(lat_2) + + a = np.sin((lat_1 - lat_2) / 2) ** 2 + b = np.cos(lat_1) * np.cos(lat_2) * np.sin((lon_1 - lon_2) / 2) ** 2 + c = np.minimum(a + b, 1.0) + + return 2 * earth_radius_in_km * np.arcsin(np.sqrt(c)) # Alias under the formula's common name - identical to great_circle_distance. diff --git a/src/damast/domains/maritime/math/statistical.py b/src/damast/domains/maritime/math/statistical.py index c3ddff5..aaa14e3 100644 --- a/src/damast/domains/maritime/math/statistical.py +++ b/src/damast/domains/maritime/math/statistical.py @@ -1,7 +1,7 @@ import warnings import numpy as np -import numpy.typing as npt +import pandas as pd __all__ = [ "N_sigma", @@ -9,9 +9,9 @@ ] -def N_sigma_limited(x: npt.NDArray[np.float64], +def N_sigma_limited(x: pd.Series, N: int, - p: float = 0.99) -> npt.NDArray[np.float64]: + p: float = 0.99) -> np.float64: """ Compute and return the N_sigma given a list without considering the :math:`100\\cdot(1-p)\\%` highest values @@ -26,7 +26,7 @@ def N_sigma_limited(x: npt.NDArray[np.float64], return x.loc[x < limit].median() + N * x.loc[x < limit].std() -def N_sigma(x: npt.NDArray[np.float64], N: int) -> npt.NDArray[np.float64]: +def N_sigma(x: pd.Series, N: int) -> np.float64: """ Compute and return the N_sigma given a list :param x: The input array