Source code for subnautica_carthography.positioning

"""
Module containing code to determine positions
"""
import pandas as pd
import numpy as np
import lmfit

_beacons = [
    ("LP5", 0, 0, 0),
    ("N1", 0, 1000, 0),
    ("E1", 1000, 0, 0),
    ("S1", 0, -1000, 0),
    ("W1", -1000, 0, 0),
]

# this is a default set of beacons. This module variable will be overwritten when used
# in conjunction with the datalogger.

default_beacons = pd.DataFrame(_beacons, columns=["beacon", "x", "y", "z"])


def _distance_to_beacons(
    x: float,
    y: float,
    z: float,
    beacon_IDs: list,
    reference_beacons: pd.DataFrame = None,
):
    """
    Vectorized version of :func:`distance_to_beacon`
    required for fitting models
    """
    distances = []
    for beacon_ID in beacon_IDs:
        d = distance_to_beacon(
            x=x, y=y, z=z, beacon_ID=beacon_ID, reference_beacons=reference_beacons
        )
        distances.append(d)

    return np.array(distances)


[docs]def distance_to_beacon( x: float, y: float, z: float, beacon_ID: int, reference_beacons: pd.DataFrame = None ) -> float: """ Calculates the distance from a position to a beacon specified by beacon_ID. """ if reference_beacons is None: reference_beacons = default_beacons ref_beac = reference_beacons.loc[int(beacon_ID)] # _, xb, yb, zb = xb = ref_beac["x"] yb = ref_beac["y"] zb = ref_beac["z"] return np.sqrt((x - xb) ** 2 + (y - yb) ** 2 + (z - zb) ** 2)
[docs]def fit_observations( distances: list, beacon_names: list, depth: float = None, x_guess: float = 0, y_guess: float = 0, beacon_log=default_beacons, ) -> dict: """ Fits distance observations and returns the estimated x, y, z coordinates. Parameters ---------- distances observed distances in m beacon_names names of the beacons to which the reference measurements where made. depth supports different in put formats None -> will attempt to determine depth based on observation.. float -> will take the depth as a known value. ">0" -> will attempt to determine the height above sea level based on observations "<0" -> will attempt to determine depth below sea level based on observations. Returns ------- coordinates a dictionary containing the keys, x, y, and z """ beacon_IDs = determine_beacon_IDs(beacon_names, beacon_log) def _wrapped_distance_to_beacons(x: float, y: float, z: float, beacon_IDs: list): return _distance_to_beacons( x=x, y=y, z=z, beacon_IDs=beacon_IDs, reference_beacons=beacon_log ) dist_model = lmfit.Model(_wrapped_distance_to_beacons, independent_vars=["beacon_IDs"]) dist_model.set_param_hint("x", value=x_guess, vary=True) dist_model.set_param_hint("y", value=y_guess, vary=True) if depth is None: # initial guess is set negative so that in face of ambiguity we will find a # coordinate below sea level dist_model.set_param_hint("z", value=-20, vary=True) elif depth == "<0": dist_model.set_param_hint("z", value=-20, vary=True, max=0) elif depth == ">0": # initial guess is positive dist_model.set_param_hint("z", value=20, vary=True, min=0) else: dist_model.set_param_hint("z", value=-depth, vary=False) fit_results = dist_model.fit(distances, beacon_IDs=beacon_IDs) return fit_results
[docs]def determine_beacon_IDs(beacon_names, beacons: pd.DataFrame): """ Determines the ID (integer index) of beacons specified by name. """ beacon_IDs = [] for beacon_name in beacon_names: assert isinstance(beacon_name, str) ID = beacons.index[beacons["beacon"] == beacon_name][0] beacon_IDs.append(ID) return beacon_IDs