Source code for l3py.filter

# Copyright (c) 2018 Andreas Kvas
# See LICENSE for copyright/license details.

"""
Spatial filters for post-processing of potential coefficients.
"""

from l3py.gravityfield import PotentialCoefficients
import pkg_resources
import numpy as np
import abc


[docs]class SpatialFilter(metaclass=abc.ABCMeta): """ Base interface for spatial filters applied to a PotentialCoefficients instance. Derived classes must at least implement a `filter` method which takes a PotentialCoefficients instance as argument. The gravity field passed to this method should remain unchanged. """ @abc.abstractmethod def filter(self, gravityfield): pass
[docs]class Gaussian(SpatialFilter): """ Implements a Gaussian filter. Parameters ---------- radius : float filter radius in kilometers """ def __init__(self, radius): self.radius = radius
[docs] def filter(self, gravityfield): """ Apply the Gaussian filter to a PotentialCoefficients instance. Parameters ---------- gravityfield : PotentialCoefficients instance gravity field to be filtered, remains unchanged Returns ------- result : PotentialCoefficients instance filterd copy of input """ if not isinstance(gravityfield, PotentialCoefficients): raise TypeError("Filter operation only implemented for instances of 'PotentialCoefficients'") nmax = gravityfield.nmax() b = np.log(2.0)/(1-np.cos(self.radius/6378.1366)) wn = np.zeros(nmax+1) wn[0] = 1.0 wn[1] = (1+np.exp(-2*b))/(1-np.exp(-2*b))-1/b for n in range(2, nmax+1): wn[n] = -(2*n-1)/b*wn[n-1]+wn[n-2] if wn[n] < 1e-7: break result = gravityfield.copy() for n in range(2, nmax+1): result.anm[n, 0:n + 1] *= wn[n] result.anm[0:n, n] *= wn[n] return result
[docs]class DDK(SpatialFilter): """ Implements the DDK filter by Kusche et al. (2009). Parameters ---------- level : int DDK filter level (positive, non-zero) """ def __init__(self, level): if level < 1: raise ValueError('DDK level must be at least 1 (requested DDK{0:d}).'.format(level)) normals = np.load(pkg_resources.resource_filename('l3py', 'data/ddk_normals.npz'), allow_pickle=True)['arr_0'] self.__nmax = normals[0].shape[0]-1 weights = 10**(15-level) * np.arange(self.__nmax + 1, dtype=float) ** 4 weights[0] = 1 self.__array = [] for normals_block in normals: m = self.__nmax + 1 - normals_block.shape[0] self.__array.append(np.linalg.solve(normals_block + np.diag(weights[m:]), normals_block))
[docs] def filter(self, gravityfield): """ Apply the DDK filter to a PotentialCoefficients instance. Parameters ---------- gravityfield : PotentialCoefficients instance gravity field to be filtered, remains unchanged Returns ------- result : PotentialCoefficients instance filterd copy of input Raises ------ ValueError if maximum spherical harmonic degree is greater than 120 """ if not isinstance(gravityfield, PotentialCoefficients): raise TypeError("Filter operation only implemented for instances of 'PotentialCoefficients'") nmax = gravityfield.nmax() if nmax > self.__nmax: raise ValueError('DDK filter only implemented for a maximum degree of {1:d} (nmax={0:d} supplied).' .format(nmax, self.__nmax)) result = gravityfield.copy() result.anm[:, 0] = (self.__array[0][0:nmax+1, 0:nmax+1]@gravityfield.anm[:, 0:1]).flatten() for m in range(1, nmax+1): result.anm[m::, m] = (self.__array[2*m-1][0:nmax + 1 - m, 0:nmax + 1 - m] @ gravityfield.anm[m::, m:m+1]).flatten() result.anm[m-1, m::] = (self.__array[2*m][0:nmax + 1 - m, 0:nmax + 1 - m] @ gravityfield.anm[m-1:m, m::].T).flatten() result.anm[0:2, 0:2] = gravityfield.anm[0:2, 0:2].copy() return result