polyfemos.util.coordinator — polyfemos 0.3.29 documentation

# Source code for polyfemos.util.coordinator

    # -*- coding: utf-8 -*-
    # -----------------------------------------------------------------------------
    # This file is part of Polyfemos.
    #
    # Polyfemos is free software: you can redistribute it and/or modify it under
    # the terms of the GNU Lesser General Public License as published by the Free
    # Software Foundation, either version 3 of the License, or any later version.
    #
    # Polyfemos is distributed in the hope that it will be useful, but WITHOUT ANY
    # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
    # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    # details.
    #
    # You should have received a copy of the GNU Lesser General Public License and
    # GNU General Public License along with Polyfemos. If not, see
    # <https://www.gnu.org/licenses/>.'
    #
    # Author: Henrik Jänkävaara
    # -----------------------------------------------------------------------------
    """
    Coordinate transformation functions

    :copyright:
        2019, University of Oulu, Sodankyla Geophysical Observatory
    :license:
        GNU Lesser General Public License v3.0 or later
        (https://spdx.org/licenses/LGPL-3.0-or-later.html)
    """
    import pyproj
    import numpy as np
    from scipy.interpolate import SmoothBivariateSpline

    from polyfemos.util.messenger import messenger
    from polyfemos.util import fileutils

    def ddm2dd(degs, mins, letter):
        """
        :type degs: numlike
        :param degs: coordinate degrees
        :type mins: numlike
        :param mins: coordinate decimal minutes
        :type letter: str
        :param letter: ``N``, ``S``, ``E``, or ``W``
        :rtype: float
        :return: coordinate as decimal degrees
        """
        sign = [1, -1][letter in "SW"]
        decimaldegrees = sign * float(degs) + float(mins) / 60.
        return decimaldegrees

    def transform_from_ozi_map(mapfile):
        """
        Reads `OziExplorer <https://www.oziexplorer4.com/w/>`_
        map files and returns a function to convert
        WGS84 coordinates to pixels.

        The map file can be created manually and it
        should contain at least the following mandatory lines:

        .. code-block:: text

            Point01,xy, 110, 159,in, deg, 68, 47.222,N, 22, 46.498,E, grid, , , , N
            Point02,xy, 219, 159,in, deg, 68, 50.001,N, 25, 32.324,E, grid, , , , N
            Point03,xy, 329, 159,in, deg, 68, 50.063,N, 28, 20.055,E, grid, , , , N
            Point04,xy, 110, 318,in, deg, 67, 19.826,N, 23, 2.020,E, grid, , , , N
            Point05,xy, 219, 318,in, deg, 67, 22.408,N, 25, 37.707,E, grid, , , , N
            Point06,xy, 329, 318,in, deg, 67, 22.466,N, 28, 15.139,E, grid, , , , N
            Point07,xy, 110, 476,in, deg, 65, 52.937,N, 23, 15.536,E, grid, , , , N
            Point08,xy, 219, 476,in, deg, 65, 55.346,N, 25, 42.393,E, grid, , , , N
            Point09,xy, 329, 476,in, deg, 65, 55.401,N, 28, 10.860,E, grid, , , , N

        .. code-block:: text

            MMPLL,1, 19.499680, 70.147437
            MMPLL,2, 31.374991, 70.251143
            MMPLL,3, 30.424810, 64.431702
            MMPLL,4, 21.120046, 64.353722

        Each line is treated as a list of elements separated with commas,
        whitespaces are removed.
        The 'Point' entries are used to create the conversion and
        the 'MMPLL' entries define the extrapolation area.
        In 'Point' entries indices 2 and 3 are pixel coordinates and
        indices from 6 to 11 are the corresponding WGS84 coordinates
        (indices 2 and 3 in the 'MMPLL' entry).

        Lines that do not start with either 'MMPLL' or 'Point' are skipped.

        :type mapfile: str
        :param mapfile: path to OziExplorer map file
        :rtype: func
        :return: A function taking WGS84 longitude and latitude as arguments
            and returning pixel xy-coordinates as a tuple.
        """
        points = []
        corners = []

        for row in fileutils.rowsof(mapfile, delims=", "):
            if len(row) >= 4 and row[0] == "MMPLL":
                corners.append([*map(float, row[2:4])])
            elif len(row) >= 12 and row[0].startswith("Point"):
                px, py = map(float, row[2:4])
                lon = ddm2dd(*row[9:12])
                lat = ddm2dd(*row[6:9])
                points.append([lon, lat, px, py])

        points = np.array(points)
        corners = np.array(corners)

        bx0, bx1 = np.min(corners[:, 0]), np.max(corners[:, 0])
        by0, by1 = np.min(corners[:, 1]), np.max(corners[:, 1])

        lons = points[:, 0]
        lats = points[:, 1]
        pxs = points[:, 2]
        pys = points[:, 3]

        sx = SmoothBivariateSpline(
            lons, lats, pxs, bbox=[bx0, bx1, by0, by1], kx=1, ky=1)
        sy = SmoothBivariateSpline(
            lons, lats, pys, bbox=[bx0, bx1, by0, by1], kx=1, ky=1)

        return lambda lon, lat: (sx(lon, lat), sy(lon, lat))

    def get_transform(from_epsg, to_epsg):
        """
        :type from_epsg: str
        :param from_epsg: EPSG code of the original coordinate system
        :type to_epsg: str
        :param to_epsg: EPSG code of the resulting coordinate system
        :rtype: func
        :return: A coordinate transformation function taking xy-coordinates
            in ``from_epsg`` coordinate system and returning xy-coordinates in
            ``to_epsg`` coordinate system
        """
        try:
            from_coords = pyproj.Proj("+init=EPSG:{}".format(from_epsg))
            to_coords = pyproj.Proj("+init=EPSG:{}".format(to_epsg))
        except RuntimeError:
            msg = "No coordinate conversion available for ESPGs from " \
                  "'{}' to '{}'.".format(from_epsg, to_epsg)
            messenger(msg, "W")
            return lambda x, y: (x, y)
        return lambda x, y: pyproj.transform(from_coords, to_coords, x, y)