File: geointerpolator.py

package info (click to toggle)
python-geotiepoints 1.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,876 kB
  • sloc: python: 3,148; makefile: 111; sh: 15
file content (129 lines) | stat: -rw-r--r-- 5,032 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013-2021 Python-geotiepoints developers
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Geographical interpolation (lon/lats)."""

import numpy as np
from geotiepoints.interpolator import Interpolator, MultipleGridInterpolator, MultipleSplineInterpolator


EARTH_RADIUS = 6370997.0


class GeoInterpolator(Interpolator):
    """Handles interpolation of geolocation from a grid of tie points.

    It is
    preferable to have tie-points out till the edges if the tiepoint grid, but
    a method is provided to extrapolate linearly the tiepoints to the borders
    of the grid. The extrapolation is done automatically if it seems necessary.

    Uses numpy, scipy, and optionally pyresample.

    The constructor takes in the tiepointed data as *data*, the
    *tiepoint_grid* and the desired *final_grid*. As optional arguments, one
    can provide *kx_* and *ky_* as interpolation orders (in x and y directions
    respectively), and the *chunksize* if the data has to be handled by pieces
    along the y axis (this affects how the extrapolator behaves). If
    *chunksize* is set, don't forget to adjust the interpolation orders
    accordingly: the interpolation is indeed done globaly (not chunkwise).

    """

    def __init__(self, lon_lat_data, *args, **kwargs):
        try:
            # Maybe it's a pyresample object ?
            self.lon_tiepoint = lon_lat_data.lons
            self.lat_tiepoint = lon_lat_data.lats
            xyz = lon_lat_data.get_cartesian_coords()
            tie_data = [xyz[:, :, 0], xyz[:, :, 1], xyz[:, :, 2]]
        except AttributeError:
            self.lon_tiepoint = lon_lat_data[0]
            self.lat_tiepoint = lon_lat_data[1]
            x__, y__, z__ = lonlat2xyz(self.lon_tiepoint, self.lat_tiepoint)
            tie_data = [x__, y__, z__]

        super().__init__(tie_data, *args, **kwargs)

    def interpolate(self):
        """Run the interpolation."""
        newx, newy, newz = super().interpolate()
        lon, lat = xyz2lonlat(newx, newy, newz)
        return lon, lat


def lonlat2xyz(lons, lats, radius=EARTH_RADIUS):
    """Convert lons and lats to cartesian coordinates."""
    lons_rad = np.deg2rad(lons)
    lats_rad = np.deg2rad(lats)
    x_coords = radius * np.cos(lats_rad) * np.cos(lons_rad)
    y_coords = radius * np.cos(lats_rad) * np.sin(lons_rad)
    z_coords = radius * np.sin(lats_rad)
    return x_coords, y_coords, z_coords


def xyz2lonlat(x__, y__, z__, radius=EARTH_RADIUS, thr=0.8, low_lat_z=True):
    """Get longitudes from cartesian coordinates."""
    lons = np.rad2deg(np.arccos(x__ / np.sqrt(x__ ** 2 + y__ ** 2))) * np.sign(y__)
    lats = np.sign(z__) * (90 - np.rad2deg(np.arcsin(np.sqrt(x__ ** 2 + y__ ** 2) / radius)))
    if low_lat_z:
        # if we are at low latitudes - small z, then get the
        # latitudes only from z. If we are at high latitudes (close to the poles)
        # then derive the latitude using x and y:
        normalized_z = z__ / radius
        lat_mask_cond = abs(normalized_z) < thr
        lat_z_only = 90 - np.rad2deg(np.arccos(normalized_z))
        lats = np.where(lat_mask_cond, lat_z_only, lats)

    return lons, lats


def _work_with_lonlats(klass):
    """Adapt MultipleInterpolator classes to work with geographical coordinates."""

    class GeoKlass(klass):

        def __init__(self, tie_points, *data, **interpolator_init_kwargs):
            """Set up the interpolator."""
            data = to_xyz(data)
            super().__init__(tie_points, *data, **interpolator_init_kwargs)

        def interpolate(self, fine_points, **interpolator_call_kwargs):
            """Interpolate to *fine_points*."""
            x, y, z = super().interpolate(fine_points, **interpolator_call_kwargs)
            return xyz2lonlat(x, y, z)

    return GeoKlass


def to_xyz(data):
    """Convert data to cartesian.

    Data can be a class with a `get_cartesian_coords` method, or a tuple of (lon, lat) arrays.
    """
    if len(data) == 1:
        xyz = data[0].get_cartesian_coords()
        data = [xyz[:, :, 0], xyz[:, :, 1], xyz[:, :, 2]]
    elif len(data) == 2:
        data = lonlat2xyz(*data)
    else:
        raise ValueError("Either pass lon/lats or a pyresample definition.")
    return data


GeoGridInterpolator = _work_with_lonlats(MultipleGridInterpolator)
GeoSplineInterpolator = _work_with_lonlats(MultipleSplineInterpolator)