Skip to content

Commit

Permalink
Merge pull request #108 from bird-house/add_geospatial
Browse files Browse the repository at this point in the history
Add geospatial process and GIS pip recipe
  • Loading branch information
huard authored Mar 16, 2021
2 parents f8dc7a5 + 76b997d commit c6cfe68
Show file tree
Hide file tree
Showing 28 changed files with 149 additions and 42 deletions.
10 changes: 10 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Changes
*******

0.13.0 (unreleased)
===================

Changes:

* Added Geospatial process.
* Added a new recipe for installing Emu with GIS libraries (`pip install eum[gis]`).
* Added example geospatial data (raster image courtesy of USGS: `Mars MGS MOLA DEM 463m v2`).
* Refined Ultimate Question process to be truer to the source literature.

0.12.0 (2020-10-07)
===================

Expand Down
Binary file added emu/data/Olympus.tif
Binary file not shown.
8 changes: 8 additions & 0 deletions emu/data/Olympus_Mons.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "FeatureCollection",
"name": "olympus_mons",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 133.821082132244499, -182.172079007781065 ], [ 119.87448803812417, -187.178548682593487 ], [ 117.371253200717959, -200.052327846396878 ], [ 121.304907945213429, -211.138082126338645 ], [ 137.754736876739969, -211.495687103110981 ], [ 147.767676226364813, -206.131612451526223 ], [ 146.337256319275525, -188.608968589682775 ], [ 133.821082132244499, -182.172079007781065 ] ] ] ] } }
]
}
2 changes: 2 additions & 0 deletions emu/processes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .wps_dry_run import SimpleDryRun
from .wps_ncml import NcMLAgg
from .wps_translation import Translation
from .wps_geodata import GeoData


processes = [
Expand All @@ -41,4 +42,5 @@
SimpleDryRun(),
NcMLAgg(),
Translation(),
GeoData(),
]
10 changes: 4 additions & 6 deletions emu/processes/wps_esgf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from pywps import Process
from pywps import LiteralInput, LiteralOutput
from pywps import ComplexInput
from pywps import Format
import logging

from pywps import ComplexInput, Format, LiteralInput, LiteralOutput, Process
from pywps.app.Common import Metadata
from pywps.ext_autodoc import MetadataUrl

import logging
LOGGER = logging.getLogger("PYWPS")


Expand Down Expand Up @@ -69,7 +67,7 @@ def _handler(request, response):
datasets.append(dataset.data)
if not datasets:
raise Exception("You need to provide at least one dataset.")
response.outputs['output'].data = 'Number of datasets = {}'.format(len(datasets))
response.outputs['output'].data = f'Number of datasets = {len(datasets)}'

response.update_status('PyWPS Process completed.', 100)
return response
61 changes: 61 additions & 0 deletions emu/processes/wps_geodata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Process using an application/geo+json and 'image/tiff; application=geotiff' output. Used to test UI interactions.
Author: Trevor James Smith
"""
from pathlib import Path
from pywps import Process, ComplexOutput
from pywps import FORMATS
import logging


LOGGER = logging.getLogger("PYWPS")

__all__ = ["GeoData"]

DATA_DIR = Path(__file__).parent.parent.joinpath('data')


class GeoData(Process):
def __init__(self):
inputs = list()
outputs = [
ComplexOutput(
"raster",
"DEM of region in GeoTIFF format.",
abstract="Elevation data for Olympus Martian region in byte range.",
as_reference=True,
supported_formats=[FORMATS.GEOTIFF],
),
ComplexOutput(
"vector",
"Region definition in GeoJSON format",
abstract="Rough vector polygon of the Olympus Mons plateau.",
as_reference=True,
supported_formats=[FORMATS.GEOJSON],
),
]

super(GeoData, self).__init__(
self._handler,
identifier="geodata",
title="deliver vector and raster data",
abstract="Return an example raster and vector dataset",
version="1.0",
inputs=inputs,
outputs=outputs,
store_supported=True,
status_supported=True,
)

@staticmethod
def _handler(request, response):
response.update_status("PyWPS Process started.", 0)

response.outputs['vector'].file = DATA_DIR / "Olympus_Mons.geojson"

response.outputs['raster'].file = DATA_DIR / "Olympus.tif"

response.update_status("PyWPS Process completed.", 100)

return response
8 changes: 4 additions & 4 deletions emu/processes/wps_output_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Author: David Huard
"""
import logging
import os
from pywps import Process, ComplexOutput
from pywps import FORMATS

import logging
from pywps import FORMATS, ComplexOutput, Process

LOGGER = logging.getLogger("PYWPS")

# TODO: can be replaced by eggshell function.
Expand All @@ -16,7 +16,7 @@

class OutputFormats(Process):
def __init__(self):
inputs = []
inputs = list()
outputs = [
ComplexOutput('netcdf', 'netCDF dummy output file.',
abstract="A very small test netCDF file. ",
Expand Down
6 changes: 4 additions & 2 deletions emu/processes/wps_ultimate_question.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self):
identifier='ultimate_question',
version='2.0',
title='Answer to the ultimate question',
abstract='This process gives the answer to the ultimate question of "What is the meaning of life?"',
abstract='This process gives the answer to the ultimate question of life, the universe, and everything.',
profile='',
metadata=[Metadata('Ultimate Question'), Metadata('What is the meaning of life')],
inputs=inputs,
Expand All @@ -24,9 +24,11 @@ def __init__(self):
@staticmethod
def _handler(request, response):
import time
sleep_delay = .1
response.update_status('PyWPS Process started.', 0)
time.sleep(sleep_delay)

sleep_delay = .1
response.update_status("Contacting the Deep Thought supercomputer.", 10)
time.sleep(sleep_delay)
response.update_status('Thinking...', 20)
time.sleep(sleep_delay)
Expand Down
6 changes: 2 additions & 4 deletions emu/processes/wps_wordcounter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import json
import logging
import re

from collections import Counter

from pywps import Process
from pywps import ComplexInput, ComplexOutput, FORMATS
from pywps import FORMATS, ComplexInput, ComplexOutput, Process
from pywps.ext_autodoc import MetadataUrl

import logging
LOGGER = logging.getLogger("PYWPS")


Expand Down
2 changes: 1 addition & 1 deletion environment-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ channels:
- conda-forge
- defaults
dependencies:
- pywps>=4.2.7
- pywps=4.4.0
- sphinx
- nbsphinx
- ipython
4 changes: 2 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ channels:
- defaults
dependencies:
- pip
- python=3.6
- pywps>=4.2.6,<4.3
- python>=3.6
- pywps>=4.4
- jinja2
- click
- psutil
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pywps>=4.2.6<4.3
pywps>=4.4.0
jinja2
click
psutil
Expand Down
Empty file added requirements_extra.txt
Empty file.
9 changes: 6 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.rst')).read()
CHANGES = open(os.path.join(here, 'CHANGES.rst')).read()
REQUIRES_PYTHON = ">=2.7.0"
REQUIRES_PYTHON = ">=3.6.0"

about = {}
with open(os.path.join(here, 'emu', '__version__.py'), 'r') as f:
exec(f.read(), about)

reqs = [line.strip() for line in open('requirements.txt')]
dev_reqs = [line.strip() for line in open('requirements_dev.txt')]
extra_reqs = [line.strip() for line in open("requirements_extra.txt")]

classifiers = [
'Development Status :: 3 - Alpha',
Expand All @@ -27,10 +28,11 @@
'Operating System :: POSIX',
'Programming Language :: Python',
'Natural Language :: English',
"Programming Language :: Python :: 2",
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Scientific/Engineering :: Atmospheric Science',
'License :: OSI Approved :: Apache Software License',
]
Expand All @@ -51,6 +53,7 @@
include_package_data=True,
install_requires=reqs,
extras_require={
"extra:": extra_reqs,
"dev": dev_reqs, # pip install ".[dev]"
},
entry_points={
Expand Down
6 changes: 3 additions & 3 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ def get_output(doc):
[identifier_el] = xpath_ns(output_el, './ows:Identifier')

lit_el = xpath_ns(output_el, './wps:Data/wps:LiteralData')
if lit_el != []:
if lit_el:
output[identifier_el.text] = lit_el[0].text

ref_el = xpath_ns(output_el, './wps:Reference')
if ref_el != []:
if ref_el:
output[identifier_el.text] = ref_el[0].attrib['href']

data_el = xpath_ns(output_el, './wps:Data/wps:ComplexData')
if data_el != []:
if data_el:
output[identifier_el.text] = data_el[0].text

return output
7 changes: 5 additions & 2 deletions tests/test_wps_bbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
from pywps import Service
from pywps.tests import assert_response_success

from .common import client_for
from .common import client_for, get_output
from emu.processes.wps_bbox import Box


@pytest.mark.skip(reason="no way of currently testing this")
@pytest.mark.xfail(reason="bbox doesn't seem to be joined to response output")
def test_wps_bbox():
client = client_for(Service(processes=[Box()]))
datainputs = "bbox=101,42,110,46"
resp = client.get(
service='WPS', request='Execute', version='1.0.0',
identifier='bbox',
datainputs=datainputs)

assert_response_success(resp)
out = get_output(resp.xml)
assert out["bbox"] == [101, 42, 110, 46]
1 change: 1 addition & 0 deletions tests/test_wps_caps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_wps_caps():
'chomsky',
'dummyprocess',
'esgf_demo',
'geodata',
'hello',
'inout',
'multiple_outputs',
Expand Down
4 changes: 2 additions & 2 deletions tests/test_wps_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ def test_wps_error():
client = client_for(Service(processes=[ShowError()]))
datainputs = "message=tomorrow-is-another-day;nice=true"
client.get(
"?service=WPS&request=Execute&version=1.0.0&identifier=hello&datainputs={}".format(
datainputs))
f"?service=WPS&request=Execute&version=1.0.0&identifier=hello&datainputs={datainputs}"
)
# TODO: parse ows:Exception
# assert_response_success(resp)
# assert get_output(resp.xml) == {'output': "Hello LovelySugarBird"}
17 changes: 17 additions & 0 deletions tests/test_wps_geo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pywps import Service
from pywps.tests import assert_response_success
from emu.processes.wps_geodata import GeoData
from .common import client_for, CFG_FILE, get_output


class TestGeoData:
def test_wps_geodata(self):

client = client_for(Service(processes=[GeoData()], cfgfiles=CFG_FILE))

resp = client.get(
service='wps', request='execute', version='1.0.0',
identifier='geodata', asobj=True)

assert_response_success(resp)
assert set(get_output(resp.xml).keys()) == {"vector", "raster"}
4 changes: 2 additions & 2 deletions tests/test_wps_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_wps_hello():
client = client_for(Service(processes=[SayHello()]))
datainputs = "name=LovelySugarBird"
resp = client.get(
"?service=WPS&request=Execute&version=1.0.0&identifier=hello&datainputs={}".format(
datainputs))
f"?service=WPS&request=Execute&version=1.0.0&identifier=hello&datainputs={datainputs}"
)
assert_response_success(resp)
assert get_output(resp.xml) == {'output': "Hello LovelySugarBird"}
2 changes: 0 additions & 2 deletions tests/test_wps_inout.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import pytest
import os
from pywps import Service
from pywps.tests import assert_response_success

Expand Down
3 changes: 1 addition & 2 deletions tests/test_wps_multiple_outputs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pytest
from lxml import etree
from pywps import Service
from pywps.tests import assert_response_success

Expand All @@ -10,7 +9,7 @@
@pytest.fixture
def resp():
client = client_for(Service(processes=[MultipleOutputs()], cfgfiles=CFG_FILE))
datainputs = 'count={}'.format(5)
datainputs = 'count=5'
response = client.get(
service="WPS", request="Execute", version="1.0.0", identifier="multiple_outputs",
datainputs=datainputs)
Expand Down
3 changes: 1 addition & 2 deletions tests/test_wps_ncmeta.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

from .common import client_for, resource_file
from emu.processes.wps_ncmeta import NCMeta
import owslib.wps

OPENDAP_URL = 'http://test.opendap.org:80/opendap/netcdf/examples/sresa1b_ncar_ccsm3_0_run1_200001.nc'
NC_URL = 'http://test.opendap.org:80/opendap/netcdf/examples/sresa1b_ncar_ccsm3_0_run1_200001.nc.nc4'
NC_URL = "http://test.opendap.org/opendap/nc4_test_files/nc4_nc_classic_comp.nc.nc4"
NC_FILE_URL = "file://{}".format(resource_file('test.nc'))


Expand Down
2 changes: 1 addition & 1 deletion tests/test_wps_nonpyid.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
def test_wps_nonpyid():
d = {'a': 1}
client = client_for(Service(processes=[NonPyID()]))
datainputs = "input 1=10;input-2={}".format(json.dumps(d))
datainputs = f"input 1=10;input-2={json.dumps(d)}"
resp = client.get(
service='WPS', request='Execute', version='1.0.0',
identifier='non.py-id',
Expand Down
Loading

0 comments on commit c6cfe68

Please sign in to comment.