From 2302ed6fc61e4b814cc9de6daf8e95b3fb4b2862 Mon Sep 17 00:00:00 2001 From: Dana Lambert Date: Fri, 22 Oct 2021 16:01:31 +1300 Subject: [PATCH] Add utils file with helper methods to request data from linz + docs --- README.md | 6 +++ backend/requirements.txt | 3 +- backend/right_tree/api/utils.py | 87 +++++++++++++++++++++++++++++++++ docker-compose.yaml | 2 + 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 backend/right_tree/api/utils.py diff --git a/README.md b/README.md index a8d3ceb..f69e28f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ $ sudo apt install git docker-compose To install `docker`, follow the [official installation documentation](https://docs.docker.com/get-docker/). [Instructions are also available for `docker-compose`](https://docs.docker.com/compose/install/). +In order to recieve address data while running in development mode you will need to set an environment variable containing a valid linz data service api key. Such a key can be retrieved by signing up to https://data.linz.govt.nz/. One way of setting the variable is exporting it in the same terminal window that you will run the application. To do this please run the following command: + +```bash +export LINZ_API_KEY= +``` + You may also need to give the `dev` script executable permissions using the following command: ``` diff --git a/backend/requirements.txt b/backend/requirements.txt index 35f9bd1..266dc2c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,4 +2,5 @@ Django==3.2.8 psycopg2-binary>=2.8 djangorestframework==3.12.4 django-cors-headers==3.10.0 -openpyxl==3.0.9 \ No newline at end of file +openpyxl==3.0.9 +requests==2.26.0 \ No newline at end of file diff --git a/backend/right_tree/api/utils.py b/backend/right_tree/api/utils.py new file mode 100644 index 0000000..65d3eb4 --- /dev/null +++ b/backend/right_tree/api/utils.py @@ -0,0 +1,87 @@ +import json +import os +import requests +from urllib.parse import urlencode + +from django.contrib.gis.geos import Point, GEOSGeometry +from django.conf import settings + +LINZ_API_KEY = os.getenv("LINZ_API_KEY") +LINZ_WFS_ENDPOINT = f"https://data.linz.govt.nz/services;key={LINZ_API_KEY}/wfs" +PROPERTY_TILE_LAYER = "layer-50804" +PROPERTY_INFO_LAYER = "layer-53353" + + +class WFSError(Exception): + pass + + +def get_point_from_coordinates(coordinates): + """Given a coordinates json string, returns the coordinates as a Point object""" + coordinates_json = json.loads(coordinates) + pnt = Point(coordinates_json["lng"], + coordinates_json["lat"], srid=4326) + return pnt + + +def wfs_getfeature(endpoint, **kwargs): + """Perform a WFS request with the with all keyword arguments as parameters + within the URL query, returning the JSON-formatted response""" + params = { + 'service': "WFS", + 'request': "GetFeature", + 'outputFormat': "application/json", + **kwargs, + } + + query = urlencode(params) + url = f"{endpoint}?{query}" + response = requests.get(url) + + try: + return response.json() + except json.JSONDecodeError as e: + raise WFSError( + f"Failed to make WFS request to {url}: {response.content}") + + +def linz_wfs_getfeature(**kwargs): + """Perform a WFS request via the LINZ endpoint""" + return wfs_getfeature(LINZ_WFS_ENDPOINT, version="2.0.0", **kwargs) + + +def intersecting_property(coordinates): + """Finds the property that intersects with the coordinates and returns its definition as a dictionary""" + point = get_point_from_coordinates(coordinates) + cql_filter = f"Intersects(shape,{point})" + json = linz_wfs_getfeature( + typeNames=PROPERTY_TILE_LAYER, cql_filter=cql_filter, count=1) + features = json.get("features") + + if len(features) > 0: + return features[0] + + +def get_address(polygon): + """Fetch the address string that intersects with the specified polygon""" + + # Don't perform request if the input polygon WKT is too long to fit into URL + # Limit is 2048 characters + if len(str(polygon)) > 1500: + return None + + cql_filter = f"Within(shape,{polygon})" + json = linz_wfs_getfeature( + typeNames=PROPERTY_INFO_LAYER, cql_filter=cql_filter, count=1) + features = json.get("features") + + if len(features) > 0: + feature = features[0] + return feature['properties'] + + +def get_address_from_coordinates(coordinates): + propertyPolygon = intersecting_property(coordinates) + if propertyPolygon is not None: + prop_geom = GEOSGeometry(json.dumps(propertyPolygon['geometry'])) + return get_address(prop_geom) diff --git a/docker-compose.yaml b/docker-compose.yaml index aa48ae5..4333724 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,8 @@ services: - postgres volumes: - ./backend:/app + environment: + - LINZ_API_KEY=${LINZ_API_KEY} ports: - "8000:8000" command: bash -c "./manage.py runserver 0.0.0.0:8000"