class BodemlocatieSearch(AbstractSearch):
    """Search class to retrieve information about bodemlocaties."""

    def __init__(self, objecttype=Bodemlocatie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemlocatie type.
            Optional: defaults to the Bodemlocatie type containing the fields
            described in the documentation.

        """
        super(BodemlocatieSearch, self).__init__('bodem:bodemlocaties',
                                                 objecttype) class BodemdiepteintervalSearch(AbstractSearch):
    """Search class to retrieve information about bodemdiepteintervallen."""

    def __init__(self, objecttype=Bodemdiepteinterval):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemdiepteinterval type.
            Optional: defaults to the Bodemdiepteinterval type containing the
            fields described in the documentation.

        """
        super(BodemdiepteintervalSearch, self).__init__(
            'bodem:bodemdiepteintervallen', objecttype) class BodemmonsterSearch(AbstractSearch):
    """Search class to retrieve information about bodemmonsters."""

    def __init__(self, objecttype=Bodemmonster):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemmonster type.
            Optional: defaults to the Bodemmonster type containing the fields
            described in the documentation.

        """
        super(BodemmonsterSearch, self).__init__('bodem:bodemmonsters',
                                                 objecttype) class BodemobservatieSearch(AbstractSearch):
    """Search class to retrieve information about bodemobservaties."""

    def __init__(self, objecttype=Bodemobservatie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemobservatie type.
            Optional: defaults to the Bodemobservatie type containing the
            fields described in the documentation.

        """
        super(BodemobservatieSearch, self).__init__('bodem:bodemobservaties',
                                                    objecttype) class BodemsiteSearch(AbstractSearch):
    """Search class to retrieve information about bodemlocaties."""

    def __init__(self, objecttype=Bodemsite):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemsite type.
            Optional: defaults to the Bodemsite type containing the fields
            described in the documentation.

        """
        super(BodemsiteSearch, self).__init__('bodem:bodemsites', objecttype) class BodemclassificatieSearch(AbstractSearch):
    """Search class to retrieve information about bodemclassificaties."""

    def __init__(self, objecttype=Bodemclassificatie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Bodemclassificatie type.
            Optional: defaults to the Bodemclassificatie type containing the
            fields described in the documentation.

        """
        super(BodemclassificatieSearch, self).__init__(
            'bodem:bodemclassificaties', objecttype) class BoringSearch(AbstractSearch):
    """Search class to retrieve information about boreholes (Boring)."""

    def __init__(self, objecttype=Boring):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Boring type.
            Optional: defaults to the Boring type containing the fields
            described in the documentation.

        """
        super(BoringSearch, self).__init__('dov-pub:Boringen', objecttype) class GrondmonsterSearch(AbstractSearch):
    """Search class to retrieve the grain size distribution of
    ground samples ('grondmonster')"""

    def __init__(self, objecttype=Grondmonster):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Grondmonster type.
            Optional: defaults to the Grondmonster type containing the fields
            described in the documentation.

        """
        super(GrondmonsterSearch, self).\
            __init__('boringen:grondmonsters', objecttype) class GrondwaterFilterSearch(AbstractSearch):
    """Search class to retrieve information about groundwater screens
    (GrondwaterFilter).
    """

    def __init__(self, objecttype=GrondwaterFilter):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the GrondwaterFilter type.
            Optional: defaults to the GrondwaterFilter type containing the
            fields described in the documentation.

        """
        super(GrondwaterFilterSearch,
              self).__init__('gw_meetnetten:meetnetten', objecttype)

    def search(self, location=None, query=None, sort_by=None,
               return_fields=None, max_features=None):
        """Search for objects of this type. Provide `location` and/or
        `query` and/or `max_features`.
        When `return_fields` is None, all fields are returned.

        Excludes 'empty' filters (i.e. Putten without Filters) by extending
        the `query` with a not-null check on pkey_filter.

        Parameters
        ----------
        location : pydov.util.location.AbstractLocationFilter or \
                   owslib.fes.BinaryLogicOpType<AbstractLocationFilter> or \
                   owslib.fes.UnaryLogicOpType<AbstractLocationFilter>
            Location filter limiting the features to retrieve. Can either be a
            single instance of a subclass of AbstractLocationFilter, or a
            combination using And, Or, Not of AbstractLocationFilters.
        query : owslib.fes.OgcExpression
            OGC filter expression to use for searching. This can contain any
            combination of filter elements defined in owslib.fes. The query
            should use the fields provided in `get_fields()`. Note that not
            all fields are currently supported as a search parameter.
        sort_by : owslib.fes.SortBy, optional
            List of properties to sort by.
        return_fields : list<str> or tuple<str> or set<str>
            A list of fields to be returned in the output data. This should
            be a subset of the fields provided in `get_fields()`. Note that
            not all fields are currently supported as return fields.
        max_features : int
            Limit the maximum number of features to request.

        Returns
        -------
        pandas.core.frame.DataFrame
            DataFrame containing the output of the search query.

        Raises
        ------
        pydov.util.errors.InvalidSearchParameterError
            When not one of `location` or `query` or `max_features` is
            provided.

        pydov.util.errors.InvalidFieldError
            When at least one of the fields in `return_fields` is unknown.

            When a field that is only accessible as return field is used as
            a query parameter.

            When a field that can only be used as a query parameter is used as
            a return field.

        pydov.util.errors.FeatureOverflowError
            When the number of features to be returned is equal to the
            maxFeatures limit of the WFS server.

        AttributeError
            When the argument supplied as return_fields is not a list,
            tuple or set.

        NotImplementedError
            This is an abstract method that should be implemented in a
            subclass.

        """
        self._pre_search_validation(location, query, sort_by, return_fields,
                                    max_features)

        exclude_empty_filters = Not([PropertyIsNull(
                                     propertyname='pkey_filter')])

        if query is not None:
            query = And([query, exclude_empty_filters])
        else:
            query = exclude_empty_filters

        fts = self._search(location=location, query=query, sort_by=sort_by,
                           return_fields=return_fields,
                           max_features=max_features)

        gw_filters = self._type.from_wfs(fts, self._wfs_namespace)

        df = pd.DataFrame(
            data=self._type.to_df_array(gw_filters, return_fields),
            columns=self._type.get_field_names(return_fields))

        return df class GrondwaterMonsterSearch(AbstractSearch):
    """Search class to retrieve information about groundwater samples
    (GrondwaterMonster).
    """

    def __init__(self, objecttype=GrondwaterMonster):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the GrondwaterFilter type.
            Optional: defaults to the GrondwaterFilter type containing the
            fields described in the documentation.

        """
        super(GrondwaterMonsterSearch,
              self).__init__('gw_meetnetten:grondwatermonsters', objecttype) class GrondwaterVergunningSearch(AbstractSearch):
    """Search class to retrieve information about Gw permits (
    all permits: current and historical)."""

    def __init__(self, objecttype=GrondwaterVergunning):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the GrondwaterVergunning type.
            Optional: defaults to the GrondwaterVergunning type containing
            the fields described in the documentation.

        """
        super(GrondwaterVergunningSearch, self).__init__(
            'gw_vergunningen:alle_verg', objecttype)

    def search(self, location=None, query=None, sort_by=None,
               return_fields=None, max_features=None):
        fts = self._search(location=location, query=query, sort_by=sort_by,
                           return_fields=return_fields,
                           max_features=max_features)

        gw_vergunningen = self._type.from_wfs(fts, self._wfs_namespace)

        df = pd.DataFrame(
            data=self._type.to_df_array(gw_vergunningen, return_fields),
            columns=self._type.get_field_names(return_fields))
        return df class FormeleStratigrafieSearch(AbstractSearch):
    """Search class to retrieve the interpretation for Formele
    stratigrafie"""

    def __init__(self, objecttype=FormeleStratigrafie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the FormeleStratigrafie type.
            Optional: defaults to the FormeleStratigrafie type containing the
            fields described in the documentation.

        """
        super(FormeleStratigrafieSearch, self).__init__(
            'interpretaties:formele_stratigrafie', objecttype) class GecodeerdeLithologieSearch(AbstractSearch):
    """Search class to retrieve gecodeerde lithologie """

    def __init__(self, objecttype=GecodeerdeLithologie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the GecodeerdeLithologie type.
            Optional: defaults to the GecodeerdeLithologie type containing
            the fields described in the documentation.

        """
        super(GecodeerdeLithologieSearch, self).__init__(
            'interpretaties:gecodeerde_lithologie',
            objecttype) class GeotechnischeCoderingSearch(AbstractSearch):
    """Search class to retrieve geotechnische codering """

    def __init__(self, objecttype=GeotechnischeCodering):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the GeotechnischeCodering type.
            Optional: defaults to the GeotechnischeCodering type containing
            the fields described in the documentation.

        """
        super(GeotechnischeCoderingSearch, self).__init__(
            'interpretaties:geotechnische_coderingen',
            objecttype) class HydrogeologischeStratigrafieSearch(AbstractSearch):
    """Search class to retrieve hydrogeological interpretations """

    def __init__(self, objecttype=HydrogeologischeStratigrafie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the HydrogeologischeStratigrafie
            type. Optional: defaults to the HydrogeologischeStratigrafie type
            containing the fields described in the documentation.

        """
        super(HydrogeologischeStratigrafieSearch, self).__init__(
            'interpretaties:hydrogeologische_stratigrafie',
            objecttype) class InformeleHydrogeologischeStratigrafieSearch(AbstractSearch):
    """Search class to retrieve information about informele
    hydrogeologische stratigrafie.

    Parameters
    ----------
    objecttype : subclass of pydov.types.abstract.AbstractDovType
        Reference to a class representing the
        InformeleHydrogeologischeStratigrafie type.
        Optional: defaults to the InformeleHydrogeologischeStratigrafie type
        containing the fields described in the documentation.

    """

    def __init__(self, objecttype=InformeleHydrogeologischeStratigrafie):
        """Initialisation."""
        super(InformeleHydrogeologischeStratigrafieSearch, self).__init__(
            'interpretaties:informele_hydrogeologische_stratigrafie',
            objecttype) class InformeleStratigrafieSearch(AbstractSearch):
    """Search class to retrieve information about 'informele stratigrafie'."""

    def __init__(self, objecttype=InformeleStratigrafie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the InformeleStratigrafie type.
            Optional: defaults to the InformeleStratigrafie type
            containing the fields described in the documentation.

        """
        super(InformeleStratigrafieSearch, self).__init__(
            'interpretaties:informele_stratigrafie', objecttype) class LithologischeBeschrijvingenSearch(AbstractSearch):
    """Search class to retrieve lithologische beschrijvingen """

    def __init__(self, objecttype=LithologischeBeschrijvingen):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the LithologischeBeschrijvingen
            type. Optional: defaults to the LithologischeBeschrijvingen type
            containing the fields described in the documentation.

        """
        super(LithologischeBeschrijvingenSearch, self).__init__(
            'interpretaties:lithologische_beschrijvingen',
            objecttype) class QuartairStratigrafieSearch(AbstractSearch):
    """Search class to retrieve the interpretation for Quartair
    stratigrafie"""

    def __init__(self, objecttype=QuartairStratigrafie):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the QuartairStratigrafie type.
            Optional: defaults to the QuartairStratigrafie type containing
            the fields described in the documentation.

        """
        super(QuartairStratigrafieSearch, self).__init__(
            'interpretaties:quartaire_stratigrafie', objecttype) class SonderingSearch(AbstractSearch):
    """Search class to retrieve information about CPT measurements (
    Sonderingen)."""

    def __init__(self, objecttype=Sondering):
        """Initialisation.

        Parameters
        ----------
        objecttype : subclass of pydov.types.abstract.AbstractDovType
            Reference to a class representing the Sondering type.
            Optional: defaults to the Sondering type containing
            the fields described in the documentation.

        """
        super(SonderingSearch, self).__init__(
            'dov-pub:Sonderingen', objecttype) class InvalidSearchParameterError(DOVError):
    """Error that occurs when given invalid parameters to the DOV search."""
    pass class Point(AbstractLocation):
    """Class representing a point location."""

    def __init__(self, x, y, epsg=31370):
        """Initialise a Point.

        To initialise a Point using GPS coordinates in decimal degrees,
        use epsg=4326 and enter the longitude as `x` and the latitude as `y`.

        Parameters
        ----------
        x : float
            X coordinate (or longitude) of the point.
        y : float
            Y coordinate (or latitude) of the point.
        epsg : int, optional
            EPSG code of the coordinate reference system (CRS) of the
            coordinates specified in `x`, `y`. Defaults to
            31370 (Belgian Lambert72).

        """
        self.x = x
        self.y = y

        self.element = etree.Element('{http://www.opengis.net/gml}Point')
        self.element.set('srsDimension', '2')
        self.element.set(
            'srsName',
            'http://www.opengis.net/gml/srs/epsg.xml#{:d}'.format(epsg))

        coordinates = etree.Element('{http://www.opengis.net/gml}pos')
        coordinates.text = '{:.06f} {:.06f}'.format(self.x, self.y)
        self.element.append(coordinates)

    def get_element(self):
        return self.element class WithinDistance(AbstractLocationFilter):
    """Class representing a spatial DWithin filter.

    A spatial DWithin will return all points that are within a given
    distance of a certain location.

    """

    def __init__(self, location, distance, distance_unit='meter'):
        """Initialise a WithinDistance filter.

        Parameters
        ----------
        location : AbstractLocation
            An instance of a location to use as location for the
            WithinDistance filter.
        distance : float
            Amount of distance units to use for the filter.
        distance_unit : string, optional, defaults to 'meter'
            The distance unit of the value of `distance`.

        """
        self.location = location
        self.distance = distance
        self.distance_unit = distance_unit
        self.geom_column = ''

        self.element = etree.Element('{http://www.opengis.net/ogc}DWithin')

        geom = etree.Element('{http://www.opengis.net/ogc}PropertyName')
        geom.text = self.geom_column

        distance = etree.Element('{http://www.opengis.net/ogc}Distance')
        distance.set('units', self.distance_unit)
        distance.text = '{:.06f}'.format(self.distance)

        self.element.append(geom)
        self.element.append(location.get_element())
        self.element.append(distance)

    def set_geometry_column(self, geometry_column):
        self.geom_column = geometry_column
        geom = self.element.find('.//{http://www.opengis.net/ogc}PropertyName')
        geom.text = geometry_column

    def toXML(self):
        if self.geom_column == '':
            raise RuntimeError('Geometry column has not been set. Use '
                               '"set_geometry_column" to set it.')
        return self.element class ServiceCheck:

    is_service_ok = None

    @staticmethod
    def service_ok(timeout=5):
        """Check whether DOV services are accessible.

        Used to skip online tests when the service is unavailable or
        unreachable.

        Parameters
        ----------
        timeout : int, optional
            Timeout in seconds. Defaults to 5.

        Returns
        -------
        bool
            True if the DOV services are reachable, False otherwise.

        """
        def check_url(url, timeout):
            try:
                ok = pydov.session.head(
                    url, allow_redirects=True, timeout=timeout).ok
            except requests.exceptions.ReadTimeout:
                ok = False
            except requests.exceptions.ConnectTimeout:
                ok = False
            except Exception:
                ok = False
            return ok

        if ServiceCheck.is_service_ok is None:
            ServiceCheck.is_service_ok = (
                check_url(build_dov_url('geoserver'), timeout) and
                check_url(build_dov_url('geonetwork'), timeout) and
                check_url(
                    build_dov_url('xdov/schema/latest/xsd/kern/dov.xsd'),
                    timeout))

        return ServiceCheck.is_service_ok
import numpy as np
import pytest
from pydov.search.bodemlocatie import BodemlocatieSearch
from pydov.search.bodemdiepteinterval import BodemdiepteintervalSearch
from pydov.search.bodemmonster import BodemmonsterSearch
from pydov.search.bodemobservatie import BodemobservatieSearch
from pydov.search.bodemsite import BodemsiteSearch
from pydov.search.bodemclassificatie import BodemclassificatieSearch
from pydov.search.boring import BoringSearch
from pydov.search.grondmonster import GrondmonsterSearch
from pydov.search.grondwaterfilter import GrondwaterFilterSearch
from pydov.search.grondwatermonster import GrondwaterMonsterSearch
from pydov.search.grondwatervergunning import GrondwaterVergunningSearch
from pydov.search.interpretaties import (
    FormeleStratigrafieSearch, GecodeerdeLithologieSearch,
    GeotechnischeCoderingSearch, HydrogeologischeStratigrafieSearch,
    InformeleHydrogeologischeStratigrafieSearch, InformeleStratigrafieSearch,
    LithologischeBeschrijvingenSearch, QuartairStratigrafieSearch)
from pydov.search.sondering import SonderingSearch
from pydov.util.errors import InvalidSearchParameterError
from pydov.util.location import Point, WithinDistance
from tests.abstract import ServiceCheck
"""Module grouping common tests for all search modules."""



search_objects = [BodemsiteSearch(),
                  BodemlocatieSearch(),
                  BodemdiepteintervalSearch(),
                  BodemobservatieSearch(),
                  BodemmonsterSearch(),
                  BodemclassificatieSearch(),
                  BoringSearch(),
                  SonderingSearch(),
                  GrondwaterFilterSearch(),
                  GrondwaterMonsterSearch(),
                  GrondwaterVergunningSearch(),
                  FormeleStratigrafieSearch(),
                  InformeleHydrogeologischeStratigrafieSearch(),
                  GeotechnischeCoderingSearch(),
                  QuartairStratigrafieSearch(),
                  InformeleStratigrafieSearch(),
                  HydrogeologischeStratigrafieSearch(),
                  GecodeerdeLithologieSearch(),
                  LithologischeBeschrijvingenSearch(),
                  GrondmonsterSearch()]


@pytest.mark.parametrize("objectsearch", search_objects)
def test_get_description(mp_wfs, objectsearch):
    """Test the get_description method.

    Test whether the method returns a non-empty string.

    Parameters
    ----------
    mp_wfs : pytest.fixture
        Monkeypatch the call to the remote GetCapabilities request.
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    description = objectsearch.get_description()

    assert isinstance(description, str)
    assert len(description) > 0


@pytest.mark.online
@pytest.mark.skipif(not ServiceCheck.service_ok(),
                    reason="DOV service is unreachable")
@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_location(objectsearch):
    """Test the get_description method.

    Test whether the method returns a non-empty string.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """
    objectsearch.search(location=WithinDistance(Point(100000, 100000), 100))


@pytest.mark.online
@pytest.mark.skipif(not ServiceCheck.service_ok(),
                    reason="DOV service is unreachable")
@pytest.mark.parametrize("objectsearch", search_objects)
def test_search_maxfeatures(objectsearch):
    """Test the search method with a max_features parameter.

    Test whether no error is raised.

    Parameters
    ----------
    objectsearch : pytest.fixture
        An instance of a subclass of AbstractTestSearch to perform search
        operations on the corresponding DOV type.

    """

    objectsearch.search(location=WithinDistance(Point(100000, 100000), 100),