Query on attribute properties

To find data based on one or more of its attribute values, we can use the query parameter of the search objects. This parameter takes a filter expression as its argument, based on the OGC filter expressions from the OWSLib library.

Available attribute fields

The list of available attributes (or: fields) depends on the dataset you want to query and can be retrieved with the get_fields method. This results in a dictionary with for each of the fields a new dictionary with more information about the field.

For each field, the following information is available:

name

The name of the field.

Example: 'methode'

definition

The definition of the field.

Example: 'De methode waarmee de boring uitgevoerd werd. Heeft als waarde 'onbekend' indien de methode niet gekend is.'

cost

The resource cost (in time) to request this field as part of the return fields. Is either ‘1’ (no additional cost) or ‘10’ (cost of an implicit XML download per result feature).

Example: 1

notnull

Whether the field is mandatory (True) or optional (False).

Example: True

query

Whether the field can be used in an attribute query.

Example: True

type

The datatype of the values of this field.

Example: 'string'

multivalue

Whether the value of this field is a multivalued type. When this is True the value of this field in the output dataframe will not be a single value, but a tuple of values of the datatype type instead.

Example: False

codelist

(Optional) In case the field has an associated codelist, it is listed here. You can use the codelist to get more information about the possible values of this field.

Example: <pydov.util.codelists.AbstractCodeList: <pydov.util.codelists.CodeListItem: ...>>

Example

To get the name and definition of all attributes that are available for use in a search query for boreholes (query is True), you’d use:

from pydov.search.boring import BoringSearch
boringsearch = BoringSearch()

fields = boringsearch.get_fields()
for f in fields:
    if fields[f]['query']:
        print(fields[f]['name'], '-', fields[f]['definition'])

Which would result in:

generated_id - Oplopend uniek volgnummer ter identificatie (intern en niet-stabiel).
id - Volgnummer ter identificatie (intern en niet-stabiel).
boornummer - Het boornummer (ook gekend als proefnummer) van de boring.
pkey_boring - Permanente URL die verwijst naar de gegevens van de boring op de website. Voeg '.xml' toe om een XML voorstelling van deze gegevens te verkrijgen.
rapport - URL die verwijst naar het rapport van de boring in PDF formaat.
diepte_boring_tot - Maximumdiepte van de boring ten opzichte van het aanvangspeil, in meter.
datum_aanvang - Datum waarop men de boring gestart is.
namen - Alternatieve benamingen voor de boring.
putnummer - Het GW_ID (ook gekend als putnummer) van de put waaraan de boring gekoppeld is. Wanneer dit veld leeg is is de boring niet gekoppeld aan een put.
x - De x-coördinaat van de boring in het Lambert72 coördinaatsysteem (in meter, EPSG:31370).
y - De y-coördinaat van de boring in het Lambert72 coördinaatsysteem (in meter, EPSG:31370).
start_boring_mtaw - De hoogte van het aanvangspeil van de boring in het TAW stelsel (in meter).
gemeente - De gemeente waarin de boring gelegen is.
uitvoerder - De organisatie die de boring uitvoerde. Heeft als waarde 'onbekend' indien de uitvoerder niet gekend is.
doel - Het doel van de boring.
methode - De methode waarmee de boring uitgevoerd werd. Heeft als waarde 'onbekend' indien de methode niet gekend is.
erkenning - In het kader van welke discipline de boring werd uitgevoerd. Deze codelijst bevat de verschillende disciplines van erkende boorbedrijven uit artikel 6, 7°, a) van VLAREL.
opdrachtgever - De organisatie die de opdracht gaf om de boring uit te voeren.  Heeft als waarde 'onbekend' indien de opdrachtgever niet gekend is.
informele_stratigrafie - Geeft aan of er aan de boring minstens één interpretatie van het type 'informele stratigrafie' gekoppeld is.
formele_stratigrafie - Geeft aan of er aan de boring minstens één interpretatie van het type 'formele stratigrafie' gekoppeld is.
lithologische_beschrijving - Geeft aan of er aan de boring minstens één interpretatie van het type 'lithologische beschrijving' gekoppeld is.
gecodeerde_lithologie - Geeft aan of er aan de boring minstens één interpretatie van het type 'gecodeerde lithologie' gekoppeld is.
hydrogeologische_stratigrafie - Geeft aan of er aan de boring minstens één interpretatie van het type 'hydrogeologische stratigrafie' gekoppeld is.
quartaire_stratigrafie - Geeft aan of er aan de boring minstens één interpretatie van het type 'quartaire stratigrafie' gekoppeld is.
geotechnische_codering - Geeft aan of er aan de boring minstens één interpretatie van het type 'geotechnische codering' gekoppeld is.
informele_hydrostratigrafie - Geeft aan of er aan de boring minstens één interpretatie van het type 'informele hydrostratigrafie' gekoppeld is.
doorheen_quartair - Geeft aan of de boring dieper ging dan de grens van het Quartair en het Neogeen/Paleogeen (Tertiair). Dit veld is enkel ingevuld indien er minstens één interpretatie van het type 'formele stratigrafie' gekoppeld is aan de boring én het Quartair geïnterpreteerd werd.
dikte_quartair - Dit veld geeft de dikte weer van het Quartair (in meter). Dit is een getal met twee decimalen, soms voorafgegaan door < of >= (bv. >= 10.00).
tertiair_onder_quartair - Indien 'doorheen_quartair' true is bevat dit veld de afkorting van de eerste lithostratigrafische eenheid van het Neogeen/Paleogeen (Tertiair) die voorkomt onder het Quartair.
opdrachten - De DOV-opdracht(en) waaraan de boring gekoppeld is.

Other information about each field includes its datatype (type), cost to use it as return field (cost) or whether it is mandatory (notnull):

fields['diepte_boring_tot']
{'cost': 1,
 'definition': 'Maximumdiepte van de boring ten opzichte van het aanvangspeil, in meter.',
 'name': 'diepte_boring_tot',
 'notnull': False,
 'query': True,
 'multivalue': False,
 'type': 'float'}

Some fields additionally have an associated codelist (codelist), containing the possible values for that field. For example, the methode field has a codelist with all possible drilling methods. A codelist contains all codes, and for each code its label and optionally a definition.

fields['methode']['codelist'].keys()
['avegaarboring',
 'droge boring',
 'edelmanboring',
 'geen boring',
 'gestoken boring',
 'graafmachine',
 'handboring',
 'kernboring',
 'lansen',
 'lepelboring',
 'luchthamer',
 'luchthevelboren of air-lift boren',
 'meerdere technieken',
 'omgek. spoelboring',
 'onbekend',
 'pulsboring',
 'ramguts',
 'ramkernboring',
 'rollerbit',
 'slagboring',
 'spade',
 'spiraalboring',
 'spoelboring',
 'steenboring',
 'trilboring',
 'voorput',
 'zuigboring']

This allows creating an extra column with the mapped labels and definitions:

from pydov.search.monster import MonsterSearch
monstersearch = MonsterSearch()

fields = monstersearch.get_fields()
df = monstersearch.search(max_features=10)

df['monstertype_label'] = df['monstertype'].map(fields['monstertype']['codelist'].get_label)
df['monstertype_definition'] = df['monstertype'].map(fields['monstertype']['codelist'].get_definition)

print(df[['naam', 'monstertype', 'monstertype_label',
     'monstertype_definition']].to_string())
#                         naam monstertype monstertype_label                                                                                                                                                                                                                                                                                                                 monstertype_definition
# 0                       0     geroerd           Geroerd  Monstername waarbij de oorspronkelijke structuur en gelaagdheid van het materiaal niet bewaard wordt. Het resulterende monster laat het niet toe een gedetailleerde beschrijving of bepaalde analyses met betrekking tot de structuur of gelaagdheid  (bv. Bulkdensiteit, volumemassa, grondmechanische proeven, ....) uit te voeren.
# 3                       0   ongeroerd         Ongeroerd                                                                                                                                                                                                                              Monstername waarbij de oorspronkelijke structuur en gelaagdheid van het materiaal maximaal bewaard wordt.
# 7       000/00/2-F1/M1501   vloeistof         Vloeistof                                       Het monster bestaat uit een van nature vloeibare stof (een stof die gemakkelijk vormveranderingen ondergaat, samendrukbaar is, maar zich verzet tegen deze volumeverandering). Het begrip gelaagdheid is niet relevant voor het karakteriseren van het materiaal waaruit het monster is genomen.

Or, for example, to get the lithostratigraphic unit definitions of the formal stratigraphy interpretations of boreholes:

from pydov.search.interpretaties import FormeleStratigrafieSearch
itp = FormeleStratigrafieSearch()

fields = itp.get_fields()
df = itp.search(max_features=10)

df['lid1_label'] = df['lid1'].map(fields['lid1']['codelist'].get_definition)
df['lid2_label'] = df['lid2'].map(fields['lid2']['codelist'].get_definition)

print(df[['diepte_laag_van', 'diepte_laag_tot', 'lid1',
     'lid1_label', 'relatie_lid1_lid2', 'lid2', 'lid2_label']].to_string())
#    diepte_laag_van  diepte_laag_tot lid1           lid1_label relatie_lid1_lid2 lid2            lid2_label
# 0              0.0             3.00    Q  Quartaire afzetting                 T    Q   Quartaire afzetting
# 1              3.0            14.05    U             Onbekend                 T   Bc  Formatie van Berchem

Using OGC filter expressions

An attribute query consists of an OGC filter predicate, a query field (propertyname) and a literal value (literal). pydov uses the OGC filter predicates from the OWSLib library, defined in the owslib.fes2 package.

Note that the literal value is always expressed as a string, even if the field that is being searched is of a numeric, date or boolean type (dates should be expressed in the ‘YYYY-mm-dd’ format).

The following OGC filters are relevant for string, numeric, date or boolean attributes:

PropertyIsEqualTo

Search for exact matches.

Example: PropertyIsEqualTo(propertyname='methode', literal='ramkernboring')

Example: PropertyIsEqualTo(propertyname='diepte_boring_tot', literal='10')

Example: PropertyIsEqualTo(propertyname='datum_aanvang', literal='2014-01-01')

Example: PropertyIsEqualTo(propertyname='quartaire_stratigrafie', literal='True')

PropertyIsNotEqualTo

Search for values different from a given literal. Does not include empty values.

Example: PropertyIsNotEqualTo(propertyname='methode', literal='onbekend')

PropertyIsNull

Search for empty values. This filter only requires a propertyname.

Example: PropertyIsNull(propertyname='gemeente')

The following OGC filters are relevant for string attributes:

PropertyIsLike

Search for fuzzy matches. You can use the ‘_’ wildcard to represent a single character and the ‘%’ wildcard to represent multiple characters.

Example: PropertyIsLike(propertyname='methode', literal='lucht%')

The following OGC filters are relevant for numeric or date attributes:

PropertyIsLessThan

Search for values strictly less than (or: before) the given literal.

Example: PropertyIsLessThan(propertyname='diepte_boring_tot', literal='10')

PropertyIsLessThanOrEqualTo

Search for values less than (or: before) or equal to the given literal.

Example: PropertyIsLessThanOrEqualTo(propertyname='datum_aanvang', literal='2014-12-31')

PropertyIsGreaterThan

Search for values strictly greater than (or: after) the given literal.

Example: PropertyIsGreaterThan(propertyname='diepte_boring_tot', literal='10')

PropertyIsGreaterThanOrEqualTo

Search for values greater than (or: after) or equal to the given literal.

Example: PropertyIsGreaterThanOrEqualTo(propertyname='datum_aanvang', literal='2015-01-01')

PropertyIsBetween

Search for values greater than (or: after) or equal to the lower boundary and less than (or: before) or equal to the upper boundary. This filter requires two literal values as the lower and upper boundaries. Boundaries are inclusive.

Example: PropertyIsBetween(propertyname='diepte_boring_tot', lower='20', upper='50')

Example: PropertyIsBetween(propertyname='datum_aanvang', lower='2014-01-01', upper='2014-12-31')

Logically combining filter expressions

You can combine different OGC filter expressions in one query by using the And, Or and Not predicates from the owslib.fes2 package.

Each of And, Or and Not take a list as argument, in the case of And and Or the list should consist of at least two items. Each item can be a simple OGC filter expression or another And, Or or Not expression, so you can nest different levels of filter expressions.

And

Return results that match all listed filters.

Example: And([PropertyIsLessThan(propertyname='diepte_boring_tot', literal='10'), PropertyIsGreaterThan(propertyname='datum_aanvang', literal='2014-12-31')])

Or

Return results that match one or more listed filters.

Example: Or([PropertyIsLessThan(propertyname='diepte_boring_tot', literal='10'), PropertyIsGreaterThan(propertyname='datum_aanvang', literal='2014-12-31')])

Not

Return results that do not match any of the listed filters.

Example: Not([PropertyIsLike(propertyname='methode', literal='lucht%')])

Example

An example of an advanced query using a nested combination of logical filter expressions:

from owslib.fes2 import And, Or, Not
from owslib.fes2 import PropertyIsEqualTo, PropertyIsLike, PropertyIsNull

from pydov.search.boring import BoringSearch
boringsearch = BoringSearch()

query = And([PropertyIsEqualTo(propertyname='gemeente',
                               literal='Antwerpen'),
             Or([Not([PropertyIsNull(propertyname='putnummer')]),
                PropertyIsLike(propertyname='doel',
                               literal='Grondwater%'),
                PropertyIsEqualTo(propertyname='erkenning',
                                  literal='2. Andere grondwaterwinningen')]
               )]
           )
df = boring.search(query=query)

Query using lists

pydov extends the default OGC filter expressions described above with a two new expressions: PropertyInList that allows you to use lists (of strings) in (exact) search queries and PropertyLikeList that does the same for fuzzy matches.

The PropertyInList internally translates to a PropertyIsEqualTo and is relevant for string, numeric, date or boolean attributes:

PropertyInList

Search for one of a list of exact matches.

Internally this is translated to Or([PropertyIsEqualTo(), PropertyIsEqualTo(), ...]).

Example: PropertyInList(propertyname='methode', list=['ramkernboring', 'spoelboring', 'spade'])

Is equivalent to:

Or([
    PropertyIsEqualTo(propertyname='methode', literal='ramkernboring'),
    PropertyIsEqualTo(propertyname='methode', literal='spoelboring'),
    PropertyIsEqualTo(propertyname='methode', literal='spade')
])

The PropertyLikeList internally translates to a PropertyIsLike and is relevant for string attributes:

PropertyLikeList

Search for one of a list of fuzzy (non-exact) matches.

The optional modifier is used to transform the list values into PropertyIsLike literals (e.g. adding wildcards). You can use the string {item} in it, which will be replaced with the list item.

Internally this is translated to Or([PropertyIsLike(), PropertyIsLike(), ...]).

Example: PropertyLikeList(propertyname='methode', list=['ramkernboring', 'spoelboring', 'spade'])

Example: PropertyLikeList(propertyname='methode', list=['ramkernboring', 'spoelboring', 'spade'], modifier='%|{item}|%')

Is equivalent to:

Or([
    PropertyIsLike(propertyname='methode', literal='%|ramkernboring|%'),
    PropertyIsLike(propertyname='methode', literal='%|spoelboring|%'),
    PropertyIsLike(propertyname='methode', literal='%|spade|%')
])

Join different searches

The Join and FuzzyJoin expressions allow you to join multiple searches together. This allows combining results from different datasets to get the results you’re looking for.

Join

Join searches together using a common attribute. Instead of a propertyname and a literal (or a list of literals), this expression takes a Pandas dataframe and one or two join columns.

You can either specify a single column (in the on parameter) which should exist both in the provided dataframe as in the datatype being searched. Alternatively, you can specify both the on column (which should exist in the queried datatype) as well as the using column (which should exists in the provided dataframe).

Example: Join(df_boringen, 'pkey_boring')

Example: Join(df_boringen, on='pkey_boring')

Example: Join(df_boringen, on='pkey_boring', using='boringfiche')

The following example returns all the lithological descriptions of boreholes that are at least 20 meters deep (note that this is different from ‘lithological descriptions with a depth of at least 20m’):

from pydov.util.query import Join

from pydov.search.boring import BoringSearch
from pydov.search.interpretaties import LithologischeBeschrijvingenSearch

bs = BoringSearch()
ls = LithologischeBeschrijvingenSearch()

boringen = bs.search(query=PropertyIsGreaterThan('diepte_tot_m', '20'),
                     return_fields=('pkey_boring',))

lithologische_beschrijvingen = ls.search(query=Join(boringen, 'pkey_boring'))

Join expressions can be logically combined with other filter expressions, for example to further restrict the resultset:

from owslib.fes2 import And
from owslib.fes2 import PropertyIsEqualTo

from pydov.util.query import Join

from pydov.search.boring import BoringSearch
from pydov.search.interpretaties import LithologischeBeschrijvingenSearch

bs = BoringSearch()
ls = LithologischeBeschrijvingenSearch()

boringen = bs.search(query=PropertyIsGreaterThan('diepte_tot_m', '20'),
                     return_fields=('pkey_boring',))

lithologische_beschrijvingen = ls.search(query=And([Join(boringen, 'pkey_boring'),
                                                    PropertyIsEqualTo('betrouwbaarheid_interpretatie', 'goed')]))

The following example gets borehole information based on a search for groundwater filters:

from pydov.util.query import Join

from pydov.search.boring import BoringSearch
from pydov.search.grondwaterfilter import GrondwaterFilterSearch

fs = GrondwaterFilterSearch()
bs = BoringSearch()

filters = fs.search(location=WithinDistance(Point(96540, 186900, epsg=31370), 10, 'km'),
                    return_fields=('pkey_filter', 'boringfiche'))

print(filters.head())
#                                              pkey_filter                                            boringfiche
# 0  https://www.dov.vlaanderen.be/data/filter/1989-000092  https://www.dov.vlaanderen.be/data/boring/1989-021283
# 1  https://www.dov.vlaanderen.be/data/filter/2003-007671                                                    NaN
# 2  https://www.dov.vlaanderen.be/data/filter/1989-001026  https://www.dov.vlaanderen.be/data/boring/1989-065942

boringen = bs.search(query=Join(filters, on='pkey_boring', using='boringfiche'))

print(boringen.head())
#                                              pkey_boring      ...     boormethode
# 0  https://www.dov.vlaanderen.be/data/boring/1989-021283      ...        onbekend
# 1  https://www.dov.vlaanderen.be/data/boring/1989-065942      ...        onbekend
FuzzyJoin

FuzzyJoin can be used to combine searches in a fuzzy way, meaning the values from the dataframe only partly match an attribute of the query type. Instead of a propertyname and a literal (or a list of literals), this expression takes a Pandas dataframe, one or two join columns and optionally a modifier.

You can either specify a single column (in the on parameter) which should exist both in the provided dataframe as in the datatype being searched. Alternatively, you can specify both the on column (which should exist in the queried datatype) as well as the using column (which should exists in the provided dataframe).

The optional modifier is passed to the PropertyLikeList, which will use it to transform the dataframe values into PropertyIsLike literals (e.g. adding wildcards).

Example: FuzzyJoin(df_boringen, 'pkey_parents')

Example: FuzzyJoin(df_boringen, on='pkey_parents')

Example: FuzzyJoin(df_boringen, on='pkey_parents', using='boringfiche')

Example: FuzzyJoin(df_boringen, on='pkey_parents', modifier='%{item}%')

The following example gets borehole samples based on a search for boreholes:

from pydov.util.query import FuzzyJoin

from pydov.search.boring import BoringSearch
from pydov.search.monster import MonsterSearch

bs = BoringSearch()
ms = MonsterSearch()

boreholes = bs.search(max_features=1000, return_fields=('pkey_boring'))

print(boreholes.head())
#                                                    pkey_boring
# 0  https://ontwikkel.dov.vlaanderen.be/data/boring/1986-076450
# 1  https://ontwikkel.dov.vlaanderen.be/data/boring/1945-076396
# 2  https://ontwikkel.dov.vlaanderen.be/data/boring/2016-139026
# 3  https://ontwikkel.dov.vlaanderen.be/data/boring/2016-139027
# 4  https://ontwikkel.dov.vlaanderen.be/data/boring/2016-139028

samples = ms.search(query=FuzzyJoin(boreholes, on='pkey_parents', using='pkey_boring'))
#                                         pkey_monster naam                                       pkey_parents materiaalklasse datum_monstername  diepte_van_m  diepte_tot_m monstertype monstersamenstelling bemonsteringsprocedure bemonsteringsinstrument                         bemonstering_door
# 0  https://www.dov.vlaanderen.be/data/monster/201...    1  [https://www.dov.vlaanderen.be/data/boring/197...        sediment        1978-10-16           0.6          0.60   ongeroerd  Enkelvoudig monster                    NaN                  [buis]                                       NaN
# 1  https://www.dov.vlaanderen.be/data/monster/201...    1  [https://www.dov.vlaanderen.be/data/boring/200...        sediment        2009-04-30           3.0          3.50   ongeroerd  Enkelvoudig monster                    NaN                  [boor]                                       NaN
# 2  https://www.dov.vlaanderen.be/data/monster/201...    1  [https://www.dov.vlaanderen.be/data/boring/196...        sediment        1966-06-28           0.5          0.57   ongeroerd  Enkelvoudig monster                    NaN              [steekbus]  Rijksinstituut voor Grondmechanica (RIG)
# 3  https://www.dov.vlaanderen.be/data/monster/201...    1  [https://www.dov.vlaanderen.be/data/boring/196...        sediment        1966-07-06           0.5          0.74   ongeroerd  Enkelvoudig monster                    NaN              [steekbus]  Rijksinstituut voor Grondmechanica (RIG)
# 4  https://www.dov.vlaanderen.be/data/monster/201...    1  [https://www.dov.vlaanderen.be/data/boring/196...        sediment        1966-07-12           0.5          0.66   ongeroerd  Enkelvoudig monster                    NaN              [steekbus]  Rijksinstituut voor Grondmechanica (RIG)