diff --git a/src/open_irceline/api.py b/src/open_irceline/api.py index 69bdb67..e61eee7 100644 --- a/src/open_irceline/api.py +++ b/src/open_irceline/api.py @@ -1,7 +1,8 @@ import asyncio import socket +from xml.etree import ElementTree from datetime import datetime, timedelta, date -from typing import Tuple, List, Dict +from typing import Tuple, List, Dict, Set import aiohttp import async_timeout @@ -73,6 +74,37 @@ class IrcelineClient: r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring) return self.format_result('rio', await r.json(), features) + async def get_rio_feature_types(self) -> Set[str]: + """ + Fetch the list of possible features from the WFS server + :return: set of features available on the WFS server + """ + querystring = {"service": "WFS", + "version": "1.3.0", + "request": "GetCapabilities"} + r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring) + + return self.parse_capabilities(await r.text()) + + @staticmethod + def parse_capabilities(xml_string: str) -> Set[str]: + """ + From an XML string obtained with GetCapabilities, generate a set of feature names + :param xml_string: XML string to parse + :return: set of FeatureType Names found in the XML document + """ + try: + root = ElementTree.fromstring(xml_string) + except ElementTree.ParseError: + return set() + # noinspection HttpUrlsUsage + namespaces = { + 'wfs': 'http://www.opengis.net/wfs', + } + path = './/wfs:FeatureTypeList/wfs:FeatureType/wfs:Name' + feature_type_names = {t.text for t in root.findall(path, namespaces)} + return feature_type_names + @staticmethod def format_result(prefix: str, data: dict, features: List[RioFeature]) -> dict: """ diff --git a/src/open_irceline/data.py b/src/open_irceline/data.py index 4dd36d5..58a7796 100644 --- a/src/open_irceline/data.py +++ b/src/open_irceline/data.py @@ -4,21 +4,25 @@ from datetime import datetime, date class RioFeature(StrEnum): - BC_HMEAN = 'rio:bc_hmean' BC_24HMEAN = 'rio:bc_24hmean' BC_DMEAN = 'rio:bc_dmean' - NO2_HMEAN = 'rio:no2_hmean' + BC_HMEAN = 'rio:bc_hmean' + NO2_ANMEAN = 'rio:no2_anmean' NO2_DMEAN = 'rio:no2_dmean' - O3_HMEAN = 'rio:o3_hmean' - O3_MAXHMEAN = 'rio:o3_maxhmean' + NO2_HMEAN = 'rio:no2_hmean' O3_8HMEAN = 'rio:o3_8hmean' + O3_ANMEAN = 'rio:o3_anmean' + O3_HMEAN = 'rio:o3_hmean' O3_MAX8HMEAN = 'rio:o3_max8hmean' - PM10_HMEAN = 'rio:pm10_hmean' + O3_MAXHMEAN = 'rio:o3_maxhmean' PM10_24HMEAN = 'rio:pm10_24hmean' + PM10_ANMEAN = 'rio:pm10_anmean' PM10_DMEAN = 'rio:pm10_dmean' - PM25_HMEAN = 'rio:pm25_hmean' + PM10_HMEAN = 'rio:pm10_hmean' PM25_24HMEAN = 'rio:pm25_24hmean' + PM25_ANMEAN = 'rio:pm25_anmean' PM25_DMEAN = 'rio:pm25_dmean' + PM25_HMEAN = 'rio:pm25_hmean' SO2_HMEAN = 'rio:so2_hmean' diff --git a/tests/fixtures/capabilities.xml b/tests/fixtures/capabilities.xml new file mode 100644 index 0000000..fea99ad --- /dev/null +++ b/tests/fixtures/capabilities.xml @@ -0,0 +1,895 @@ + + + + WFS + IRCEL - CELINE - Web Feature Service + This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction. + WFS, WMS, GEOSERVER + http://geo.irceline.be/wfs + NONE + https://creativecommons.org/licenses/by/4.0/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rio:bc_24hmean + bc_24hmean + + features, bc_24hmean + EPSG:31370 + + + + rio:bc_anmean_be + bc_anmean_be + Black Carbon (BC) annual mean concentrations - RIO 4x4 model + bc_anmean_be, features + EPSG:31370 + + + + rio:bc_anmean_vl + bc_anmean_vl + + bc_anmean_vl, features + EPSG:31370 + + + + rio:bc_dmean + bc_dmean + + bc_dmean, features + EPSG:31370 + + + + rio:bc_hmean + bc_hmean + RIO 4x4km + bc_hmean, features + EPSG:31370 + + + + rio:bc_hmean_vl + bc_hmean_vl + + bc_hmean_vl, features + EPSG:31370 + + + + rio:bc_hmean_wl + bc_hmean_wl + + bc_hmean_wl, features + EPSG:31370 + + + + rio:no2_19thmax_hmean_be + no2_19thmax_hmean_be + + features, no2_19thmax_hmean_be + EPSG:31370 + + + + rio:no2_anmean + no2_anmean + + features, no2_anmean + EPSG:31370 + + + + rio:no2_anmean_be + no2_anmean_be + Nitrogen Dioxide (NO2) annual mean concentrations - RIO 4x4 model + features, no2_anmean_be + EPSG:31370 + + + + rio:no2_anmean_vl + no2_anmean_vl + + no2_anmean_vl, features + EPSG:31370 + + + + rio:no2_hmean + no2_hmean + + no2_hmean, features + EPSG:31370 + + + + rio:no2_hmean_vl + no2_hmean_vl + + no2_hmean_vl, features + EPSG:31370 + + + + rio:no2_hmean_wl + no2_hmean_wl + + features, no2_hmean_wl + EPSG:31370 + + + + rio:no2_maxhmean + no2_maxhmean + + no2_maxhmean, features + EPSG:31370 + + + + rio:o3_8hmean + o3_8hmean + + o3_8hmean, features + EPSG:31370 + + + + rio:o3_anmean + o3_anmean + + features, o3_anmean + EPSG:31370 + + + + rio:o3_anmean_be + o3_anmean_be + Ozone (O3) annual mean concentrations - RIO 4x4 model + o3_anmean_be, features + EPSG:31370 + + + + rio:o3_anmean_vl + o3_anmean_vl + + o3_anmean_vl, features + EPSG:31370 + + + + rio:o3_aot40for_5y_be + o3_aot40for_5y_be + + features, o3_aot40for_5y_be + EPSG:31370 + + + + rio:o3_aot40for_be + o3_aot40for_be + + o3_aot40for_be, features + EPSG:31370 + + + + rio:o3_aot40veg_5y_be + o3_aot40veg_5y_be + + o3_aot40veg_5y_be, features + EPSG:31370 + + + + rio:o3_aot40veg_be + o3_aot40veg_be + + o3_aot40veg_be, features + EPSG:31370 + + + + rio:o3_aot60_be + o3_aot60_be + + o3_aot60_be, features + EPSG:31370 + + + + rio:o3_hmean + o3_hmean + + features, o3_hmean + EPSG:31370 + + + + rio:o3_hmean_vl + o3_hmean_vl + + o3_hmean_vl, features + EPSG:31370 + + + + rio:o3_max8hmean + o3_max8hmean + + features, o3_max8hmean + EPSG:31370 + + + + rio:o3_maxhmean + o3_maxhmean + + features, o3_maxhmean + EPSG:31370 + + + + rio:o3_net60_3y_be + o3_net60_3y_be + + o3_net60_3y_be, features + EPSG:31370 + + + + rio:o3_net60_be + o3_net60_be + + o3_net60_be, features + EPSG:31370 + + + + rio:pm10_24hmean + pm10_24hmean + + features, pm10_24hmean + EPSG:31370 + + + + rio:pm10_24hmean_1x1 + pm10_24hmean_1x1 + + pm10_24hmean_1x1, features + EPSG:31370 + + + + rio:pm10_24hmean_vl + pm10_24hmean_vl + + features, pm10_24hmean_vl + EPSG:31370 + + + + rio:pm10_anmean + pm10_anmean + + pm10_anmean, features + EPSG:31370 + + + + rio:pm10_anmean_be + pm10_anmean_be + Particulate Matter (PM10) annual mean concentrations - RIO 4x4 model + pm10_anmean_be, features + EPSG:31370 + + + + rio:pm10_anmean_vl + pm10_anmean_vl + + pm10_anmean_vl, features + EPSG:31370 + + + + rio:pm10_dmean + pm10_dmean + + features, pm10_dmean + EPSG:31370 + + + + rio:pm10_excday_be + pm10_excday_be + + features, pm10_excday_be + EPSG:31370 + + + + rio:pm10_hmean + pm10_hmean + + pm10_hmean, features + EPSG:31370 + + + + rio:pm10_hmean_1x1 + pm10_hmean_1x1 + + features, pm10_hmean_1x1 + EPSG:31370 + + + + rio:pm10_hmean_vl + pm10_hmean_vl + + features, pm10_hmean_vl + EPSG:31370 + + + + rio:pm10_hmean_wl + pm10_hmean_wl + + features, pm10_hmean_wl + EPSG:31370 + + + + rio:pm25_24hmean + pm25_24hmean + + features, pm25_24hmean + EPSG:31370 + + + + rio:pm25_24hmean_vl + pm25_24hmean_vl + + pm25_24hmean_vl, features + EPSG:31370 + + + + rio:pm25_anmean + pm25_anmean + + pm25_anmean, features + EPSG:31370 + + + + rio:pm25_anmean_be + pm25_anmean_be + Particulate Matter (PM2.5) annual mean concentrations - RIO 4x4 model + pm25_anmean_be, features + EPSG:31370 + + + + rio:pm25_anmean_vl + pm25_anmean_vl + + pm25_anmean_vl, features + EPSG:31370 + + + + rio:pm25_dmean + pm25_dmean + + features, pm25_dmean + EPSG:31370 + + + + rio:pm25_hmean + pm25_hmean + + pm25_hmean, features + EPSG:31370 + + + + rio:pm25_hmean_1x1 + pm25_hmean_1x1 + + features, pm25_hmean_1x1 + EPSG:31370 + + + + rio:pm25_hmean_vl + pm25_hmean_vl + + pm25_hmean_vl, features + EPSG:31370 + + + + rio:rio_grid_1x1 + rio_grid_1x1 + + features, rio_grid_1x1 + EPSG:31370 + + + + rio:rio_grid_4x4 + rio_grid_4x4 + + rio_grid_4x4, features + EPSG:31370 + + + + rio:rio_grid_4x4_be + rio_grid_4x4_be + + features, rio_grid_4x4_clip_be + EPSG:31370 + + + + rio:rio_grid_4x4_centroids + rio_grid_4x4_centroids + + rio_grid_4x4_centroids, features + EPSG:31370 + + + + rio:so2_25thmax_hmean_be + so2_25thmax_hmean_be + + so2_25thmax_hmean_be, features + EPSG:31370 + + + + rio:so2_anmean_be + so2_anmean_be + + so2_anmean_be, features + EPSG:31370 + + + + rio:so2_anmean_vl + so2_anmean_vl + + so2_anmean_vl, features + EPSG:31370 + + + + rio:so2_hmean + so2_hmean + + so2_hmean, features + EPSG:31370 + + + + rio:so2_hmean_vl + so2_hmean_vl + + so2_hmean_vl, features + EPSG:31370 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abs + abs_2 + abs_3 + abs_4 + acos + AddCoverages + Affine + Aggregate + And + Area + area2 + AreaGrid + array + asin + atan + atan2 + attributeCount + BandMerge + bands + BandSelect + BarnesSurface + between + boundary + boundaryDimension + boundedBy + Bounds + buffer + BufferFeatureCollection + bufferWithSegments + Categorize + ceil + Centroid + classify + ClassifyByRange + Clip + CollectGeometries + Collection_Average + Collection_Bounds + Collection_Count + Collection_Max + Collection_Median + Collection_Min + Collection_Nearest + Collection_Sum + Collection_Unique + Concatenate + contains + Contour + contrast + convert + convexHull + ConvolveCoverage + cos + Count + CoverageClassStats + CropCoverage + crosses + darken + dateDifference + dateFormat + dateParse + desaturate + difference + dimension + disjoint + disjoint3D + distance + distance3D + double2bool + endAngle + endPoint + env + envelope + EqualArea + EqualInterval + equalsExact + equalsExactTolerance + equalTo + exp + exteriorRing + Feature + FeatureClassStats + floor + footprints + geometry + geometryType + geomFromWKT + geomLength + getGeometryN + getX + getY + getz + grayscale + greaterEqualThan + greaterThan + Grid + GroupCandidateSelection + Heatmap + hsl + id + IEEEremainder + if_then_else + in + in10 + in2 + in3 + in4 + in5 + in6 + in7 + in8 + in9 + inArray + InclusionFeatureCollection + int2bbool + int2ddouble + interiorPoint + interiorRingN + Interpolate + intersection + IntersectionFeatureCollection + intersects + intersects3D + isCached + isClosed + isCoverage + isEmpty + isInstanceOf + isLike + isNull + isometric + isRing + isSimple + isValid + isWithinDistance + isWithinDistance3D + Jenks + Jiffle + jsonArrayContains + jsonPointer + language + lapply + length + lessEqualThan + lessThan + lighten + list + listMultiply + litem + literate + log + LRSGeocode + LRSMeasure + LRSSegment + mapGet + max + max_2 + max_3 + max_4 + min + min_2 + min_3 + min_4 + mincircle + minimumdiameter + minrectangle + mix + modulo + MultiplyCoverages + Nearest + NormalizeCoverage + not + notEqualTo + now + numberFormat + numberFormat2 + numGeometries + numInteriorRing + numPoints + octagonalenvelope + offset + Or + overlaps + parameter + parseBoolean + parseDouble + parseInt + parseLong + pgNearest + pi + PointBuffers + pointN + PointStacker + PolygonExtraction + pow + property + PropertyExists + Quantile + Query + random + RangeLookup + RasterAsPointCollection + RasterZonalStatistics + RasterZonalStatistics2 + Recode + RectangularClip + relate + relatePattern + Reproject + rescaleToPixels + rint + round + round_2 + roundDouble + saturate + ScaleCoverage + setCRS + shade + Simplify + sin + size + Snap + spin + sqrt + StandardDeviation + startAngle + startPoint + strAbbreviate + strCapitalize + strConcat + strDefaultIfBlank + strEndsWith + strEqualsIgnoreCase + strIndexOf + stringTemplate + strLastIndexOf + strLength + strMatches + strPosition + strReplace + strStartsWith + strStripAccents + strSubstring + strSubstringStart + strToLowerCase + strToUpperCase + strTrim + strTrim2 + strURLEncode + StyleCoverage + symDifference + tan + tint + toDegrees + toRadians + touches + toWKT + Transform + TransparencyFill + union + UnionFeatureCollection + Unique + UniqueInterval + VectorToRaster + VectorZonalStatistics + vertices + within + + + + + + \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py index 2eedb09..adc5c76 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,5 @@ from datetime import datetime, date -from aiohttp import ClientSession from freezegun import freeze_time from src.open_irceline.api import IrcelineClient @@ -40,3 +39,25 @@ async def test_format_result_dmean(): } assert result == expected + + +def test_parse_capabilities(): + with open('tests/fixtures/capabilities.xml', 'r') as xml_file: + result = IrcelineClient.parse_capabilities(xml_file.read()) + + expected = {'rio:so2_anmean_be', 'rio:o3_hmean', 'rio:bc_anmean_vl', 'rio:o3_anmean_be', 'rio:pm10_hmean_vl', + 'rio:o3_aot40for_be', 'rio:no2_maxhmean', 'rio:pm10_24hmean_1x1', 'rio:o3_aot40veg_5y_be', + 'rio:pm10_24hmean_vl', 'rio:pm10_anmean', 'rio:pm25_anmean_be', 'rio:o3_net60_be', 'rio:pm25_hmean_vl', + 'rio:so2_25thmax_hmean_be', 'rio:so2_hmean_vl', 'rio:pm10_dmean', 'rio:bc_hmean_wl', + 'rio:o3_aot40veg_be', 'rio:o3_max8hmean', 'rio:pm25_24hmean', 'rio:no2_hmean', + 'rio:rio_grid_4x4_centroids', 'rio:no2_anmean', 'rio:so2_hmean', 'rio:o3_hmean_vl', 'rio:rio_grid_4x4', + 'rio:pm10_hmean_1x1', 'rio:pm25_anmean_vl', 'rio:pm25_dmean', 'rio:rio_grid_1x1', 'rio:o3_aot60_be', + 'rio:no2_anmean_be', 'rio:o3_net60_3y_be', 'rio:pm10_anmean_be', 'rio:o3_maxhmean', + 'rio:no2_19thmax_hmean_be', 'rio:bc_anmean_be', 'rio:bc_24hmean', 'rio:no2_anmean_vl', + 'rio:no2_hmean_wl', 'rio:pm10_24hmean', 'rio:bc_hmean_vl', 'rio:pm10_anmean_vl', 'rio:pm10_hmean_wl', + 'rio:pm25_anmean', 'rio:o3_aot40for_5y_be', 'rio:rio_grid_4x4_be', 'rio:o3_8hmean', + 'rio:pm25_hmean_1x1', 'rio:no2_hmean_vl', 'rio:o3_anmean', 'rio:pm10_excday_be', 'rio:o3_anmean_vl', + 'rio:pm10_hmean', 'rio:pm25_24hmean_vl', 'rio:pm25_hmean', 'rio:bc_hmean', 'rio:so2_anmean_vl', + 'rio:bc_dmean'} + + assert result == expected