<commit_before># Copyright (c) 2018, TU Wien, Department of Geodesy and Geoinformation
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#    * Neither the name of TU Wien, Department of Geodesy and Geoinformation
#      nor the names of its contributors may be used to endorse or promote
#      products derived from this software without specific prior written
#      permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL TU WIEN, DEPARTMENT OF GEODESY AND
# GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
The grids module defines the grid classes.
"""

import numpy as np
import numpy.testing as nptest
import warnings
try:
    from osgeo import ogr
    ogr_installed = True
except ImportError:
    ogr_installed = False

try:
    from itertools import izip as zip
except ImportError:
    pass  # python3

import pygeogrids.nearest_neighbor as NN
from pygeogrids.geodetic_datum import GeodeticDatum


class GridDefinitionError(Exception):
    pass


class GridIterationError(Exception):
    pass


class BasicGrid(object):

    """
    Grid that just has lat,lon coordinates and can find the
    nearest neighbour. It can also yield the gpi, lat, lon
    information in order.

    Parameters
    ----------
    lon : numpy.array
        longitudes of the points in the grid
    lat : numpy.array
        latitudes of the points in the grid
    geodatum : basestring
        Name of the geodatic datum associated with the grid
    gpis : numpy.array, optional
        if the gpi numbers are in a different order than the
        lon and lat arrays an array containing the gpi numbers
        can be given
        if no array is given here the lon lat arrays are given
        gpi numbers starting at 0
    subset : numpy.array, optional
        if the active part of the array is only a subset of
        all the points then the subset array which is a index
        into lon and lat can be given here.
    setup_kdTree : boolean, optional
        if set (default) then the kdTree for nearest neighbour
        search will be built on initialization
    shape : tuple, optional
        The shape of the grid array in 2-d space.
        e.g. for a 1x1 degree global regular grid the shape would be (180,360).
        if given the grid can be reshaped into the given shape
        this indicates that it is a regular grid and fills the
        attributes self.lon2d and self.lat2d which
        define the grid only be the meridian coordinates(self.lon2d) and
        the coordinates of the circles of latitude(self.lat2d).
        The shape has to be given as (lat2d, lon2d)
        It it is not given the shape is set to the length of the input
        lon and lat arrays.
    transform_lon : bool or None, optional (default: None)
        Whether to transform longitudes to values between -180 and 180.
        By default values are transformed, but a warning is issued.
        To turn off the warning, set this to ``True``, to turn of
        transformation set this to ``False``.

    Attributes
    ----------
    arrlon : numpy.array
        1D array of all longitudes of the grid
    arrlat : numpy.array
        1D array of all latitudes of the grid
    n_gpi : int
        number of gpis in the grid
    gpidirect : boolean
        if true the gpi number is equal to the index
        of arrlon and arrlat
    gpis : numpy.array
        gpi number for elements in arrlon and arrlat
        gpi[i] is located at arrlon[i],arrlat[i]
    subset : numpy.array
        if given then this contains the indices of a subset of
        the grid. This can be used if only a part of a grid is
        interesting for a application. e.g. land points, or only
        a specific country
    allpoints : boolean
        if False only a subset of the grid is active
    activearrlon : numpy.array
        array of longitudes that are active, is defined by
        arrlon[subset] if a subset is given otherwise equal to
        arrlon
    activearrlat : numpy.array
        array of latitudes that are active, is defined by
        arrlat[subset] if a subset is given otherwise equal to
        arrlat
    activegpis : numpy.array
        array of gpis that are active, is defined by
        gpis[subset] if a subset is given otherwise equal to
        gpis
    geodatum : object
        pygeogrids.geodatic_datum object (reference ellipsoid) associated
        with the grid
    issplit : boolean
        if True then the array was split in n parts with
        the self.split function
    kdTree : object
        grid.nearest_neighbor.findGeoNN object for
        nearest neighbor search
    shape : tuple, optional
        if given during initialization then this is
        the shape the grid can be reshaped to
    lat2d : numpy.array, optional
        if shape is given this attribute contains all latitudes according to
        the provided 2d-shape that make up the grid
    lon2d : numpy.array, optional
        if shape is given this attribute contains all longitudes according to
        the provided 2d-shape that make up the grid
    """

    def __init__(self, lon, lat, gpis=None, geodatum='WGS84', subset=None,
                 setup_kdTree=True, shape=None, transform_lon=None):
        """
        init method, prepares lon and lat arrays for _transform_lonlats if
        necessary

        """
        lon = np.asanyarray(lon)
        lat = np.asanyarray(lat)
        if gpis is not None:
            gpis = np.asanyarray(gpis)
        if subset is not None:
            subset = np.asanyarray(subset)

        if lat.shape != lon.shape:
            raise GridDefinitionError(
                "lat and lon np.arrays have to have equal shapes")

        self.n_gpi = len(lon)

        # transfrom longitudes to be between -180 and 180 if they are between 0
        # and 360
        if transform_lon or transform_lon is None:
            if np.any(lon > 180):
                lon[lon > 180] -= 360
                if transform_lon is None:
                    warnings.warn(
                        "Longitude values have been transformed to be in"
                        " (-180, 180]. If this was not intended or to suppress"
                        " this warning set the transform_lon keyword argument"
                    )

        self.arrlon = lon
        self.arrlat = lat

        self.shape = None

        if shape is not None and len(shape) == 2:
            if len(self.arrlat) % shape[0] != 0:
                msg = ("Given shape does not have the correct first dimension."
                       " Length of lat array is not divisible by shape[0] "
                       " without rest")
                raise GridDefinitionError(msg)

            if len(self.arrlon) % shape[1] != 0:
                msg = ("Given shape does not have the correct second "
                       "dimension. Length of lon array is not divisible by "
                       "shape[1] without rest")
                raise GridDefinitionError(msg)

            self.shape = shape
            self.lat2d = np.reshape(self.arrlat, self.shape)
            self.lon2d = np.reshape(self.arrlon, self.shape)

        else:
            self.shape = tuple([len(self.arrlon)])

        self.geodatum = GeodeticDatum(geodatum)

        if gpis is None:
            self.gpis = np.arange(self.n_gpi, dtype=int)
            self.gpidirect = True
        else:
            if lat.shape != gpis.shape:
                raise GridDefinitionError("lat, lon gpi np.arrays have to "
                                          "have equal shapes")
            self.gpis = gpis
            self.gpidirect = False

        self.subset = subset

        if subset is not None:
            self.activearrlon = self.arrlon[subset]
            self.activearrlat = self.arrlat[subset]
            self.activegpis = self.gpis[subset]
            self.allpoints = False
        else:
            self.activearrlon = self.arrlon
            self.activearrlat = self.arrlat
            self.activegpis = self.gpis
            self.allpoints = True

        self.issplit = False

        self.kdTree = None
        if setup_kdTree:
            self._setup_kdtree()

    def _setup_kdtree(self):
        """
        Setup kdTree
        """
        if self.kdTree is None:
            self.kdTree = NN.findGeoNN(self.activearrlon, self.activearrlat,
                                       self.geodatum)
            self.kdTree._build_kdtree()

    def split(self, n):
        """
        Function splits the grid into n parts this changes not function but
        grid_points() which takes the argument n and will only iterate through
        this part of the grid.

        Parameters
        ----------
        n : int
            Number of parts the grid should be split into
        """
        self.issplit = True
        self.subarrlats = np.array_split(self.activearrlat, n)
        self.subarrlons = np.array_split(self.activearrlon, n)
        self.subgpis = np.array_split(self.activegpis, n)

    def unite(self):
        """
        Unites a split array, so that it can be iterated over as a whole
        again.
        """
        self.issplit = False

    def grid_points(self, *args):
        """
        Yields all grid points in order

        Parameters
        ----------
        n : int, optional
            if the grid is split in n parts using the split function
            then this iterator will only iterate of the nth part of the
            grid

        Returns
        -------
        gpi : long
            grid point index
        lon : float
            longitude of gpi
        lat : float
            longitude of gpi
        """

        if not self.issplit and len(args) == 0:
            return self._normal_grid_points()
        elif self.issplit and len(args) == 1:
            return self._split_grid_points(args[0])

        raise GridIterationError("this function only takes an argument if "
                                 "the grid is split, and takes no argument "
                                 "if the grid is not split")

    def get_grid_points(self, *args):
        """
        Returns all active grid points.

        Parameters
        ----------
        n : int, optional
            if the grid is split in n parts using the split function
            then this function will only return the nth part of the
            grid

        Returns
        -------
        gpis : numpy.ndarray
            Grid point indices.
        arrlon : numpy.ndarray
            Longitudes.
        arrlat : numpy.ndarray
            Latitudes.
        """

        if not self.issplit and len(args) == 0:
            return (self.activegpis,
                    self.activearrlon,
                    self.activearrlat)

        elif self.issplit and len(args) == 1:
            n = args[0]
            return (self.subgpis[n],
                    self.subarrlons[n],
                    self.subarrlats[n])

    def _normal_grid_points(self):
        """
        Yields all grid points in order.

        Returns
        -------
        gpi : long
            grid point index
        lon : float
            longitude of gpi
        lat : float
            longitude of gpi
        """
        for i, (lon, lat) in enumerate(zip(self.activearrlon,
                                           self.activearrlat)):
            yield self.activegpis[i], lon, lat

    def _split_grid_points(self, n):
        """
        Yields all grid points or split grid in order.

        Parameters
        ----------
        n : int
            Number of subgrid to yield

        Returns
        -------
        gpi : long
            Grid point index.
        lon : float
            Longitude of gpi.
        lat : float
            Longitude of gpi.
        """

        for i, (lon, lat) in enumerate(zip(self.subarrlons[n],
                                           self.subarrlats[n])):
            yield self.subgpis[n][i], lon, lat

    def find_nearest_gpi(self, lon, lat, max_dist=np.Inf):
        """
        Finds nearest gpi, builds kdTree if it does not yet exist.

        Parameters
        ----------
        lon : float or iterable
            Longitude of point.
        lat : float or iterable
            Latitude of point.
        max_dist : float, optional
            Maximum distance to consider for search (default: np.Inf).

        Returns
        -------
        gpi : long
            Grid point index.
        distance : float
            Distance of gpi to given lon, lat.
            At the moment not on a great circle but in spherical
            cartesian coordinates.
        """
        gpi, distance = self.find_k_nearest_gpi(lon, lat, max_dist=np.Inf, k=1)

        if not _element_iterable(lon):
            gpi = gpi[0]
            distance = distance[0]

        return gpi, distance

    def find_k_nearest_gpi(self, lon, lat, max_dist=np.Inf, k=1):
        """
        Find k nearest gpi, builds kdTree if it does not yet exist.

        Parameters
        ----------
        lon : float or iterable
            Longitude of point.
        lat : float or iterable
            Latitude of point.
        max_dist : float, optional
            Maximum distance to consider for search (default: np.Inf).
        k : int, optional
            The number of nearest neighbors to return (default: 1).

        Returns
        -------
        gpi : long
            Grid point index.<commit_msg>fix #64<commit_after># Copyright (c) 2018, TU Wien, Department of Geodesy and Geoinformation
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#    * Neither the name of TU Wien, Department of Geodesy and Geoinformation
#      nor the names of its contributors may be used to endorse or promote
#      products derived from this software without specific prior written
#      permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL TU WIEN, DEPARTMENT OF GEODESY AND
# GEOINFORMATION BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
The grids module defines the grid classes.
"""

import numpy as np
import numpy.testing as nptest
import warnings
try:
    from osgeo import ogr
    ogr_installed = True
except ImportError:
    ogr_installed = False

try:
    from itertools import izip as zip
except ImportError:
    pass  # python3

import pygeogrids.nearest_neighbor as NN
from pygeogrids.geodetic_datum import GeodeticDatum


class GridDefinitionError(Exception):
    pass


class GridIterationError(Exception):
    pass


class BasicGrid(object):

    """
    Grid that just has lat,lon coordinates and can find the
    nearest neighbour. It can also yield the gpi, lat, lon
    information in order.

    Parameters
    ----------
    lon : numpy.array
        longitudes of the points in the grid
    lat : numpy.array
        latitudes of the points in the grid
    geodatum : basestring
        Name of the geodatic datum associated with the grid
    gpis : numpy.array, optional
        if the gpi numbers are in a different order than the
        lon and lat arrays an array containing the gpi numbers
        can be given
        if no array is given here the lon lat arrays are given
        gpi numbers starting at 0
    subset : numpy.array, optional
        if the active part of the array is only a subset of
        all the points then the subset array which is a index
        into lon and lat can be given here.
    setup_kdTree : boolean, optional
        if set (default) then the kdTree for nearest neighbour
        search will be built on initialization
    shape : tuple, optional
        The shape of the grid array in 2-d space.
        e.g. for a 1x1 degree global regular grid the shape would be (180,360).
        if given the grid can be reshaped into the given shape
        this indicates that it is a regular grid and fills the
        attributes self.lon2d and self.lat2d which
        define the grid only be the meridian coordinates(self.lon2d) and
        the coordinates of the circles of latitude(self.lat2d).
        The shape has to be given as (lat2d, lon2d)
        It it is not given the shape is set to the length of the input
        lon and lat arrays.
    transform_lon : bool or None, optional (default: None)
        Whether to transform longitudes to values between -180 and 180.
        By default values are transformed, but a warning is issued.
        To turn off the warning, set this to ``True``, to turn of
        transformation set this to ``False``.

    Attributes
    ----------
    arrlon : numpy.array
        1D array of all longitudes of the grid
    arrlat : numpy.array
        1D array of all latitudes of the grid
    n_gpi : int
        number of gpis in the grid
    gpidirect : boolean
        if true the gpi number is equal to the index
        of arrlon and arrlat
    gpis : numpy.array
        gpi number for elements in arrlon and arrlat
        gpi[i] is located at arrlon[i],arrlat[i]
    subset : numpy.array
        if given then this contains the indices of a subset of
        the grid. This can be used if only a part of a grid is
        interesting for a application. e.g. land points, or only
        a specific country
    allpoints : boolean
        if False only a subset of the grid is active
    activearrlon : numpy.array
        array of longitudes that are active, is defined by
        arrlon[subset] if a subset is given otherwise equal to
        arrlon
    activearrlat : numpy.array
        array of latitudes that are active, is defined by
        arrlat[subset] if a subset is given otherwise equal to
        arrlat
    activegpis : numpy.array
        array of gpis that are active, is defined by
        gpis[subset] if a subset is given otherwise equal to
        gpis
    geodatum : object
        pygeogrids.geodatic_datum object (reference ellipsoid) associated
        with the grid
    issplit : boolean
        if True then the array was split in n parts with
        the self.split function
    kdTree : object
        grid.nearest_neighbor.findGeoNN object for
        nearest neighbor search
    shape : tuple, optional
        if given during initialization then this is
        the shape the grid can be reshaped to
    lat2d : numpy.array, optional
        if shape is given this attribute contains all latitudes according to
        the provided 2d-shape that make up the grid
    lon2d : numpy.array, optional
        if shape is given this attribute contains all longitudes according to
        the provided 2d-shape that make up the grid
    """

    def __init__(self, lon, lat, gpis=None, geodatum='WGS84', subset=None,
                 setup_kdTree=True, shape=None, transform_lon=None):
        """
        init method, prepares lon and lat arrays for _transform_lonlats if
        necessary

        """
        lon = np.asanyarray(lon)
        lat = np.asanyarray(lat)
        if gpis is not None:
            gpis = np.asanyarray(gpis)
        if subset is not None:
            subset = np.asanyarray(subset)

        if lat.shape != lon.shape:
            raise GridDefinitionError(
                "lat and lon np.arrays have to have equal shapes")

        self.n_gpi = len(lon)

        # transfrom longitudes to be between -180 and 180 if they are between 0
        # and 360
        if transform_lon or transform_lon is None:
            if np.any(lon > 180):
                lon[lon > 180] -= 360
                if transform_lon is None:
                    warnings.warn(
                        "Longitude values have been transformed to be in"
                        " (-180, 180]. If this was not intended or to suppress"
                        " this warning set the transform_lon keyword argument"
                    )

        self.arrlon = lon
        self.arrlat = lat

        self.shape = None

        if shape is not None and len(shape) == 2:
            if len(self.arrlat) % shape[0] != 0:
                msg = ("Given shape does not have the correct first dimension."
                       " Length of lat array is not divisible by shape[0] "
                       " without rest")
                raise GridDefinitionError(msg)

            if len(self.arrlon) % shape[1] != 0:
                msg = ("Given shape does not have the correct second "
                       "dimension. Length of lon array is not divisible by "
                       "shape[1] without rest")
                raise GridDefinitionError(msg)

            self.shape = shape
            self.lat2d = np.reshape(self.arrlat, self.shape)
            self.lon2d = np.reshape(self.arrlon, self.shape)

        else:
            self.shape = tuple([len(self.arrlon)])

        self.geodatum = GeodeticDatum(geodatum)

        if gpis is None:
            self.gpis = np.arange(self.n_gpi, dtype=int)
            self.gpidirect = True
        else:
            if lat.shape != gpis.shape:
                raise GridDefinitionError("lat, lon gpi np.arrays have to "
                                          "have equal shapes")
            self.gpis = gpis
            self.gpidirect = False

        self.subset = subset

        if subset is not None:
            self.activearrlon = self.arrlon[subset]
            self.activearrlat = self.arrlat[subset]
            self.activegpis = self.gpis[subset]
            self.allpoints = False
        else:
            self.activearrlon = self.arrlon
            self.activearrlat = self.arrlat
            self.activegpis = self.gpis
            self.allpoints = True

        self.issplit = False

        self.kdTree = None
        if setup_kdTree:
            self._setup_kdtree()

    def _setup_kdtree(self):
        """
        Setup kdTree
        """
        if self.kdTree is None:
            self.kdTree = NN.findGeoNN(self.activearrlon, self.activearrlat,
                                       self.geodatum)
            self.kdTree._build_kdtree()

    def split(self, n):
        """
        Function splits the grid into n parts this changes not function but
        grid_points() which takes the argument n and will only iterate through
        this part of the grid.

        Parameters
        ----------
        n : int
            Number of parts the grid should be split into
        """
        self.issplit = True
        self.subarrlats = np.array_split(self.activearrlat, n)
        self.subarrlons = np.array_split(self.activearrlon, n)
        self.subgpis = np.array_split(self.activegpis, n)

    def unite(self):
        """
        Unites a split array, so that it can be iterated over as a whole
        again.
        """
        self.issplit = False

    def grid_points(self, *args):
        """
        Yields all grid points in order

        Parameters
        ----------
        n : int, optional
            if the grid is split in n parts using the split function
            then this iterator will only iterate of the nth part of the
            grid

        Returns
        -------
        gpi : long
            grid point index
        lon : float
            longitude of gpi
        lat : float
            longitude of gpi
        """

        if not self.issplit and len(args) == 0:
            return self._normal_grid_points()
        elif self.issplit and len(args) == 1:
            return self._split_grid_points(args[0])

        raise GridIterationError("this function only takes an argument if "
                                 "the grid is split, and takes no argument "
                                 "if the grid is not split")

    def get_grid_points(self, *args):
        """
        Returns all active grid points.

        Parameters
        ----------
        n : int, optional
            if the grid is split in n parts using the split function
            then this function will only return the nth part of the
            grid

        Returns
        -------
        gpis : numpy.ndarray
            Grid point indices.
        arrlon : numpy.ndarray
            Longitudes.
        arrlat : numpy.ndarray
            Latitudes.
        """

        if not self.issplit and len(args) == 0:
            return (self.activegpis,
                    self.activearrlon,
                    self.activearrlat)

        elif self.issplit and len(args) == 1:
            n = args[0]
            return (self.subgpis[n],
                    self.subarrlons[n],
                    self.subarrlats[n])

    def _normal_grid_points(self):
        """
        Yields all grid points in order.

        Returns
        -------
        gpi : long
            grid point index
        lon : float
            longitude of gpi
        lat : float
            longitude of gpi
        """
        for i, (lon, lat) in enumerate(zip(self.activearrlon,
                                           self.activearrlat)):
            yield self.activegpis[i], lon, lat

    def _split_grid_points(self, n):
        """
        Yields all grid points or split grid in order.

        Parameters
        ----------
        n : int
            Number of subgrid to yield

        Returns
        -------
        gpi : long
            Grid point index.
        lon : float
            Longitude of gpi.
        lat : float
            Longitude of gpi.
        """

        for i, (lon, lat) in enumerate(zip(self.subarrlons[n],
                                           self.subarrlats[n])):
            yield self.subgpis[n][i], lon, lat

    def find_nearest_gpi(self, lon, lat, max_dist=np.Inf):
        """
        Finds nearest gpi, builds kdTree if it does not yet exist.

        Parameters
        ----------
        lon : float or iterable
            Longitude of point.
        lat : float or iterable
            Latitude of point.
        max_dist : float, optional
            Maximum distance [m] to consider for search (default: np.Inf).

        Returns
        -------
        gpi : long
            Grid point index.
        distance : float
            Distance of gpi to given lon, lat.
            At the moment not on a great circle but in spherical
            cartesian coordinates.
        """
        gpi, distance = self.find_k_nearest_gpi(lon, lat, max_dist=max_dist, k=1)

        if not _element_iterable(lon):
            gpi = gpi[0]
            distance = distance[0]

        return gpi, distance

    def find_k_nearest_gpi(self, lon, lat, max_dist=np.Inf, k=1):
        """
        Find k nearest gpi, builds kdTree if it does not yet exist.

        Parameters
        ----------
        lon : float or iterable
            Longitude of point.
        lat : float or iterable
            Latitude of point.
        max_dist : float, optional
            Maximum distance to consider for search (default: np.Inf).
        k : int, optional
            The number of nearest neighbors to return (default: 1).

        Returns
        -------
        gpi : long
            Grid point index.
{'fraction_non_alphanumeric': 0.06368990549239831, 'fraction_numerical': 0.0055471853170798524, 'mean_word_length': 1.764054514480409, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2191772', 'n_tokens_mistral': 7886, 'n_tokens_neox': 7442, 'n_words': 3149}