diff --git a/.env b/.env
index 68a6815..49747e5 100644
--- a/.env
+++ b/.env
@@ -33,6 +33,13 @@ DEV_LOG_ENQUEUE=true
DEV_LOG_ROTATION="1 days"
DEV_LOG_RETENTION="1 months"
DEV_LOG_FORMAT='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} [id:{extra[request_id]}] - {message}'
+# opa
+DEV_OPA_URL="http://localhost:8383"
+# oidc
+DEV_APP_URI="http://localhost:5000"
+DEV_OIDC_WELL_KNOWN_ENDPOINT="http://localhost:8282/auth/realms/pygeoapi/.well-known/openid-configuration"
+DEV_OIDC_CLIENT_ID="pygeoapi-client"
+DEV_OIDC_CLIENT_SECRET="YPwVOZMlAwF5dFgs9QkPetqVGgyteoFO"
# prod configs
PROD_ROOT_PATH=
@@ -45,3 +52,10 @@ PROD_LOG_ENQUEUE=false
PROD_LOG_ROTATION="1 days"
PROD_LOG_RETENTION="1 months"
PROD_LOG_FORMAT='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} [id:{extra[request_id]}] - {message}'
+# opa
+PROD_OPA_URL="http://localhost:8383"
+# oidc
+PROD_APP_URI="http://localhost:5000"
+PROD_OIDC_WELL_KNOWN_ENDPOINT="http://localhost:8282/auth/realms/pygeoapi/.well-known/openid-configuration"
+PROD_OIDC_CLIENT_ID="pygeoapi-client"
+PROD_OIDC_CLIENT_SECRET="YPwVOZMlAwF5dFgs9QkPetqVGgyteoFO"
\ No newline at end of file
diff --git a/app/config/app.py b/app/config/app.py
index 8e15d89..d4f3b5c 100644
--- a/app/config/app.py
+++ b/app/config/app.py
@@ -31,6 +31,11 @@ class DevConfig(GlobalConfig):
LOG_ROTATION: Optional[str] = Field(None, env="DEV_LOG_ROTATION")
LOG_RETENTION: Optional[str] = Field(None, env="DEV_LOG_RETENTION")
LOG_FORMAT: Optional[str] = Field(None, env="DEV_LOG_FORMAT")
+ OPA_URL: Optional[str] = Field(None, env="DEV_OPA_URL")
+ APP_URI: Optional[str] = Field(None, env="DEV_APP_URI")
+ OIDC_WELL_KNOWN_ENDPOINT: Optional[str] = Field(None, env="DEV_OIDC_WELL_KNOWN_ENDPOINT")
+ OIDC_CLIENT_ID: Optional[str] = Field(None, env="DEV_OIDC_CLIENT_ID")
+ OIDC_CLIENT_SECRET: Optional[str] = Field(None, env="DEV_OIDC_CLIENT_SECRET")
class ProdConfig(GlobalConfig):
@@ -45,6 +50,11 @@ class ProdConfig(GlobalConfig):
LOG_ROTATION: Optional[str] = Field(None, env="PROD_LOG_ROTATION")
LOG_RETENTION: Optional[str] = Field(None, env="PROD_LOG_RETENTION")
LOG_FORMAT: Optional[str] = Field(None, env="PROD_LOG_FORMAT")
+ OPA_URL: Optional[str] = Field(None, env="PROD_OPA_URL")
+ APP_URI: Optional[str] = Field(None, env="PROD_APP_URI")
+ OIDC_WELL_KNOWN_ENDPOINT: Optional[str] = Field(None, env="PROD_OIDC_WELL_KNOWN_ENDPOINT")
+ OIDC_CLIENT_ID: Optional[str] = Field(None, env="PROD_OIDC_CLIENT_ID")
+ OIDC_CLIENT_SECRET: Optional[str] = Field(None, env="PROD_OIDC_CLIENT_SECRET")
class FactoryConfig:
diff --git a/app/config/auth.py b/app/config/auth.py
new file mode 100644
index 0000000..355ee6f
--- /dev/null
+++ b/app/config/auth.py
@@ -0,0 +1,22 @@
+"""Authn and Authz module"""
+from fastapi_opa import OPAConfig
+from fastapi_opa.auth import OIDCAuthentication
+from fastapi_opa.auth import OIDCConfig
+
+from app.config.app import configuration as cfg
+
+
+# The hostname of your Open Policy Agent instance
+opa_host = cfg.OPA_URL
+# In this example we use OIDC authentication flow (using Keycloak)
+oidc_config = OIDCConfig(
+ well_known_endpoint=cfg.OIDC_WELL_KNOWN_ENDPOINT,
+ # well known endpoint
+ app_uri=cfg.APP_URI, # host where this app is running
+ # client id of your app configured in the identity provider
+ client_id=cfg.OIDC_CLIENT_ID,
+ # the client secret retrieved from your identity provider
+ client_secret=cfg.OIDC_CLIENT_SECRET,
+)
+oidc_auth = OIDCAuthentication(oidc_config)
+opa_config = OPAConfig(authentication=oidc_auth, opa_host=opa_host)
\ No newline at end of file
diff --git a/app/main.py b/app/main.py
index 26af885..be74a9c 100644
--- a/app/main.py
+++ b/app/main.py
@@ -2,12 +2,14 @@
import uvicorn
from app.config.app import configuration as cfg
from app.config.logging import create_logger
+from app.config.auth import opa_config
from app.utils.app_exceptions import app_exception_handler
from app.utils.app_exceptions import AppExceptionError
from app.utils.request_exceptions import http_exception_handler
from app.utils.request_exceptions import request_validation_exception_handler
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
+from fastapi_opa import OPAMiddleware
from mangum import Mangum
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.cors import CORSMiddleware
@@ -40,6 +42,8 @@ async def custom_validation_exception_handler(request, e):
async def custom_app_exception_handler(request, e):
return await app_exception_handler(request, e)
+ # Add OPAMiddleware to the pygeoapi app
+ pygeoapi_app.add_middleware(OPAMiddleware, config=opa_config)
app.mount(path="/api", app=pygeoapi_app)
app.logger = create_logger(name="app.main")
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 4b4a2ff..0000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-furo==2021.11.23
-sphinx==4.3.0
-sphinx-click==3.0.2
diff --git a/example-config.yml b/example-config.yml
index 83c3d50..5bb1217 100644
--- a/example-config.yml
+++ b/example-config.yml
@@ -175,100 +175,6 @@ resources:
id_field: id
title_field: name
- gdps-temperature:
- type: collection
- title: Global Deterministic Prediction System sample
- description: Global Deterministic Prediction System sample
- keywords:
- - gdps
- - global
- extents:
- spatial:
- bbox: [-180,-90,180,90]
- crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
- links:
- - type: text/html
- rel: canonical
- title: information
- href: https://eccc-msc.github.io/open-data/msc-data/nwp_gdps/readme_gdps_en
- hreflang: en-CA
- providers:
- - type: coverage
- name: rasterio
- data: tests/data/CMC_glb_TMP_TGL_2_latlon.15x.15_2020081000_P000.grib2
- options:
- DATA_ENCODING: COMPLEX_PACKING
- format:
- name: GRIB
- mimetype: application/x-grib2
-
- test-data:
- type: stac-collection
- title: pygeoapi test data
- description: pygeoapi test data
- keywords:
- - poi
- - portugal
- links:
- - type: text/html
- rel: canonical
- title: information
- href: https://github.com/geopython/pygeoapi/tree/master/tests/data
- hreflang: en-US
- extents:
- spatial:
- bbox: [-180,-90,180,90]
- crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
- providers:
- - type: stac
- name: FileSystem
- data: tests/data
- file_types:
- - .gpkg
- - .sqlite
- - .csv
- - .grib2
- - .tif
- - .shp
-
- canada-metadata:
- type: collection
- title:
- en: Open Canada sample data
- fr: Exemple de donn\u00e9es Canada Ouvert
- description:
- en: Sample metadata records from open.canada.ca
- fr: Exemples d'enregistrements de m\u00e9tadonn\u00e9es sur ouvert.canada.ca
- keywords:
- en:
- - canada
- - open data
- fr:
- - canada
- - donn\u00e9es ouvertes
- links:
- - type: text/html
- rel: canonical
- title: information
- href: https://open.canada.ca/en/open-data
- hreflang: en-CA
- - type: text/html
- rel: alternate
- title: informations
- href: https://ouvert.canada.ca/fr/donnees-ouvertes
- hreflang: fr-CA
- extents:
- spatial:
- bbox: [-180,-90,180,90]
- crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
- providers:
- - type: record
- name: TinyDBCatalogue
- data: tests/data/open.canada.ca/sample-records.tinydb
- id_field: externalId
- time_field: recordCreated
- title_field: title
-
hello-world:
type: process
processor:
diff --git a/poetry.lock b/poetry.lock
index 5c66ae1..e06abc2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -142,6 +142,17 @@ category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "cffi"
+version = "1.15.0"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
[[package]]
name = "cfgv"
version = "3.3.1"
@@ -154,7 +165,7 @@ python-versions = ">=3.6.1"
name = "charset-normalizer"
version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.5.0"
@@ -219,6 +230,25 @@ tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
[package.extras]
toml = ["tomli"]
+[[package]]
+name = "cryptography"
+version = "36.0.2"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.12"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools_rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
+
[[package]]
name = "darglint"
version = "1.8.1"
@@ -277,6 +307,24 @@ dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<6.0.0)"]
test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"]
+[[package]]
+name = "fastapi-opa"
+version = "1.3.3"
+description = "Fastapi OPA middleware incl. auth flow."
+category = "main"
+optional = false
+python-versions = ">=3.6.2,<4.0"
+
+[package.dependencies]
+fastapi = ">=0.65.2"
+itsdangerous = "*"
+PyJWT = {version = "*", extras = ["crypto"]}
+requests = "*"
+
+[package.extras]
+graphql = ["graphene (>=2,<3)"]
+saml = ["python3-saml", "python-multipart"]
+
[[package]]
name = "filelock"
version = "3.6.0"
@@ -794,6 +842,14 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
[[package]]
name = "pydantic"
version = "1.9.0"
@@ -864,6 +920,23 @@ category = "dev"
optional = false
python-versions = ">=3.5"
+[[package]]
+name = "pyjwt"
+version = "2.3.0"
+description = "JSON Web Token implementation in Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cryptography = {version = ">=3.3.1", optional = true, markers = "extra == \"crypto\""}
+
+[package.extras]
+crypto = ["cryptography (>=3.3.1)"]
+dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
+docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
+tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
+
[[package]]
name = "pymdown-extensions"
version = "9.3"
@@ -1027,7 +1100,7 @@ python-versions = ">=3.7"
name = "requests"
version = "2.27.1"
description = "Python HTTP for Humans."
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
@@ -1238,7 +1311,7 @@ python-versions = "*"
name = "urllib3"
version = "1.26.9"
description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
@@ -1349,7 +1422,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "d05de3326b59fe56abf982b5270a644058883be292dfb6cd1478f448d862e244"
+content-hash = "d8b5248072c54e8f5f6ede4ead5bdf0e2362b0bd0a4418ee98ae70bc38053344"
[metadata.files]
affine = [
@@ -1396,6 +1469,58 @@ certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
+cffi = [
+ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
+ {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
+ {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
+ {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
+ {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
+ {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
+ {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
+ {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
+ {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
+ {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
+ {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
+ {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
+ {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
+ {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
+ {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
+ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
+ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
+]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
@@ -1463,6 +1588,28 @@ coverage = [
{file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"},
{file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"},
]
+cryptography = [
+ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
+ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"},
+ {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"},
+ {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"},
+ {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"},
+ {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"},
+ {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"},
+ {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"},
+ {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"},
+ {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"},
+ {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"},
+ {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"},
+ {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"},
+ {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"},
+ {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"},
+ {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"},
+ {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"},
+ {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"},
+ {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"},
+ {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
+]
darglint = [
{file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"},
{file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"},
@@ -1483,6 +1630,10 @@ fastapi = [
{file = "fastapi-0.75.1-py3-none-any.whl", hash = "sha256:f46f8fc81261c2bd956584114da9da98c84e2410c807bc2487532dabf55e7ab8"},
{file = "fastapi-0.75.1.tar.gz", hash = "sha256:8b62bde916d657803fb60fffe88e2b2c9fb854583784607e4347681cae20ad01"},
]
+fastapi-opa = [
+ {file = "fastapi-opa-1.3.3.tar.gz", hash = "sha256:fd8e784e1b82a8f33daf592206c2e8d5eee8dfd535e94af05a232fa8f3363137"},
+ {file = "fastapi_opa-1.3.3-py3-none-any.whl", hash = "sha256:947d2fa2c71382df964cdec5d6b2636d9e11449b0195ed32af4ef0c3beb0a448"},
+]
filelock = [
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
{file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"},
@@ -1735,6 +1886,10 @@ pycodestyle = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
]
+pycparser = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
pydantic = [
{file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
{file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
@@ -1788,6 +1943,10 @@ pygments = [
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
]
+pyjwt = [
+ {file = "PyJWT-2.3.0-py3-none-any.whl", hash = "sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f"},
+ {file = "PyJWT-2.3.0.tar.gz", hash = "sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41"},
+]
pymdown-extensions = [
{file = "pymdown-extensions-9.3.tar.gz", hash = "sha256:a80553b243d3ed2d6c27723bcd64ca9887e560e6f4808baa96f36e93061eaf90"},
{file = "pymdown_extensions-9.3-py3-none-any.whl", hash = "sha256:b37461a181c1c8103cfe1660081726a0361a8294cbfda88e5b02cefe976f0546"},
diff --git a/pyproject.toml b/pyproject.toml
index cc7aacc..b096c20 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,7 @@ uvicorn = "^0.17.6"
pydantic = {extras = ["dotenv"], version = "^1.9.0"}
pygeoapi = "^0.12.0"
jinja2 = "3.0.3"
+fastapi-opa = "^1.3.3"
[tool.poetry.dev-dependencies]
pytest = "^6.2.5"
diff --git a/scripts/iam/README.md b/scripts/iam/README.md
new file mode 100644
index 0000000..37f8a69
--- /dev/null
+++ b/scripts/iam/README.md
@@ -0,0 +1,84 @@
+# How to use Keycloak and Open Policy Agent
+
+## Run Keycloak and OPA together
+
+```shell
+docker-compose up -d
+```
+
+## Configure Keycloak
+
+Open the administration interface at `http://localhost:8282/auth` and access with the credentials `admin/admin`.
+
+### Create a new realm
+
+- *Add realm* with the name `pygeoapi` and click on the *Create* button
+- In the *Clients* menu under *Configure* click on the button *Create* on the top-right corner to *Add Client* with a **Client ID** called `pygeoapi-client` and then click *Save*.
+- In the *settings* page
+ - Set *Access Type* to `confidential`
+ - Set *Root URL* to `http://localhost:5000`
+ - Set *Valid Redirect URIs* to `http://localhost:5000/*`
+ - Set *Admin URL* to `http://localhost:5000`
+ - Set *Web Origins* to `http://localhost:5000/*`
+ - Click the *Save* button
+- Set *client_id* and *client_secret* in the application configuration
+
+### Create new users
+
+Add new users in the *Users* menu
+
+- Add *Username* with value `francbartoli`
+- Under *Credentials*
+ - Set *Password* to `francbartoli`
+ - Set *Temporary* to `off`
+- Under *Attributes*
+ - Set key/value
+ - `user=francbartoli`
+ - `company=geobeyond`
+
+- Add *Username* with value `tomkralidis`
+- Under *Credentials*
+ - Set *Password* to `tomkralidis`
+ - Set *Temporary* to `off`
+- Under *Attributes*
+ - Set key/value
+ - `user=tomkralidis`
+ - `company=osgeo`
+
+### Create the mapping
+
+Under the tab *Mappers* of the new realm set the following mapping:
+
+- Name: user
+- Name: company
+
+## Update the policy
+
+If there are changes in the `auth.rego` then restart the OPA server
+
+```shell
+docker-compose stop opa
+docker-compose up -d
+```
+
+## Get Access Token
+
+```shell
+export KC_RESPONSE=$(curl -X POST 'http://localhost:8282/auth/realms/pygeoapi/protocol/openid-connect/token' \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "username=francbartoli" \
+ -d 'password=francbartoli' \
+ -d 'grant_type=password' \
+ -d 'client_id=pygeoapi-client' \
+ -d 'client_secret=YPwVOZMlAwF5dFgs9QkPetqVGgyteoFO' | jq -r '.')
+```
+
+```shell
+KC_ACCESS_TOKEN=$(echo $KC_RESPONSE| jq -r .access_token)
+KC_ID_TOKEN=$(echo $KC_RESPONSE| jq -r .id_token)
+KC_REFRESH_TOKEN=$(echo $KC_RESPONSE| jq -r .refresh_token)
+```
+
+```shell
+curl -v -X POST 'http://localhost:8282/auth/realms/pygeoapi/protocol/openid-connect/userinfo' -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer $KC_ACCESS_TOKEN" | jq .
+```
diff --git a/scripts/iam/docker-compose.yml b/scripts/iam/docker-compose.yml
new file mode 100644
index 0000000..9a8b96d
--- /dev/null
+++ b/scripts/iam/docker-compose.yml
@@ -0,0 +1,20 @@
+version: '2'
+services:
+ keycloak:
+ image: jboss/keycloak:latest
+ ports:
+ - 8282:8080
+ environment:
+ - KEYCLOAK_USER=admin
+ - KEYCLOAK_PASSWORD=admin
+ opa:
+ image: openpolicyagent/opa:latest
+ ports:
+ - 8383:8181
+ command:
+ - "run"
+ - "--server"
+ - "--log-level=debug"
+ - "/policy/auth.rego"
+ volumes:
+ - ./policy:/policy
\ No newline at end of file
diff --git a/scripts/iam/policy/auth.rego b/scripts/iam/policy/auth.rego
new file mode 100644
index 0000000..3cd6e68
--- /dev/null
+++ b/scripts/iam/policy/auth.rego
@@ -0,0 +1,6 @@
+# policy/auth.rego
+package httpapi.authz
+
+# HTTP API request
+import input
+default allow = true
diff --git a/tests/data/ne_110m_lakes.geojson b/tests/data/ne_110m_lakes.geojson
new file mode 100644
index 0000000..2deb2ea
--- /dev/null
+++ b/tests/data/ne_110m_lakes.geojson
@@ -0,0 +1,2411 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 0,
+ "scalerank": 0,
+ "name": "Lake Baikal",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Baikal",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 106.57998579307912,
+ 52.79998159444554
+ ],
+ [
+ 106.53998823448521,
+ 52.93999888774037
+ ],
+ [
+ 107.0800069519353,
+ 53.18001007751998
+ ],
+ [
+ 107.2999935242018,
+ 53.37999787048953
+ ],
+ [
+ 107.59997521365611,
+ 53.51998932556822
+ ],
+ [
+ 108.03994835818912,
+ 53.859968573616456
+ ],
+ [
+ 108.37997928266967,
+ 54.25999583598784
+ ],
+ [
+ 109.05270307824526,
+ 55.027597561251326
+ ],
+ [
+ 109.19346967980832,
+ 55.53560272889659
+ ],
+ [
+ 109.50699059452313,
+ 55.73091380474372
+ ],
+ [
+ 109.92980716353523,
+ 55.7129562445223
+ ],
+ [
+ 109.70000206913326,
+ 54.980003567110515
+ ],
+ [
+ 109.66000451053935,
+ 54.71999359803395
+ ],
+ [
+ 109.47996382043448,
+ 54.33999095317566
+ ],
+ [
+ 109.31997358605884,
+ 53.81999685323869
+ ],
+ [
+ 109.22003136600637,
+ 53.619983222052994
+ ],
+ [
+ 108.99999311730755,
+ 53.78002513286093
+ ],
+ [
+ 108.60001753136845,
+ 53.4399942083804
+ ],
+ [
+ 108.800005324338,
+ 53.37999787048953
+ ],
+ [
+ 108.76000776574409,
+ 53.200008856816936
+ ],
+ [
+ 108.45997439985749,
+ 53.14001251892607
+ ],
+ [
+ 108.17999148970011,
+ 52.79998159444554
+ ],
+ [
+ 107.79996300662566,
+ 52.579995022179034
+ ],
+ [
+ 107.31999230349876,
+ 52.42000478780339
+ ],
+ [
+ 106.64003380740229,
+ 52.32001089131862
+ ],
+ [
+ 106.1000150899522,
+ 52.03997630472897
+ ],
+ [
+ 105.740037062607,
+ 51.759993394571595
+ ],
+ [
+ 105.24001590375084,
+ 51.52000804300813
+ ],
+ [
+ 104.81998986208251,
+ 51.46001170511727
+ ],
+ [
+ 104.30002160036167,
+ 51.50000926371118
+ ],
+ [
+ 103.7600028829116,
+ 51.60000316019595
+ ],
+ [
+ 103.6200114278329,
+ 51.73999461527464
+ ],
+ [
+ 103.85999677939637,
+ 51.85998729105637
+ ],
+ [
+ 104.39996382041414,
+ 51.85998729105637
+ ],
+ [
+ 105.05997521364597,
+ 52.0000045843512
+ ],
+ [
+ 105.4800012553143,
+ 52.28001333272471
+ ],
+ [
+ 105.98002241417046,
+ 52.51999868428817
+ ],
+ [
+ 106.26000532432784,
+ 52.619992580772944
+ ],
+ [
+ 106.57998579307912,
+ 52.79998159444554
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 1,
+ "scalerank": 0,
+ "name": "Lake Winnipeg",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Winnipeg",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -98.95540137408423,
+ 53.92978343364277
+ ],
+ [
+ -97.95799455441879,
+ 54.337097072967325
+ ],
+ [
+ -97.8050064766187,
+ 54.05938792583079
+ ],
+ [
+ -97.64380184608419,
+ 53.42521474874492
+ ],
+ [
+ -96.39258622921722,
+ 51.39730235453108
+ ],
+ [
+ -96.23789282915149,
+ 50.6910147161819
+ ],
+ [
+ -96.72558915890605,
+ 50.448910630894474
+ ],
+ [
+ -96.92110694048235,
+ 50.75405996357799
+ ],
+ [
+ -97.23568722205913,
+ 51.49778717712263
+ ],
+ [
+ -98.20097713905517,
+ 52.18456696228162
+ ],
+ [
+ -99.23680538612963,
+ 53.21628693298888
+ ],
+ [
+ -98.95540137408423,
+ 53.92978343364277
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 2,
+ "scalerank": 0,
+ "name": "Great Slave Lake",
+ "name_alt": "https://en.wikipedia.org/wiki/Great_Slave_Lake",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -115.00000342492967,
+ 61.9723932969562
+ ],
+ [
+ -115.80669837122196,
+ 62.54171255151576
+ ],
+ [
+ -116.0585951404287,
+ 62.77880402287089
+ ],
+ [
+ -115.84279435917783,
+ 62.7503044704619
+ ],
+ [
+ -114.45259497766185,
+ 62.42820526798667
+ ],
+ [
+ -113.35341142459757,
+ 62.04448192000336
+ ],
+ [
+ -111.77890804731261,
+ 62.44360484480964
+ ],
+ [
+ -111.04001258018727,
+ 62.92000987410843
+ ],
+ [
+ -110.20001217328286,
+ 63.08000010848407
+ ],
+ [
+ -109.40000932497237,
+ 62.87817780216814
+ ],
+ [
+ -109.09010576051801,
+ 62.814099026126215
+ ],
+ [
+ -109.11651241741916,
+ 62.6928144395372
+ ],
+ [
+ -110.10089677614705,
+ 62.51561595320837
+ ],
+ [
+ -111.27599300824811,
+ 62.34911448836395
+ ],
+ [
+ -112.63050981326654,
+ 61.55991201440246
+ ],
+ [
+ -113.64000891808828,
+ 61.07999298770784
+ ],
+ [
+ -115.3409903634076,
+ 60.87659455020702
+ ],
+ [
+ -116.43999304895887,
+ 60.86000641544133
+ ],
+ [
+ -118.06110856817108,
+ 61.31196849226605
+ ],
+ [
+ -118.3474735177165,
+ 61.36137116153708
+ ],
+ [
+ -118.3849906075604,
+ 61.52110301375126
+ ],
+ [
+ -118.17960262741634,
+ 61.55629466414203
+ ],
+ [
+ -116.8028132800801,
+ 61.32589529076871
+ ],
+ [
+ -115.67879920129957,
+ 61.69179026961132
+ ],
+ [
+ -115.00000342492967,
+ 61.9723932969562
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 3,
+ "scalerank": 0,
+ "name": "L. Ontario",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Ontario",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -79.05630591502026,
+ 43.25410431576152
+ ],
+ [
+ -79.36168779164908,
+ 43.20237620703736
+ ],
+ [
+ -79.76047481964547,
+ 43.29720246029295
+ ],
+ [
+ -79.46116492381094,
+ 43.639197089200565
+ ],
+ [
+ -79.1561706204243,
+ 43.75743276628437
+ ],
+ [
+ -78.45052893747877,
+ 43.9031861435636
+ ],
+ [
+ -77.60536088734519,
+ 44.039327704436545
+ ],
+ [
+ -77.16148617217414,
+ 43.850140285815996
+ ],
+ [
+ -76.88269181995948,
+ 44.0694550644627
+ ],
+ [
+ -76.56555355498425,
+ 44.20802541765336
+ ],
+ [
+ -76.35303422718391,
+ 44.134670722015045
+ ],
+ [
+ -76.23926856149336,
+ 43.979150499032656
+ ],
+ [
+ -76.17999569365458,
+ 43.5900011256587
+ ],
+ [
+ -76.9300015937227,
+ 43.2599954290428
+ ],
+ [
+ -77.74915056019732,
+ 43.342832750006664
+ ],
+ [
+ -78.53499406605984,
+ 43.379988104824534
+ ],
+ [
+ -79.05630591502026,
+ 43.25410431576152
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 4,
+ "scalerank": 0,
+ "name": "L. Erie",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Erie",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -83.12001135937246,
+ 42.08001577409016
+ ],
+ [
+ -82.57123348664891,
+ 42.01702220312636
+ ],
+ [
+ -81.82918575715374,
+ 42.33552989355732
+ ],
+ [
+ -81.39226152212595,
+ 42.61517690690481
+ ],
+ [
+ -81.09496700715081,
+ 42.66075552018623
+ ],
+ [
+ -80.54515560578142,
+ 42.560089830081665
+ ],
+ [
+ -80.27917700877515,
+ 42.71566172949635
+ ],
+ [
+ -79.79135148793986,
+ 42.84203644466612
+ ],
+ [
+ -78.92000932485044,
+ 42.96500051530464
+ ],
+ [
+ -78.90057898630869,
+ 42.8667119410855
+ ],
+ [
+ -79.76220598012725,
+ 42.269616604169045
+ ],
+ [
+ -80.51644934764329,
+ 41.980331936199136
+ ],
+ [
+ -81.03119828970264,
+ 41.845508124349635
+ ],
+ [
+ -81.62351355663209,
+ 41.568935858723535
+ ],
+ [
+ -82.34744869660895,
+ 41.435920722004255
+ ],
+ [
+ -82.84610043000939,
+ 41.48710622818935
+ ],
+ [
+ -83.46283281119673,
+ 41.69396698665372
+ ],
+ [
+ -83.12001135937246,
+ 42.08001577409016
+ ],
+ [
+ -83.12001135937246,
+ 42.08001577409016
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 5,
+ "scalerank": 0,
+ "name": "Lake Superior",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Superior",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -89.60000342482806,
+ 48.00998973244721
+ ],
+ [
+ -89.19405921095924,
+ 48.40546946877693
+ ],
+ [
+ -88.62641944044917,
+ 48.56251414651193
+ ],
+ [
+ -88.40423661981026,
+ 48.80632355406499
+ ],
+ [
+ -88.17895321323383,
+ 48.93670319273738
+ ],
+ [
+ -87.24903581414156,
+ 48.73511343036678
+ ],
+ [
+ -86.56000810417788,
+ 48.71108388935106
+ ],
+ [
+ -86.32103044304411,
+ 48.57729360614741
+ ],
+ [
+ -85.98652889681881,
+ 48.01035146747326
+ ],
+ [
+ -84.8642201403039,
+ 47.86007640236849
+ ],
+ [
+ -85.04061764193222,
+ 47.575700995466306
+ ],
+ [
+ -84.64500871452178,
+ 47.28220469826462
+ ],
+ [
+ -84.81528255892351,
+ 46.90233124448706
+ ],
+ [
+ -84.39559241406505,
+ 46.77683502866624
+ ],
+ [
+ -84.60490780306328,
+ 46.439594631529474
+ ],
+ [
+ -84.9100054593145,
+ 46.48000560158172
+ ],
+ [
+ -85.11999264193253,
+ 46.76001434995524
+ ],
+ [
+ -86.1026200019625,
+ 46.67265534116582
+ ],
+ [
+ -86.99000769727856,
+ 46.45000743263628
+ ],
+ [
+ -87.69427995476835,
+ 46.83104360614041
+ ],
+ [
+ -88.2612220934425,
+ 46.958581041036766
+ ],
+ [
+ -87.93992387566777,
+ 47.485913194359185
+ ],
+ [
+ -88.82260901564527,
+ 47.154796454449
+ ],
+ [
+ -89.62498897984119,
+ 46.83083690041124
+ ],
+ [
+ -90.39703487828177,
+ 46.57648550067064
+ ],
+ [
+ -91.00999487991183,
+ 46.92000458433087
+ ],
+ [
+ -92.01192338740282,
+ 46.71167104754619
+ ],
+ [
+ -92.00877112503301,
+ 46.85843211525511
+ ],
+ [
+ -91.33000118687926,
+ 47.28000844989221
+ ],
+ [
+ -90.61999284540505,
+ 47.68000987404746
+ ],
+ [
+ -89.60000342482806,
+ 48.00998973244721
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 6,
+ "scalerank": 0,
+ "name": "Lake Victoria",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Victoria",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 33.85036827976734,
+ 0.128157863766091
+ ],
+ [
+ 33.85036827976734,
+ 0.128157863766091
+ ],
+ [
+ 34.13624230320599,
+ -0.319308363449238
+ ],
+ [
+ 34.0726286150547,
+ -1.059831638191795
+ ],
+ [
+ 33.579428745261055,
+ -1.506005954599821
+ ],
+ [
+ 33.251748488098286,
+ -1.957968031424549
+ ],
+ [
+ 33.64717654799571,
+ -2.30089283611342
+ ],
+ [
+ 33.07672041192572,
+ -2.547131035984201
+ ],
+ [
+ 32.95176679864397,
+ -2.43044565186915
+ ],
+ [
+ 32.37309410983957,
+ -2.489925225437091
+ ],
+ [
+ 31.926558058405476,
+ -2.714511000177573
+ ],
+ [
+ 31.648022088352292,
+ -2.32921152100937
+ ],
+ [
+ 31.836020949030114,
+ -1.629305922048232
+ ],
+ [
+ 31.866199985488578,
+ -1.027378838712494
+ ],
+ [
+ 31.815143670384202,
+ -0.64042571371094
+ ],
+ [
+ 32.27284183119332,
+ -0.056120293786734
+ ],
+ [
+ 32.906136508930246,
+ 0.0867650415003
+ ],
+ [
+ 33.33184695815069,
+ 0.324993394365833
+ ],
+ [
+ 33.85036827976734,
+ 0.128157863766091
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 7,
+ "scalerank": 0,
+ "name": "Lake Ladoga",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Ladoga",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 29.836711460089845,
+ 61.22608226179696
+ ],
+ [
+ 29.836711460089845,
+ 61.22608226179696
+ ],
+ [
+ 30.85199832532828,
+ 61.77504100203353
+ ],
+ [
+ 32.52688317234018,
+ 61.11751007755173
+ ],
+ [
+ 32.94432539239392,
+ 60.64260366478942
+ ],
+ [
+ 32.81575442885176,
+ 60.481889960361684
+ ],
+ [
+ 32.599901971168606,
+ 60.533514716221276
+ ],
+ [
+ 32.583882277158125,
+ 60.20893504499601
+ ],
+ [
+ 31.699440138482714,
+ 60.23565176049091
+ ],
+ [
+ 31.50973595553924,
+ 59.92034800886205
+ ],
+ [
+ 31.106246372204282,
+ 59.92768606224749
+ ],
+ [
+ 31.10893354668346,
+ 60.14645823835514
+ ],
+ [
+ 30.533878208139498,
+ 60.63009796817478
+ ],
+ [
+ 30.502045525847706,
+ 60.843211574946466
+ ],
+ [
+ 29.836711460089845,
+ 61.22608226179696
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 8,
+ "scalerank": 0,
+ "name": "Balqash K\u00f6li",
+ "name_alt": "Lake\rBalkhash",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 78.99076541536459,
+ 46.748619696634876
+ ],
+ [
+ 78.99076541536459,
+ 46.748619696634876
+ ],
+ [
+ 79.2451684915375,
+ 46.64516347918655
+ ],
+ [
+ 78.88989301953086,
+ 46.369934800800024
+ ],
+ [
+ 78.44506229036242,
+ 46.29717438413307
+ ],
+ [
+ 77.20606814973246,
+ 46.3955663112168
+ ],
+ [
+ 75.61128177277294,
+ 46.50718740496724
+ ],
+ [
+ 75.46333214712132,
+ 46.6706141220903
+ ],
+ [
+ 75.40566124868357,
+ 46.47080719663377
+ ],
+ [
+ 74.91122114451494,
+ 46.404661363300164
+ ],
+ [
+ 74.77138471873312,
+ 46.107831936215646
+ ],
+ [
+ 74.27802981964263,
+ 46.004065660173566
+ ],
+ [
+ 74.0999528339648,
+ 44.98893382423201
+ ],
+ [
+ 73.4367375019309,
+ 45.60946442319282
+ ],
+ [
+ 73.4399931171653,
+ 45.80586070411809
+ ],
+ [
+ 73.73713260284359,
+ 46.012747300798594
+ ],
+ [
+ 73.67925499867667,
+ 46.18307282163262
+ ],
+ [
+ 74.02068118682908,
+ 46.20490611427593
+ ],
+ [
+ 74.09432010284502,
+ 46.42781240496693
+ ],
+ [
+ 74.939384800114,
+ 46.81678091082786
+ ],
+ [
+ 76.20313195181177,
+ 46.7800131292522
+ ],
+ [
+ 77.18131513866464,
+ 46.64340648048862
+ ],
+ [
+ 77.85993004752152,
+ 46.64785065366573
+ ],
+ [
+ 78.29700931184618,
+ 46.46853343361292
+ ],
+ [
+ 78.39710656119556,
+ 46.657669175801175
+ ],
+ [
+ 78.99076541536459,
+ 46.748619696634876
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 9,
+ "scalerank": 0,
+ "name": "Lake Tanganyika",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Tanganyika",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 30.806006300588507,
+ -8.578339125201033
+ ],
+ [
+ 30.46442508313922,
+ -8.498188978716335
+ ],
+ [
+ 30.566847771941724,
+ -8.1150082332721
+ ],
+ [
+ 30.27735639824263,
+ -7.848357842646031
+ ],
+ [
+ 30.14702843600253,
+ -7.299244073112582
+ ],
+ [
+ 29.536523064906334,
+ -6.7541610652979
+ ],
+ [
+ 29.19318484875913,
+ -6.038029066597119
+ ],
+ [
+ 29.371985304489016,
+ -5.616452731960017
+ ],
+ [
+ 29.101717563602506,
+ -5.054006442895265
+ ],
+ [
+ 29.281034783655315,
+ -3.455499362810748
+ ],
+ [
+ 29.652588331833897,
+ -4.420143324403149
+ ],
+ [
+ 29.600085076625334,
+ -4.896393324405054
+ ],
+ [
+ 29.79195966972506,
+ -5.040880629093131
+ ],
+ [
+ 29.758421665167646,
+ -5.466901136907339
+ ],
+ [
+ 29.951226434048635,
+ -5.860985609565162
+ ],
+ [
+ 29.722041456834177,
+ -6.244114678577112
+ ],
+ [
+ 30.52803877129051,
+ -6.922729587433992
+ ],
+ [
+ 30.604158156056457,
+ -7.541916599155222
+ ],
+ [
+ 31.189032016735865,
+ -8.729906101113095
+ ],
+ [
+ 31.022117140433124,
+ -8.786543470904988
+ ],
+ [
+ 30.806006300588507,
+ -8.578339125201033
+ ],
+ [
+ 30.806006300588507,
+ -8.578339125201033
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 10,
+ "scalerank": 0,
+ "name": "Lake Malawi",
+ "name_alt": "Lake Nyasa",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 35.2602047055542,
+ -14.277474460510291
+ ],
+ [
+ 35.236226840970915,
+ -14.401291192281633
+ ],
+ [
+ 34.881209751125766,
+ -14.012012627826891
+ ],
+ [
+ 34.706543409979076,
+ -14.262023207255027
+ ],
+ [
+ 34.54675988133266,
+ -14.047669366108266
+ ],
+ [
+ 34.55110070164528,
+ -13.67208505621096
+ ],
+ [
+ 34.3209338722171,
+ -13.379389743709737
+ ],
+ [
+ 34.32997724786827,
+ -12.944584242405995
+ ],
+ [
+ 34.032527703596315,
+ -12.208556817272836
+ ],
+ [
+ 34.322690870915096,
+ -11.652983493702834
+ ],
+ [
+ 34.25990400568048,
+ -10.447579034062642
+ ],
+ [
+ 33.906592238100984,
+ -9.801726983278854
+ ],
+ [
+ 33.99557905450757,
+ -9.495440769084837
+ ],
+ [
+ 34.52422895685345,
+ -10.03013681400887
+ ],
+ [
+ 34.60789310073403,
+ -11.080511976773494
+ ],
+ [
+ 34.93702029800096,
+ -11.463434340056267
+ ],
+ [
+ 34.69388268406766,
+ -12.422393894096615
+ ],
+ [
+ 34.86756717300062,
+ -13.701127211159019
+ ],
+ [
+ 35.05597944513687,
+ -13.742933444883136
+ ],
+ [
+ 35.2602047055542,
+ -14.277474460510291
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 11,
+ "scalerank": 0,
+ "name": "Aral Sea",
+ "name_alt": "https://en.wikipedia.org/wiki/Aral_Sea",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 60.05285159692946,
+ 44.264636949229114
+ ],
+ [
+ 59.77002648299603,
+ 44.15999217383806
+ ],
+ [
+ 59.06288618351405,
+ 44.36928172462015
+ ],
+ [
+ 59.34571129744745,
+ 44.99720205339871
+ ],
+ [
+ 59.35930219914022,
+ 45.190006822279685
+ ],
+ [
+ 58.96139367049281,
+ 45.37500844988459
+ ],
+ [
+ 58.92144778833119,
+ 45.101846828789746
+ ],
+ [
+ 58.92144778833119,
+ 44.73556427670495
+ ],
+ [
+ 58.497132602782614,
+ 44.212340399749735
+ ],
+ [
+ 58.285000848224456,
+ 44.473952338227335
+ ],
+ [
+ 58.285000848224456,
+ 44.89255727800766
+ ],
+ [
+ 58.689989048095896,
+ 45.50001373959863
+ ],
+ [
+ 58.78000939314833,
+ 45.88673432065487
+ ],
+ [
+ 59.20427290226462,
+ 45.93905670835039
+ ],
+ [
+ 59.535002068932585,
+ 45.70501414650049
+ ],
+ [
+ 59.55784305200561,
+ 46.30533926043519
+ ],
+ [
+ 59.77002648299603,
+ 46.25299103452352
+ ],
+ [
+ 60.12359663273702,
+ 46.096023871436955
+ ],
+ [
+ 60.12359663273702,
+ 45.88673432065487
+ ],
+ [
+ 59.94675988143425,
+ 45.808211981787366
+ ],
+ [
+ 59.84071984237133,
+ 45.520477606786216
+ ],
+ [
+ 60.12359663273702,
+ 45.572774156265595
+ ],
+ [
+ 60.23997195825834,
+ 44.78403677019473
+ ],
+ [
+ 60.05285159692946,
+ 44.264636949229114
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 12,
+ "scalerank": 1,
+ "name": "V\u00e4nern",
+ "name_alt": null,
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 13.979281447005576,
+ 59.20491364199721
+ ],
+ [
+ 13.979281447005576,
+ 59.20491364199721
+ ],
+ [
+ 13.984449090234762,
+ 59.086212877022774
+ ],
+ [
+ 13.91892337408865,
+ 58.902503160225166
+ ],
+ [
+ 13.28268313971111,
+ 58.608670966213566
+ ],
+ [
+ 12.830100945698888,
+ 58.50903880475484
+ ],
+ [
+ 12.460149366921371,
+ 58.50619660097878
+ ],
+ [
+ 12.537767368223768,
+ 58.77594757754237
+ ],
+ [
+ 12.522161085671598,
+ 58.880282294339665
+ ],
+ [
+ 12.697085808979608,
+ 58.953843695707135
+ ],
+ [
+ 13.027039829163215,
+ 58.993531195707305
+ ],
+ [
+ 13.195298292705559,
+ 59.12900096296045
+ ],
+ [
+ 13.59144982265505,
+ 59.336481838612315
+ ],
+ [
+ 13.979281447005576,
+ 59.20491364199721
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 13,
+ "scalerank": 1,
+ "name": "Lake Okeechobee",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Okeechobee",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -80.70643775096435,
+ 26.788959458924822
+ ],
+ [
+ -80.93244462759287,
+ 26.823272609966622
+ ],
+ [
+ -80.91970638703292,
+ 27.068916530866048
+ ],
+ [
+ -80.69369951040441,
+ 27.034629218040394
+ ],
+ [
+ -80.70643775096435,
+ 26.788959458924822
+ ],
+ [
+ -80.70643775096435,
+ 26.788959458924822
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 14,
+ "scalerank": 1,
+ "name": "Lago de Nicaragua",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Nicaragua",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -84.85548682324658,
+ 11.147898667846633
+ ],
+ [
+ -85.29013729525353,
+ 11.176165676310276
+ ],
+ [
+ -85.79132117383625,
+ 11.509737046754324
+ ],
+ [
+ -85.8851655748783,
+ 11.900100816287136
+ ],
+ [
+ -85.5653401354239,
+ 11.940330918826362
+ ],
+ [
+ -85.03684526237491,
+ 11.5216484643976
+ ],
+ [
+ -84.85548682324658,
+ 11.147898667846633
+ ],
+ [
+ -84.85548682324658,
+ 11.147898667846633
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 15,
+ "scalerank": 1,
+ "name": "Lake Tana",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Tana",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 37.14370730972843,
+ 11.850594794151519
+ ],
+ [
+ 37.01482628759251,
+ 12.035596421756424
+ ],
+ [
+ 37.24401126480697,
+ 12.233878892460353
+ ],
+ [
+ 37.518361443844526,
+ 12.160601711470477
+ ],
+ [
+ 37.48187788264647,
+ 11.825092474815477
+ ],
+ [
+ 37.33635704931254,
+ 11.713393866416595
+ ],
+ [
+ 37.14370730972843,
+ 11.850594794151519
+ ],
+ [
+ 37.14370730972843,
+ 11.850594794151519
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 16,
+ "scalerank": 1,
+ "name": "Lago Titicaca",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Titicaca",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -69.40673987494259,
+ -16.126198825752063
+ ],
+ [
+ -69.7290974595793,
+ -15.928794854397104
+ ],
+ [
+ -69.98365556504908,
+ -15.73717864345884
+ ],
+ [
+ -69.8770212470148,
+ -15.669844252182529
+ ],
+ [
+ -69.8679778713637,
+ -15.546079196843493
+ ],
+ [
+ -69.88559953477524,
+ -15.35425628017606
+ ],
+ [
+ -69.59675411647981,
+ -15.410480238509614
+ ],
+ [
+ -68.98697221543571,
+ -15.885903415594846
+ ],
+ [
+ -68.9596870591856,
+ -15.91329192470954
+ ],
+ [
+ -68.74623755560401,
+ -16.356003920154023
+ ],
+ [
+ -68.90524593776611,
+ -16.506640720284835
+ ],
+ [
+ -69.00115739609983,
+ -16.536406345284952
+ ],
+ [
+ -69.09084184434238,
+ -16.461992282784657
+ ],
+ [
+ -69.18205074733753,
+ -16.40116912197712
+ ],
+ [
+ -69.25098710801488,
+ -16.227536309476427
+ ],
+ [
+ -69.40673987494259,
+ -16.126198825752063
+ ],
+ [
+ -69.40673987494259,
+ -16.126198825752063
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 17,
+ "scalerank": 1,
+ "name": "Lake Winnipegosis",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Winnipegosis",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -99.40377193886468,
+ 53.1258531764781
+ ],
+ [
+ -99.54846594928192,
+ 53.120427151087455
+ ],
+ [
+ -99.80498775917877,
+ 53.14290639913442
+ ],
+ [
+ -100.43146114785316,
+ 53.2841897650204
+ ],
+ [
+ -100.60685095905177,
+ 53.531616522833886
+ ],
+ [
+ -100.33487789589965,
+ 53.745246893928496
+ ],
+ [
+ -100.42673275429846,
+ 53.90740753846039
+ ],
+ [
+ -100.4044860501968,
+ 53.94507965760117
+ ],
+ [
+ -100.32629960813921,
+ 54.094011135466346
+ ],
+ [
+ -100.23586585162842,
+ 54.23033356385231
+ ],
+ [
+ -99.99549292682278,
+ 54.215864162810576
+ ],
+ [
+ -100.04778947630214,
+ 54.10013479269293
+ ],
+ [
+ -100.18356930214904,
+ 53.930351874397985
+ ],
+ [
+ -99.87128862180926,
+ 53.928465684619326
+ ],
+ [
+ -99.91066606321566,
+ 53.821314602262134
+ ],
+ [
+ -100.05649695514333,
+ 53.44332733826322
+ ],
+ [
+ -99.66551306842301,
+ 53.29581696228607
+ ],
+ [
+ -99.39317827024485,
+ 53.26791168884846
+ ],
+ [
+ -99.40377193886468,
+ 53.1258531764781
+ ],
+ [
+ -99.40377193886468,
+ 53.1258531764781
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 18,
+ "scalerank": 1,
+ "name": "Lake Onega",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Onega",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 35.71464725112901,
+ 62.2802298041189
+ ],
+ [
+ 36.0541614112866,
+ 61.716310736733874
+ ],
+ [
+ 36.391401808423325,
+ 61.27605337182331
+ ],
+ [
+ 36.10945519383887,
+ 61.01508738874924
+ ],
+ [
+ 35.35074181492962,
+ 60.948579820389625
+ ],
+ [
+ 34.866843702948586,
+ 61.11637319604125
+ ],
+ [
+ 35.207288038887384,
+ 61.114435329830485
+ ],
+ [
+ 35.57832482274313,
+ 61.08634918887975
+ ],
+ [
+ 35.16000410334027,
+ 61.39428904890701
+ ],
+ [
+ 34.85733523940689,
+ 61.55179881453273
+ ],
+ [
+ 34.48691857273877,
+ 61.86697337508076
+ ],
+ [
+ 34.265019972477376,
+ 62.21914826114994
+ ],
+ [
+ 34.289824659977455,
+ 62.29774811466581
+ ],
+ [
+ 34.66561567560399,
+ 62.22979360620195
+ ],
+ [
+ 34.62613488133297,
+ 62.45223480900245
+ ],
+ [
+ 34.8356311378443,
+ 62.29676626245225
+ ],
+ [
+ 35.080267368314026,
+ 62.1411943630377
+ ],
+ [
+ 35.21658979669991,
+ 62.193284206787894
+ ],
+ [
+ 35.463706495919666,
+ 62.2560193955901
+ ],
+ [
+ 35.13969526544963,
+ 62.48776235620312
+ ],
+ [
+ 34.614352654770414,
+ 62.762448432050576
+ ],
+ [
+ 34.99541466649072,
+ 62.748469957115674
+ ],
+ [
+ 35.23395307795005,
+ 62.675347805422575
+ ],
+ [
+ 35.71464725112901,
+ 62.2802298041189
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 19,
+ "scalerank": 1,
+ "name": "Great Salt Lake",
+ "name_alt": "https://en.wikipedia.org/wiki/Great_Salt_Lake",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -112.18405127648089,
+ 41.34124949804567
+ ],
+ [
+ -112.13875688357706,
+ 41.142346910154174
+ ],
+ [
+ -112.17193315310845,
+ 40.851460272783186
+ ],
+ [
+ -112.67882727745939,
+ 41.130487168943205
+ ],
+ [
+ -112.70549231652205,
+ 41.16753917089636
+ ],
+ [
+ -112.87814327680917,
+ 41.62815705013003
+ ],
+ [
+ -112.58955624067522,
+ 41.43891795507716
+ ],
+ [
+ -112.40532975955472,
+ 41.33801972102742
+ ],
+ [
+ -112.21901038292629,
+ 41.42855683040267
+ ],
+ [
+ -112.18405127648089,
+ 41.34124949804567
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 20,
+ "scalerank": 1,
+ "name": "Great Bear Lake",
+ "name_alt": "https://en.wikipedia.org/wiki/Great_Bear_Lake",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -117.7592923653704,
+ 66.22368419052789
+ ],
+ [
+ -117.97374955938167,
+ 65.85489533147692
+ ],
+ [
+ -118.10710059291085,
+ 65.76691620550001
+ ],
+ [
+ -119.7200520432652,
+ 65.73479930283061
+ ],
+ [
+ -119.74568355368197,
+ 65.65436493596833
+ ],
+ [
+ -119.6648099371452,
+ 65.52742178004333
+ ],
+ [
+ -119.70219783590836,
+ 65.36794830999061
+ ],
+ [
+ -121.35509436098008,
+ 64.87999359807459
+ ],
+ [
+ -121.33610327211281,
+ 64.99461192489797
+ ],
+ [
+ -120.94527441468938,
+ 65.37774099390991
+ ],
+ [
+ -121.05728308168202,
+ 65.4463414577774
+ ],
+ [
+ -122.56450374412293,
+ 65.0310696478799
+ ],
+ [
+ -123.232705851873,
+ 65.18041453720339
+ ],
+ [
+ -123.17963415590926,
+ 65.31937246363624
+ ],
+ [
+ -122.32599117087979,
+ 65.79378795029179
+ ],
+ [
+ -122.35622188377054,
+ 65.90184337021411
+ ],
+ [
+ -124.95363440005697,
+ 66.04925039332666
+ ],
+ [
+ -124.89753963280414,
+ 66.15115631780624
+ ],
+ [
+ -119.48724971579031,
+ 66.96929759385118
+ ],
+ [
+ -119.35743771042887,
+ 66.87519481064767
+ ],
+ [
+ -120.1769492193738,
+ 66.46527151149239
+ ],
+ [
+ -117.60545162643749,
+ 66.55934845647975
+ ],
+ [
+ -117.61278967982294,
+ 66.41997711858856
+ ],
+ [
+ -117.7592923653704,
+ 66.22368419052789
+ ],
+ [
+ -117.7592923653704,
+ 66.22368419052789
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 21,
+ "scalerank": 1,
+ "name": "Lake Athabasca",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Athabasca",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -109.65330135785099,
+ 59.03763703066841
+ ],
+ [
+ -111.08626298708849,
+ 58.56017263450765
+ ],
+ [
+ -111.19948605023998,
+ 58.685565497463884
+ ],
+ [
+ -111.160005255969,
+ 58.75985036888345
+ ],
+ [
+ -109.09672034385136,
+ 59.55042226830068
+ ],
+ [
+ -106.54517066122398,
+ 59.31968699811746
+ ],
+ [
+ -106.54695349813804,
+ 59.292815253325685
+ ],
+ [
+ -109.65330135785099,
+ 59.03763703066841
+ ],
+ [
+ -109.65330135785099,
+ 59.03763703066841
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 22,
+ "scalerank": 1,
+ "name": "Reindeer Lake",
+ "name_alt": "https://en.wikipedia.org/wiki/Reindeer_Lake",
+ "admin": null,
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -101.89514441608819,
+ 58.01403025983099
+ ],
+ [
+ -101.89514441608819,
+ 58.01403025983099
+ ],
+ [
+ -101.54384802936804,
+ 57.86809601503873
+ ],
+ [
+ -101.97090206582807,
+ 57.34867035585697
+ ],
+ [
+ -101.93403093138781,
+ 57.23066722271848
+ ],
+ [
+ -103.20416012247362,
+ 56.34539826112639
+ ],
+ [
+ -103.2825015938281,
+ 56.40994212505895
+ ],
+ [
+ -103.1487371488406,
+ 56.70411021588043
+ ],
+ [
+ -103.07832800984292,
+ 56.71080231386223
+ ],
+ [
+ -103.01440426309787,
+ 56.56510061301529
+ ],
+ [
+ -102.57680823445028,
+ 56.938281968811054
+ ],
+ [
+ -102.81322791218561,
+ 57.28714956321349
+ ],
+ [
+ -102.81369300007623,
+ 57.46434804954232
+ ],
+ [
+ -102.12874772826359,
+ 58.01914622662788
+ ],
+ [
+ -101.89514441608819,
+ 58.01403025983099
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 23,
+ "scalerank": 0,
+ "name": "Lake Huron",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Huron",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -80.41056433787725,
+ 45.590137437515665
+ ],
+ [
+ -79.76292945017934,
+ 44.824602769543844
+ ],
+ [
+ -80.08500281443844,
+ 44.493925279308144
+ ],
+ [
+ -80.89769222687659,
+ 44.63164297136599
+ ],
+ [
+ -81.40233842642287,
+ 45.25005483660284
+ ],
+ [
+ -81.27567949087549,
+ 44.6201708033972
+ ],
+ [
+ -81.75265296092948,
+ 44.06464915625956
+ ],
+ [
+ -81.70017554393709,
+ 43.590052802090995
+ ],
+ [
+ -81.78272864452336,
+ 43.310845038417995
+ ],
+ [
+ -82.43000179719522,
+ 42.98001251888543
+ ],
+ [
+ -82.4799987454376,
+ 43.39001333268915
+ ],
+ [
+ -82.6599877591102,
+ 43.970003770516996
+ ],
+ [
+ -83.02999101432,
+ 44.06999766700177
+ ],
+ [
+ -83.6500048489579,
+ 43.62999868425261
+ ],
+ [
+ -83.84870073112015,
+ 43.63831858985161
+ ],
+ [
+ -83.89998959016984,
+ 43.88998281511303
+ ],
+ [
+ -83.34999732128743,
+ 44.29001007748441
+ ],
+ [
+ -83.25878841829228,
+ 44.74574453386644
+ ],
+ [
+ -83.33999793163895,
+ 45.200006211928155
+ ],
+ [
+ -84.08000444205858,
+ 45.58998240821879
+ ],
+ [
+ -84.93000423861146,
+ 45.789996039404485
+ ],
+ [
+ -84.75355506055087,
+ 45.924483954444085
+ ],
+ [
+ -84.71999121777729,
+ 45.91998810483469
+ ],
+ [
+ -83.83919226757845,
+ 46.010215155616294
+ ],
+ [
+ -84.33670711946846,
+ 46.40876963966737
+ ],
+ [
+ -84.14870825879063,
+ 46.55576325132161
+ ],
+ [
+ -83.95257036002683,
+ 46.334278062518635
+ ],
+ [
+ -83.20251278352643,
+ 46.21015127215355
+ ],
+ [
+ -82.44199072948692,
+ 46.19963511818216
+ ],
+ [
+ -81.63136837434045,
+ 46.09754832618957
+ ],
+ [
+ -80.73679765493583,
+ 45.90381338152733
+ ],
+ [
+ -80.41056433787725,
+ 45.590137437515665
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "type": "Feature",
+ "properties": {
+ "id": 24,
+ "scalerank": 0,
+ "name": "Lake Michigan",
+ "name_alt": "https://en.wikipedia.org/wiki/Lake_Michigan",
+ "admin": "admin-0",
+ "featureclass": "Lake"
+ },
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -85.53999284538475,
+ 46.03000722918408
+ ],
+ [
+ -84.75355506055087,
+ 45.924483954444085
+ ],
+ [
+ -84.93000423861146,
+ 45.789996039404485
+ ],
+ [
+ -85.06999569369015,
+ 45.40999339454619
+ ],
+ [
+ -85.29044735384727,
+ 45.30824249936349
+ ],
+ [
+ -85.46710323763705,
+ 44.81457754167923
+ ],
+ [
+ -85.55999162468169,
+ 45.150009263685774
+ ],
+ [
+ -85.95983801954006,
+ 44.91059235287753
+ ],
+ [
+ -86.20935767286137,
+ 44.574798895844935
+ ],
+ [
+ -86.47027197950304,
+ 44.08423452409818
+ ],
+ [
+ -86.52001054558397,
+ 43.65999685319804
+ ],
+ [
+ -86.18842871778315,
+ 43.04140412044818
+ ],
+ [
+ -86.21604977084317,
+ 42.38170278581012
+ ],
+ [
+ -86.62191647006355,
+ 41.8944198675139
+ ],
+ [
+ -86.8244364082154,
+ 41.75618541113313
+ ],
+ [
+ -87.09444576694042,
+ 41.64616628678374
+ ],
+ [
+ -87.4342183092595,
+ 41.640714423176945
+ ],
+ [
+ -87.52617652052288,
+ 41.70851390234388
+ ],
+ [
+ -87.79569495314115,
+ 42.23411489518453
+ ],
+ [
+ -87.80344641798493,
+ 42.49399567318035
+ ],
+ [
+ -87.77672970249003,
+ 42.740853990238634
+ ],
+ [
+ -87.90214840366241,
+ 43.23051402442029
+ ],
+ [
+ -87.71221167677363,
+ 43.79650014909703
+ ],
+ [
+ -87.486359829442,
+ 44.49335683855294
+ ],
+ [
+ -86.9674767727993,
+ 45.26287059181122
+ ],
+ [
+ -87.11806189649782,
+ 45.25933075619923
+ ],
+ [
+ -87.85282324903983,
+ 44.61505483660031
+ ],
+ [
+ -87.98831885450912,
+ 44.73331635190026
+ ],
+ [
+ -87.5964306302237,
+ 45.093707790703775
+ ],
+ [
+ -87.00000708692704,
+ 45.73999909116209
+ ],
+ [
+ -86.31999691439827,
+ 45.829993597998396
+ ],
+ [
+ -85.53999284538475,
+ 46.03000722918408
+ ]
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/tests/data/obs.csv b/tests/data/obs.csv
new file mode 100644
index 0000000..0f9430f
--- /dev/null
+++ b/tests/data/obs.csv
@@ -0,0 +1,6 @@
+id,stn_id,datetime,value,lat,long
+371,35,"2001-10-30T14:24:55Z",89.9,45,-75
+377,35,"2002-10-30T18:31:38Z",93.9,45,-75
+238,2147,"2007-10-30T08:57:29Z",103.5,43,-79
+297,2147,"2003-10-30T07:37:29Z",93.5,43,-79
+964,604,"2000-10-30T18:24:39Z",99.9,49,-122