From 34eba99e66edffdf4bc54929a5e5fdbafee353a7 Mon Sep 17 00:00:00 2001 From: Francesco Bartoli Date: Tue, 12 Apr 2022 18:34:47 +0200 Subject: [PATCH] Add basic authn and authz implementation via keycloak and opa (#2) --- .env | 14 + app/config/app.py | 10 + app/config/auth.py | 22 + app/main.py | 4 + docs/requirements.txt | 3 - example-config.yml | 94 -- poetry.lock | 167 ++- pyproject.toml | 1 + scripts/iam/README.md | 84 ++ scripts/iam/docker-compose.yml | 20 + scripts/iam/policy/auth.rego | 6 + tests/data/ne_110m_lakes.geojson | 2411 ++++++++++++++++++++++++++++++ tests/data/obs.csv | 6 + 13 files changed, 2741 insertions(+), 101 deletions(-) create mode 100644 app/config/auth.py delete mode 100644 docs/requirements.txt create mode 100644 scripts/iam/README.md create mode 100644 scripts/iam/docker-compose.yml create mode 100644 scripts/iam/policy/auth.rego create mode 100644 tests/data/ne_110m_lakes.geojson create mode 100644 tests/data/obs.csv 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