Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Python Geomark
[![Build Status](https://travis-ci.org/pauperpythonistas/python-geomark.svg?branch=master)](https://travis-ci.org/pauperpythonistas/python-geomark)

A small python library that provides implementations of the BC Governments [Geomark Web Service]

Expand Down
1 change: 1 addition & 0 deletions geomark/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# These will probably never change
# If, for some reason they ever do, note the names of the keywords, as they are specifically referred to by name.
PROTOCOL = 'https'
GEOMARK_BASE_URL = '{protocol}://apps.gov.bc.ca/pub/geomark'
GEOMARK_ID_BASE_URL = GEOMARK_BASE_URL + '/geomarks/{geomarkId}'
GEOMARK_GROUP_BASE_URL = GEOMARK_BASE_URL + '/geomarkGroups/{geomarkGroupId}'
144 changes: 116 additions & 28 deletions geomark/geomark.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import os
import requests
# from six.moves.urllib.parse import urlparse

from . import config as _config

_base_url = "{protocol}://apps.gov.bc.ca/pub/geomark"
_gm_id_base = _base_url + '/geomarks/{geomarkId}'


class Geomark:

def __init__(self, geomarkId=None, geomarkUrl=None, protocol='https', config=_config):
def __init__(self, geomarkId=None, geomarkUrl=None, config=_config):
self.config = config
self.protocol = protocol

self.logger = self.config.LOGGER

Expand All @@ -15,13 +20,12 @@ def __init__(self, geomarkId=None, geomarkUrl=None, protocol='https', config=_co

if geomarkId:
self.geomarkUrl = self.config.GEOMARK_ID_BASE_URL.format(
protocol=self.protocol,
protocol=config.PROTOCOL,
geomarkId=geomarkId,
)
self.geomarkId = geomarkId
else:
self.geomarkId = self._parse_geomark_url()
self.geomarkUrl = geomarkUrl
self.geomarkId, self.geomarkUrl, self.config.PROTOCOL = self._parse_geomark_url(geomarkUrl)

self.logger.info('Initiated Geomark object with the following parameters: '
'geomarkId={geomarkId}; '
Expand All @@ -31,55 +35,139 @@ def __init__(self, geomarkId=None, geomarkUrl=None, protocol='https', config=_co

geomarkId=geomarkId,
geomarkUrl=geomarkUrl,
protocol=protocol,
custom_config='YES' if config is _config else 'NO'
))
protocol=config.PROTOCOL,
custom_config='YES' if config is _config else 'NO')
)

def _parse_geomark_url(self, url):
raise NotImplementedError("_parse_geomark_url() method has not yet been implemented")
# return url

def _handle_request(self, request):
if request.ok:
return request.content
else:
raise ValueError("A non-ok status code ({}) was returned from the server.".format(request.status_code))
"""
Parse the geomarkUrl for GeomarkId, protocol, and strip off the format specifier if one is present
:param url:
:return: GeomarkId, GeomarkUrl (no format), PROTOCOL
"""
parsed = requests.compat.urlparse(url)
tail = parsed.path.split('/')[-1]
gmid, format = os.path.splitext(tail)
if format:
url = url.replace(format, '')

return gmid, url, parsed.scheme

def boundingBox(self, fileFormatExtension='json', srid=None):
url = self.geomarkUrl + '/boundingBox.{fileFormatExtension}'.format(
fileFormatExtension=fileFormatExtension,
)
return self._handle_request(requests.get(url, params={'srid': srid} if srid else None))
return self._handle_get(requests.get(url, params={'srid': srid} if srid else None))

def feature(self, fileFormatExtension='json', srid=None):
url = self.geomarkUrl + '/feature.{fileFormatExtension}'.format(
fileFormatExtension=fileFormatExtension,
)
return self._handle_request(requests.get(url, params={'srid': srid} if srid else None))
return self._handle_get(requests.get(url, params={'srid': srid} if srid else None))

def info(self, fileFormatExtension='json', srid=None):
url = self.geomarkUrl + '.{fileFormatExtension}?'.format(
fileFormatExtension=fileFormatExtension,
)
return self._handle_request(requests.get(url, params={'srid': srid} if srid else None))
return self._handle_get(requests.get(url, params={'srid': srid} if srid else None))

def parts(self, fileFormatExtension='json', srid=None):
url = self.geomarkUrl + '/parts.{fileFormatExtension}'.format(
fileFormatExtension=fileFormatExtension,
)
return self._handle_request(requests.get(url, params={'srid': srid} if srid else None))
return self._handle_get(requests.get(url, params={'srid': srid} if srid else None))

def point(self, fileFormatExtension='json', srid=None):
url = self.geomarkUrl + '/point.{fileFormatExtension}'.format(
fileFormatExtension=fileFormatExtension,
)
return self._handle_request(requests.get(url, params={'srid': srid} if srid else None))
return self._handle_get(requests.get(url, params={'srid': srid} if srid else None))

def copy(self, **kwargs):
copy_url = self.config.GEOMARK_BASE_URL + '/geomarks/copy'
raise NotImplementedError("copy is not implemented")

@classmethod
def create(cls, config=_config, **kwargs):
create_url = config.GEOMARK_BASE_URL + '/geomarks/new'
raise NotImplementedError("create is not implemented")
"""
This is almost the same as create but provides the geomarkUrl kwarg and a different post url.
TIP: If you do a straight copy without altering any of the parameters the geomark
server will notice that the geometry is identical and instead of giving you back a new Geomark
instance you will simply be given back the original. The workaround is to specify a tiny buffer
on the geometry using the bufferMeters kwarg.
:param kwargs:
:return:
"""
# Todo: allow sourcing from multiple geomarks, ie geomark_url as a list.
# Todo: put allow overlap into formData, NOT as a query param

url = self.config.GEOMARK_BASE_URL.format(protocol=self.config.PROTOCOL) + '/geomarks/copy'
kwargs.update({'geomarkUrl': self.geomarkUrl})
params = self._validate_post_kwargs(**kwargs)
r = requests.post(url, params=params)
return Geomark._handle_post(r)

@staticmethod
def create(
format=None,
srid=4326,
resultFormat='geojson',
multiple=False,
allowOverlap=False,
# callback ---- Not supported
# redirectUrl ---- Not supported
# failureRedirectUrl ---- Not supported
bufferMetres=None,
bufferJoin=None,
bufferCap=None,
bufferMitreLimit=None,
bufferSegments=None,
body=None,
extra_kwargs={}):
"""
Create a Geomark layer
:param format:
:param srid:
:param resultFormat:
:param multiple:
:param allowOverlap:
:param bufferMetres:
:param bufferJoin:
:param bufferCap:
:param bufferMitreLimit:
:param bufferSegments:
:param body:
:param extra_kwargs: put the overridden config object here, key: "config"
:return:
"""
import inspect
kwargs = inspect.getargvalues(inspect.currentframe()).locals # collect the method's named args
form_data = Geomark._validate_post_kwargs(**kwargs)

config = extra_kwargs.get("config", _config)
url = config.GEOMARK_BASE_URL.format(protocol=config.PROTOCOL) + '/geomarks/new'

return Geomark._handle_post(requests.post(url, data=form_data))

@staticmethod
def _handle_get(response):
if response.ok:
return response.content
else:
response.raise_for_status()

@staticmethod
def _handle_post(response):
if response.ok:
url = response.url
response.close()
return Geomark(geomarkUrl=url)
else:
response.raise_for_status()

@staticmethod
def _validate_post_kwargs(**kwargs):
"""
Used by both create() and copy()
Doesn't do anything right now.
We don't need to pull out Nones because requests will do that for us.
:param kwargs:
:return:
"""
# TODO Actually validate post kwargs.
return kwargs
12 changes: 11 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import pytest
from geomark import config


_geomark_ids = ['gm-abcdefghijklmnopqrstuvwxyz0000bc', 'gm-abcdefghijklmnopqrstuv0bcislands']
_kml_files = ['point.kml', 'line.kml', 'polygon.kml']


@pytest.fixture(scope='module',
Expand Down Expand Up @@ -30,3 +31,12 @@ def geomarkHttpsUrl(request):
geomarkId=request.param
)
yield gm_url


@pytest.fixture(scope='module',
params=_kml_files)
def kmlFile(request):
filename = request.module.__file__
test_dir, _ = os.path.splitext(filename)
with open(os.path.join(test_dir, request.param), 'r') as kml:
yield kml.read()
10 changes: 5 additions & 5 deletions tests/test_geomark.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ def test_point(geomarkId):
assert gm.point()


# def test_copy(geomarkId):
# gm = Geomark(geomarkId=geomarkId)
# assert gm.copy()
def test_copy(geomarkId):
gm = Geomark(geomarkId=geomarkId)
assert gm.copy()


# def test_create(geomarkId):
# assert Geomark.create()
def test_create(kmlFile):
assert Geomark.create(format='kml', body=kmlFile)
10 changes: 10 additions & 0 deletions tests/test_geomark/line.kml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Folder><name>line</name>
<Placemark>
<Style><LineStyle><color>ff0000ff</color></LineStyle><PolyStyle><fill>0</fill></PolyStyle></Style>
<LineString><coordinates>-123.275826838094,50.0397327592544 -123.532260057459,49.7971192942263</coordinates></LineString>
</Placemark>
</Folder>
</Document></kml>
9 changes: 9 additions & 0 deletions tests/test_geomark/point.kml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Folder><name>point</name>
<Placemark>
<Point><coordinates>-123.378707231492,49.9629563462708</coordinates></Point>
</Placemark>
</Folder>
</Document></kml>
10 changes: 10 additions & 0 deletions tests/test_geomark/polygon.kml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Folder><name>polygon</name>
<Placemark>
<Style><LineStyle><color>ff0000ff</color></LineStyle><PolyStyle><fill>0</fill></PolyStyle></Style>
<Polygon><outerBoundaryIs><LinearRing><coordinates>-123.476981040111,50.0489459288124 -123.128416125165,49.9214970832597 -123.429379664061,49.7495179181764 -123.598287772625,49.910748385442 -123.598287772625,50.0305195896964 -123.476981040111,50.0489459288124</coordinates></LinearRing></outerBoundaryIs></Polygon>
</Placemark>
</Folder>
</Document></kml>