diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index 0c5332639..000000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,75 +0,0 @@ -[bumpversion] -current_version = 0.11.7 -commit = True -tag = True -tag_name = {new_version} -parse = - (?P\d+)\.(?P\d+)\.(?P\d+) - ((?P
a|b|rc)(?P\d+))?
-serialize = 
-	{major}.{minor}.{patch}{pre}{prenum}
-	{major}.{minor}.{patch}
-
-[bumpversion:file (global):pyproject.toml]
-search = version="{current_version}"
-replace = version="{new_version}"
-
-[bumpversion:file (core):pyproject.toml]
-search = titiler.core=={current_version}
-replace = titiler.core=={new_version}
-
-[bumpversion:file (extensions):pyproject.toml]
-search = titiler.extensions=={current_version}
-replace = titiler.extensions=={new_version}
-
-[bumpversion:file (mosaic):pyproject.toml]
-search = titiler.mosaic=={current_version}
-replace = titiler.mosaic=={new_version}
-
-[bumpversion:file (application):pyproject.toml]
-search = titiler.application=={current_version}
-replace = titiler.application=={new_version}
-
-[bumpversion:file:src/titiler/core/titiler/core/__init__.py]
-search = __version__ = "{current_version}"
-replace = __version__ = "{new_version}"
-
-[bumpversion:file:src/titiler/extensions/titiler/extensions/__init__.py]
-search = __version__ = "{current_version}"
-replace = __version__ = "{new_version}"
-
-[bumpversion:file:src/titiler/mosaic/titiler/mosaic/__init__.py]
-search = __version__ = "{current_version}"
-replace = __version__ = "{new_version}"
-
-[bumpversion:file:src/titiler/application/titiler/application/__init__.py]
-search = __version__ = "{current_version}"
-replace = __version__ = "{new_version}"
-
-[bumpversion:file:src/titiler/mosaic/pyproject.toml]
-search = titiler.core=={current_version}
-replace = titiler.core=={new_version}
-
-[bumpversion:file:src/titiler/extensions/pyproject.toml]
-search = titiler.core=={current_version}
-replace = titiler.core=={new_version}
-
-[bumpversion:file (core):src/titiler/application/pyproject.toml]
-search = titiler.core=={current_version}
-replace = titiler.core=={new_version}
-
-[bumpversion:file (extensions):src/titiler/application/pyproject.toml]
-search = titiler.extensions[cogeo,stac]=={current_version}
-replace = titiler.extensions[cogeo,stac]=={new_version}
-
-[bumpversion:file (mosaic):src/titiler/application/pyproject.toml]
-search = titiler.mosaic=={current_version}
-replace = titiler.mosaic=={new_version}
-
-[bumpversion:file:deployment/aws/lambda/Dockerfile]
-search = titiler.application=={current_version}
-replace = titiler.application=={new_version}
-
-[bumpversion:file:deployment/k8s/charts/Chart.yaml]
-search = appVersion: {current_version}
-replace = appVersion: {new_version}
diff --git a/.github/data/urls.txt b/.github/data/urls.txt
index 4782485da..40b810e3c 100644
--- a/.github/data/urls.txt
+++ b/.github/data/urls.txt
@@ -1,7 +1,7 @@
 PROT=http
 HOST=localhost
 PORT=8000
-PATH=cog/tiles/
+PATH=cog/tiles/WebMercatorQuad/
 EXT=.png
 QUERYSTRING=?url=/data/world.tif
 $(PROT)://$(HOST):$(PORT)/$(PATH)0/0/0$(EXT)$(QUERYSTRING)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..4fa1bd99b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      # Check for updates to GitHub Actions every week
+      interval: "weekly"
+    groups:
+      all:
+        patterns:
+        - "*"
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index e2e9bd880..11a365980 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -11,17 +11,20 @@ on:
 
 jobs:
   benchmark:
+    if: github.repository == 'developmentseed/titiler'
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
 
       - name: install siege
         run: |
           sudo apt update
-          sudo apt install --yes siege
+          sudo apt install --yes siege jq
+          siege -C
+
       - name: Start containers
-        run: docker-compose -f "docker-compose.yml" up -d --build benchmark
+        run: docker compose -f "docker-compose.yml" up -d --build benchmark
 
       # Let's wait a bit to make sure the docker are up
       - name: Sleep for 10 seconds
@@ -30,12 +33,39 @@ jobs:
 
       - name: Run siege (WebMercator TMS)
         run: |
-          siege --file .github/data/urls.txt -b -c 1 -r 100 > /dev/null
+          siege --file .github/data/urls.txt -b -c 1 -r 100 --json-output 2>&1 | jq -c > results.json
+          echo "Benchmark Results"
+          cat results.json | jq
+          echo "Parse Results"
+          cat results.json | jq '{"name": "WebMercator elapsed_time", "unit": "s", "value": .elapsed_time}, {"name": "WebMercator data_transferred", "unit": "Megabytes", "value": .data_transferred}, {"name": "WebMercator response_time", "unit": "s", "value": .response_time}, {"name": "WebMercator longest_transaction", "unit": "s", "value": .longest_transaction}' > output.json
 
       - name: Run siege (WGS1984Quad TMS)
         run: |
-          siege --file .github/data/urls_wgs84.txt -b -c 1 -r 100 > /dev/null
+          siege --file .github/data/urls_wgs84.txt -b -c 1 -r 100 --json-output 2>&1 | jq -c > results.json
+          echo "Benchmark Results"
+          cat results.json | jq
+          echo "Parse Results"
+          cat results.json | jq '{"name": "WGS1984Quad elapsed_time", "unit": "s", "value": .elapsed_time}, {"name": "WGS1984Quad data_transferred", "unit": "Megabytes", "value": .data_transferred}, {"name": "WGS1984Quad response_time", "unit": "s", "value": .response_time}, {"name": "WGS1984Quad longest_transaction", "unit": "s", "value": .longest_transaction}' >> output.json
 
       - name: Stop containers
         if: always()
-        run: docker-compose -f "docker-compose.yml" down
+        run: docker compose -f "docker-compose.yml" down
+
+      - name: Merge Outputs
+        run: |
+          cat output.json | jq '[inputs]' > benchmark.json
+
+      - name: Check and Store benchmark result
+        uses: benchmark-action/github-action-benchmark@v1
+        with:
+          name: TiTiler performance Benchmarks
+          tool: 'customSmallerIsBetter'
+          output-file-path: benchmark.json
+          alert-threshold: '130%'
+          comment-on-alert: true
+          fail-on-alert: false
+          # GitHub API token to make a commit comment
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          gh-pages-branch: 'gh-benchmarks'
+          # Make a commit only if main
+          auto-push: ${{ github.ref == 'refs/heads/main' }}
diff --git a/.github/workflows/check_charts.yaml b/.github/workflows/check_charts.yaml
index e79f20937..9411493d7 100644
--- a/.github/workflows/check_charts.yaml
+++ b/.github/workflows/check_charts.yaml
@@ -19,13 +19,13 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
       - name: Check Version
         run: |
-          current_version=$(grep 'version=' pyproject.toml | cut -f2 -d= | tr -d ' ' | tr -d '"')
+          current_version=$(grep '^version=' pyproject.toml | cut -f2 -d= | tr -d ' ' | tr -d '"')
           app_version=$(grep 'appVersion:' deployment/k8s/charts/Chart.yaml | cut -f2 -d: | tr -d ' ')
           if [[ "$current_version" != "$app_version" ]]; then
             echo "❌ current version from pyproject.toml ($current_version) and appVersion from Chart.yaml ($app_version) differs";
@@ -33,16 +33,16 @@ jobs:
           fi
 
       - name: Set up Helm
-        uses: azure/setup-helm@v1
+        uses: azure/setup-helm@v4
         with:
           version: v3.9.2
 
-      - uses: actions/setup-python@v2
+      - uses: actions/setup-python@v5
         with:
           python-version: 3.7
 
       - name: Set up chart-testing
-        uses: helm/chart-testing-action@v2.2.1
+        uses: helm/chart-testing-action@v2.6.1
 
       - name: Run chart-testing (list-changed)
         id: list-changed
@@ -56,7 +56,7 @@ jobs:
         run: ct lint --chart-dirs deployment/k8s --target-branch ${{ github.event.repository.default_branch }}
 
       - name: Build container
-        uses: docker/build-push-action@v2
+        uses: docker/build-push-action@v6
         if: steps.list-changed.outputs.changed == 'true'
         with:
           # See https://github.com/developmentseed/titiler/discussions/387
@@ -67,7 +67,7 @@ jobs:
           tags: "titiler:dev"
 
       - name: Create kind cluster
-        uses: helm/kind-action@v1.2.0
+        uses: helm/kind-action@v1.10.0
         if: steps.list-changed.outputs.changed == 'true'
 
       - name: Load container image in kind cluster
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d44a4ef80..07353d32c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,20 +16,22 @@ on:
       - '.github/codecov.yml'
       - 'dockerfiles/**'
   pull_request:
+  workflow_dispatch:
+
 env:
-  LATEST_PY_VERSION: '3.10'
+  LATEST_PY_VERSION: '3.12'
 
 jobs:
   tests:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11']
+        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -65,7 +67,7 @@ jobs:
 
       - name: Upload Results
         if: ${{ matrix.python-version == env.LATEST_PY_VERSION }}
-        uses: codecov/codecov-action@v1
+        uses: codecov/codecov-action@v4
         with:
           file: ./coverage.xml
           flags: unittests
@@ -77,9 +79,9 @@ jobs:
     runs-on: ubuntu-latest
     if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ env.LATEST_PY_VERSION }}
 
@@ -112,96 +114,91 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v1
+        uses: docker/setup-qemu-action@v3
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v1
+        uses: docker/setup-buildx-action@v3
 
-      - name: Login to DockerHub
-        uses: docker/login-action@v1
+      - name: Login to Docker Hub
+        if: github.repository == 'developmentseed/titiler'
+        uses: docker/login-action@v3
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
-      - name: Login to Github
-        uses: docker/login-action@v1
+      - name: Log in to the GitHub Container registry
+        uses: docker/login-action@v3
         with:
           registry: ghcr.io
           username: ${{ github.actor }}
           password: ${{ secrets.GITHUB_TOKEN }}
 
-      - name: Set tag version
-        id: tag
-        # https://stackoverflow.com/questions/58177786/get-the-current-pushed-tag-in-github-actions
-        run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
-      # Uvicorn
-      # Push `latest` when commiting to main
-      - name: Build and push uvicorn
-        if: github.ref == 'refs/heads/main'
-        uses: docker/build-push-action@v2
+      - name: Docker meta (unicorn)
+        id: meta-uvicorn
+        uses: docker/metadata-action@v5
         with:
-          # See https://github.com/developmentseed/titiler/discussions/387
-          platforms: linux/amd64
-          context: .
-          file: dockerfiles/Dockerfile.uvicorn
-          push: true
+          images: |
+            ghcr.io/${{ github.repository }}-uvicorn
+          flavor: |
+            latest=false
           tags: |
-            ghcr.io/${{ github.repository }}-uvicorn:latest
+            type=semver,pattern={{version}}
+            type=raw,value=latest,enable={{is_default_branch}}
 
-      # Push `{VERSION}` when pushing a new tag
-      - name: Build and push uvicorn
-        if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
-        uses: docker/build-push-action@v2
+      - name: Docker meta (gunicorn)
+        id: meta-gunicorn
+        uses: docker/metadata-action@v5
         with:
-          # See https://github.com/developmentseed/titiler/discussions/387
-          platforms: linux/amd64
-          context: .
-          file: dockerfiles/Dockerfile.uvicorn
-          push: true
+          images: |
+            ghcr.io/${{ github.repository }}
+          flavor: |
+            latest=false
           tags: |
-            ghcr.io/${{ github.repository }}-uvicorn:${{ steps.tag.outputs.tag }}
+            type=semver,pattern={{version}}
+            type=raw,value=latest,enable={{is_default_branch}}
 
-      # Gunicorn
-      # Push `latest` when commiting to main
-      - name: Build and push
-        if: github.ref == 'refs/heads/main'
-        uses: docker/build-push-action@v2
+      # Uvicorn
+      - name: Build and push uvicorn
+        uses: docker/build-push-action@v6
         with:
-          # See https://github.com/developmentseed/titiler/discussions/387
-          platforms: linux/amd64
+          # TODO: add `linux/arm64 once https://github.com/rasterio/rasterio-wheels/issues/69 is resolved
+          platforms: linux/amd64 # ,linux/arm64
           context: .
-          file: dockerfiles/Dockerfile.gunicorn
-          push: true
-          tags: |
-            ghcr.io/${{ github.repository }}:latest
+          file: dockerfiles/Dockerfile.uvicorn
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.meta-uvicorn.outputs.tags }}
+          labels: ${{ steps.meta-uvicorn.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
 
-      # Push `{VERSION}` when pushing a new tag
+      # Gunicorn
       - name: Build and push
-        if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
-        uses: docker/build-push-action@v2
+        uses: docker/build-push-action@v6
         with:
-          # See https://github.com/developmentseed/titiler/discussions/387
-          platforms: linux/amd64
+          # TODO: add `linux/arm64 once https://github.com/rasterio/rasterio-wheels/issues/69 is resolved
+          platforms: linux/amd64 # ,linux/arm64
           context: .
           file: dockerfiles/Dockerfile.gunicorn
-          push: true
-          tags: |
-            ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }}
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.meta-gunicorn.outputs.tags }}
+          labels: ${{ steps.meta-gunicorn.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
 
   deploy:
     needs: [tests, publish]
     runs-on: ubuntu-latest
-    if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
+    if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' && github.repository == 'developmentseed/titiler'
 
     defaults:
       run:
         working-directory: deployment/aws
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       # Let's wait a bit to make sure Pypi is up to date
       - name: Sleep for 120 seconds
@@ -209,14 +206,14 @@ jobs:
         shell: bash
 
       - name: Configure AWS credentials
-        uses: aws-actions/configure-aws-credentials@v1
+        uses: aws-actions/configure-aws-credentials@v4
         with:
           aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
           aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
           aws-region: us-east-1
 
       - name: Set up Node.js
-        uses: actions/setup-node@v1
+        uses: actions/setup-node@v4
         with:
           node-version: '14.x'
 
@@ -224,7 +221,7 @@ jobs:
         run: npm install -g
 
       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: '3.x'
 
diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml
index f9b537459..31536e303 100644
--- a/.github/workflows/deploy_mkdocs.yml
+++ b/.github/workflows/deploy_mkdocs.yml
@@ -19,47 +19,19 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout main
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
 
-      - name: Set up Python 3.8
-        uses: actions/setup-python@v2
+      - name: Set up Python 3.11
+        uses: actions/setup-python@v5
         with:
-          python-version: 3.8
+          python-version: 3.11
 
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
           python -m pip install src/titiler/core src/titiler/extensions["cogeo,stac"] src/titiler/mosaic src/titiler/application
-          python -m pip install nbconvert==6.5.3 mkdocs mkdocs-material mkdocs-jupyter pygments pdocs
+          python -m pip install -r requirements/requirements-docs.txt
 
-      - name: update API docs
-        run: |
-          pdocs as_markdown \
-            --output_dir docs/src/api \
-            --exclude_source \
-            --overwrite \
-            titiler.core.dependencies \
-            titiler.core.factory \
-            titiler.core.routing \
-            titiler.core.errors \
-            titiler.core.resources.enums \
-            titiler.core.middleware
-
-          pdocs as_markdown \
-            --output_dir docs/src/api \
-            --exclude_source \
-            --overwrite \
-            titiler.extensions.cogeo \
-            titiler.extensions.viewer \
-            titiler.extensions.stac
-
-          pdocs as_markdown \
-            --output_dir docs/src/api \
-            --exclude_source \
-            --overwrite \
-            titiler.mosaic.factory \
-            titiler.mosaic.resources.enums \
-            titiler.mosaic.errors
 
       - name: Deploy docs
         run: mkdocs gh-deploy --force -f docs/mkdocs.yml
diff --git a/.gitignore b/.gitignore
index 95640297c..3cfc7f31b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -107,4 +107,4 @@ ENV/
 
 cdk.out/
 deployment/k8s/titiler/values-test.yaml
-docs/src/api/
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 63ed17680..e1601ada2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -23,7 +23,7 @@ repos:
         args: ["--fix"]
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.991
+    rev: v1.3.0
     hooks:
       - id: mypy
         language_version: python
@@ -31,3 +31,4 @@ repos:
         additional_dependencies:
         - types-simplejson
         - types-attrs
+        - pydantic~=2.0
diff --git a/CHANGES.md b/CHANGES.md
index 2481fa831..e6afa4a81 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,529 @@
 # Release Notes
 
+## 0.19.0 (TBD)
+
+### Misc
+
+* Remove default `WebMercatorQuad` tile matrix set in `/tiles`, `/tilesjson.json`, `/map` and `/WMTSCapabilities.xml` endpoints **breaking change**
+
+    ```
+    # Before
+    /tiles/{z}/{x}/{y}
+    /tilejson.json
+    /map
+    /WMTSCapabilities.xml
+
+    # Now
+    /tiles/WebMercatorQuad/{z}/{x}/{y}
+    /WebMercatorQuad/tilejson.json
+    /WebMercatorQuad/map
+    /WebMercatorQuad/WMTSCapabilities.xml
+    ```
+
+* Use `@attrs.define` instead of dataclass for factories **breaking change**
+* Use `@attrs.define` instead of dataclass for factory extensions **breaking change**
+* Handle `numpy` types in JSON/GeoJSON response
+* In the `map.html` template, use the tilejson's `minzoom` and `maxzoom` to populate `minNativeZoom` and `maxNativeZoom` parameters in leaflet `tileLayer` instead of `minZoom` and `maxZoom`
+
+### titiler.core
+
+* Update `rio-tiler` dependency to `>=7.0,<8.0`
+
+* Update `geojson-pydantic` dependency to `>=1.1.2,<2.0` which better handle antimeridian crossing dataset
+
+* handle `antimeridian` crossing bounds in `/info.geojson` endpoints (returning MultiPolygon instead of Polygon)
+
+* Improve XSS security for HTML templates (author @jcary741, https://github.com/developmentseed/titiler/pull/953)
+
+* Remove all default values to the dependencies **breaking change**
+
+    * `DatasetParams.unscale`: `False` -> `None` (default to `False` in rio-tiler)
+    * `DatasetParams.resampling_method`: `nearest` -> `None` (default to `nearest` in rio-tiler)
+    * `DatasetParams.reproject_method`: `nearest` -> `None` (default to `nearest` in rio-tiler)
+    * `ImageRenderingParams.add_mask`: `True` -> `None` (default to `True` in rio-tiler)
+    * `StatisticsParams.categorical`: `False` -> `None` (default to `False` in rio-tiler)
+
+* Add `as_dict(exclude_none=True/False)` method to the `DefaultDependency` class.
+
+    ```python
+    from typing import Optional
+    from titiler.core.dependencies import DefaultDependency
+    from dataclasses import dataclass
+
+    @dataclass
+    class Deps(DefaultDependency):
+        value: Optional[int] = None
+
+    print({**Deps().__dict__.items()})
+    >> {'value': None}
+
+    Deps().as_dict()  # `exclude_none` defaults to True
+    >> {}
+
+    Deps(value=1).as_dict()
+    >> {'value': 1}
+    ```
+
+* Fix Hillshade algorithm (bad `azimuth` angle)
+
+* Set default `azimuth` and `altitude` angles to 45º for the Hillshade algorithm **breaking change**
+
+* Use `.as_dict()` method when passing option to rio-tiler Reader's methods to avoid parameter conflicts when using custom Readers.
+
+* Rename `BaseTilerFactory` to `BaseFactory` **breaking change**
+
+* Remove useless attribute in `BaseFactory` (and moved them to `TilerFactory`) **breaking change**
+
+* Add `crs` option to `/bounds` endpoints to enable geographic_crs selection by the user
+
+* `/bounds` endpoints now return a `crs: str` attribute in the response
+
+* update `wmts.xml` template to support multiple layers
+
+* re-order endpoints parameters
+
+* avoid `lat/lon` overflow in `map` viewer
+
+* add OGC Tiles `/tiles` and `/tiles/{tileMatrixSet}` endpoints
+
+### titiler.mosaic
+
+* Rename `reader` attribute to `backend` in `MosaicTilerFactory`  **breaking change**
+
+* Add `crs` option to `/bounds` endpoints to enable geographic_crs selection by the user
+
+* `/bounds` endpoints now return a `crs: str` attribute in the response
+
+* Update `cogeo-mosaic` dependency to `>=8.0,<9.0`
+
+* re-order endpoints parameters
+
+* add OGC Tiles `/tiles` and `/tiles/{tileMatrixSet}` endpoints
+
+### titiler.extensions
+
+* Encode URL for cog_viewer and stac_viewer (author @guillemc23, https://github.com/developmentseed/titiler/pull/961)
+
+* Add links for render parameters and `/map` link to **viewer** dashboard (author @hrodmn, https://github.com/developmentseed/titiler/pull/987)
+
+* Update viewers to use `/info.geojson` endpoint instead of `/info`
+
+## 0.18.10 (2024-10-17)
+
+### titiler.application
+
+* update `starlette-cramjam` dependency and set compression-level default to `6`
+
+## 0.18.9 (2024-09-23)
+
+* fix release 0.18.8
+
+## 0.18.8 (2024-09-23)
+
+### titiler.extensions
+
+* Add links for render parameters and /map link to viewer dashboard (author @hrodmn, https://github.com/developmentseed/titiler/pull/987)
+
+## 0.18.7 (2024-09-19)
+
+* fix Hillshade algorithm (bad `azimuth` angle) (https://github.com/developmentseed/titiler/pull/985) [Backported]
+* Encode URL for cog_viewer and stac_viewer (author @guillemc23, https://github.com/developmentseed/titiler/pull/961) [Backported]
+* Improve XSS security for HTML templates (author @jcary741, https://github.com/developmentseed/titiler/pull/953) [Backported]
+
+## 0.18.6 (2024-08-27)
+
+* Switch back to `fastapi` instead of `fastapi-slim` and use `>=0.109.0` version
+
+## 0.18.5 (2024-07-03)
+
+* Set version requirement for FastAPI to `>=0.111.0`
+
+## 0.18.4 (2024-06-26)
+
+* fix Tiles URL encoding for WMTSCapabilities XML document
+
+## 0.18.3 (2024-05-20)
+
+* fix `WMTSCapabilities.xml` response for ArcMap compatibility
+    * replace `Cloud Optimized GeoTIFF` with dataset URL or `TiTiler` for the *ows:ServiceIdentification* **title**
+    * replace `cogeo` with `Dataset` for the `layer` *ows:Identifier*
+
+## 0.18.2 (2024-05-07)
+
+* move to `fastapi-slim` to avoid unwanted dependencies (author @n8sty, https://github.com/developmentseed/titiler/pull/815)
+
+## 0.18.1 (2024-04-12)
+
+### titiler.core
+
+* fix `TerrainRGB` algorithm name (author @JinIgarashi, https://github.com/developmentseed/titiler/pull/804)
+* add more tests for `RescalingParams` and `HistogramParams` dependencies
+* make sure to return *empty* content for `204` Error code
+
+## 0.18.0 (2024-03-22)
+
+### titiler.core
+
+* Add `ColorMapFactory` to create colorMap metadata endpoints (https://github.com/developmentseed/titiler/pull/796)
+* **Deprecation** remove default `WebMercatorQuad` tile matrix set in `/tiles`, `/tilesjson.json`, `/map` and `/WMTSCapabilities.xml` endpoints (https://github.com/developmentseed/titiler/pull/802)
+
+    ```
+    # Before
+    /tiles/{z}/{x}/{y}
+    /tilejson.json
+    /map
+    /WMTSCapabilities.xml
+
+    # Now
+    /tiles/WebMercatorQuad/{z}/{x}/{y}
+    /WebMercatorQuad/tilejson.json
+    /WebMercatorQuad/map
+    /WebMercatorQuad/WMTSCapabilities.xml
+    ```
+
+* **Deprecation** `default_tms` attribute in `BaseTilerFactory` (because `tileMatrixSetId` is now required in endpoints).
+
+### titiler.mosaic
+
+* **Deprecation** remove default `WebMercatorQuad` tile matrix set in `/tiles`, `/tilesjson.json`, `/map` and `/WMTSCapabilities.xml` endpoints (https://github.com/developmentseed/titiler/pull/802)
+
+    ```
+    # Before
+    /tiles/{z}/{x}/{y}
+    /tilejson.json
+    /map
+    /WMTSCapabilities.xml
+
+    # Now
+    /tiles/WebMercatorQuad/{z}/{x}/{y}
+    /WebMercatorQuad/tilejson.json
+    /WebMercatorQuad/map
+    /WebMercatorQuad/WMTSCapabilities.xml
+    ```
+
+* **Deprecation** `default_tms` attribute in `MosaicTilerFactory` (because `tileMatrixSetId` is now required in endpoints).
+
+### Misc
+
+* add `request` as first argument in `TemplateResponse` to adapt with latest starlette version
+
+## 0.17.3 (2024-03-21)
+
+### titiler.application
+
+* Add `extra="ignore"` option `ApiSettings` to fix pydantic issue when using `.env` file (author @imanshafiei540, https://github.com/developmentseed/titiler/pull/800)
+
+## 0.17.2 (2024-03-15)
+
+### titiler.core
+
+* fix OpenAPI metadata for algorithm (author @JinIgarashi, https://github.com/developmentseed/titiler/pull/797)
+
+## 0.17.1 (2024-03-13)
+
+* add python 3.12 support
+
+### titiler.core
+
+* Add `use_epsg` parameter to WMTS endpoint to resolve ArcMAP issues and fix XML formating (author @gadomski, https://github.com/developmentseed/titiler/pull/782)
+* Add more OpenAPI metadata for algorithm (author @JinIgarashi, https://github.com/developmentseed/titiler/pull/783)
+
+### titiler.application
+
+* fix invalid url parsing in HTML responses
+
+## 0.17.0 (2024-01-17)
+
+### titiler.core
+
+* update `rio-tiler` version to `>6.3.0`
+* use new `align_bounds_with_dataset=True` rio-tiler option in GeoJSON statistics methods for more precise calculation
+
+## 0.16.2 (2024-01-17)
+
+### titiler.core
+
+* fix leafletjs template maxZoom to great than 18 for `/map` endpoint (author @Firefishy, https://github.com/developmentseed/titiler/pull/749)
+
+## 0.16.1 (2024-01-08)
+
+### titiler.core
+
+* use morecantile `TileMatrixSet.cellSize` property instead of deprecated/private `TileMatrixSet._resolution` method
+
+### titiler.mosaic
+
+* use morecantile `TileMatrixSet.cellSize` property instead of deprecated/private `TileMatrixSet._resolution` method
+
+## 0.16.0 (2024-01-08)
+
+### titiler.core
+
+* update FastAPI version lower limit to `>=0.107.0`
+* fix template loading for starlette >= 0.28 by using `jinja2.Environment` argument (author @jasongi, https://github.com/developmentseed/titiler/pull/744)
+
+### titiler.extensions
+
+* fix template loading for starlette >= 0.28 by using `jinja2.Environment` argument (author @jasongi, https://github.com/developmentseed/titiler/pull/744)
+
+### titiler.application
+
+* fix template loading for starlette >= 0.28 by using `jinja2.Environment` argument (author @jasongi, https://github.com/developmentseed/titiler/pull/744)
+
+## 0.15.8 (2024-01-08)
+
+### titiler.core
+
+* use morecantile `TileMatrixSet.cellSize` property instead of deprecated/private `TileMatrixSet._resolution` method [backported from 0.16.1]
+
+### titiler.mosaic
+
+* use morecantile `TileMatrixSet.cellSize` property instead of deprecated/private `TileMatrixSet._resolution` method [backported from 0.16.1]
+
+## 0.15.7 (2024-01-08)
+
+### titiler.core
+
+* update FastAPI version upper limit to `<0.107.0` to avoid starlette breaking change (`0.28`)
+
+### titiler.application
+
+* add simple *auth* (optional) based on `global_access_token` string, set with `TITILER_API_GLOBAL_ACCESS_TOKEN` environment variable (author @DeflateAwning, https://github.com/developmentseed/titiler/pull/735)
+
+## 0.15.6 (2023-11-16)
+
+### titiler.core
+
+* in `/map` HTML response, add Lat/Lon buffer to AOI to avoid creating wrong AOI (when data covers the whole world).
+
+## 0.15.5 (2023-11-09)
+
+### titiler.core
+
+* add `algorithm` options for `/statistics` endpoints
+
+* switch from `BaseReader.statistics()` method to a combination of `BaseReader.preview()` and `ImageData.statistics()` methods to get the statistics
+
+## 0.15.4 (2023-11-06)
+
+### titiler.core
+
+* update `rio-tiler` requirement to `>=6.2.5,<7.0`
+
+* allow `bidx` option in `titiler.core.dependencies.AssetsBidxExprParams` and `titiler.core.dependencies.AssetsBidxParams`
+
+    ```python
+    # merge band 1 form asset1 and asset2
+    # before
+    httpx.get(
+        "/stac/preview",
+        params=(
+            ("url", "stac.json"),
+            ("assets", "asset1"),
+            ("assets", "asset2"),
+            ("asset_bidx", "asset1|1"),
+            ("asset_bidx", "asset2|1"),
+        )
+    )
+
+    # now
+    httpx.get(
+        "/stac/preview",
+        params=(
+            ("url", "stac.json"),
+            ("assets", "asset1"),
+            ("assets", "asset2"),
+            ("bidx", 1),
+        )
+    )
+    ```
+
+* fix openapi examples
+
+## 0.15.3 (2023-11-02)
+
+* add `dst_crs` options in `/statistics [POST]` and `/feature [POST]` endpoints
+
+## 0.15.2 (2023-10-23)
+
+### titiler.core
+
+* add `dependencies.TileParams` dependency with `buffer` and `padding` options
+* add `tile_dependency` attribute in `TilerFactory` class (defaults to `TileParams`)
+* add `reproject` (alias to `reproject_method`) option in `DatasetParams` dependency
+
+### titiler.mosaic
+
+*  Change `HTTP_404_NOT_FOUND` to `HTTP_204_NO_CONTENT` when no asset is found or tile is empty (author @simouel, https://github.com/developmentseed/titiler/pull/713)
+* add `tile_dependency` attribute in `MosaicTilerFactory` class (defaults to `TileParams`)
+
+### cdk application
+
+* Support non-root paths in AWS API Gateway Lambda handler (author @DanSchoppe, https://github.com/developmentseed/titiler/pull/716)
+
+## 0.15.1 (2023-10-17)
+
+* Allow a default `color_formula` parameter to be set via a dependency (author @samn, https://github.com/developmentseed/titiler/pull/707)
+* add `titiler.core.dependencies.create_colormap_dependency` to create ColorMapParams dependency from `rio_tiler.colormap.ColorMaps` object
+* add `py.typed` files in titiler submodules (https://peps.python.org/pep-0561)
+
+## 0.15.0 (2023-09-28)
+
+### titiler.core
+
+- added `PartFeatureParams` dependency
+
+**breaking changes**
+
+- `max_size` is now set to `None` for `/statistics [POST]`, `/bbox` and `/feature` endpoints, meaning the tiler will create image from the highest resolution.
+
+- renamed `titiler.core.dependencies.ImageParams` to `PreviewParams`
+
+- split TileFactory `img_dependency` attribute in two:
+  - `img_preview_dependency`: used in `/preview` and `/statistics [GET]`, default to `PreviewParams` (with `max_size=1024`)
+
+  - `img_part_dependency`: used in `/bbox`, `/feature` and `/statistics [POST]`, default to `PartFeatureParams` (with `max_size=None`)
+
+- renamed `/crop` endpoints to `/bbox/...` or `/feature/...`
+  - `/crop/{minx},{miny},{maxx},{maxy}.{format}` -> `/bbox/{minx},{miny},{maxx},{maxy}.{format}`
+
+  - `/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` -> `/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}`
+
+  - `/crop [POST]` -> `/feature [POST]`
+
+  - `/crop.{format} [POST]` -> `/feature.{format} [POST]`
+
+  - `/crop/{width}x{height}.{format}  [POST]` -> `/feature/{width}x{height}.{format} [POST]`
+
+- update `rio-tiler` requirement to `>=6.2.1`
+
+- Take coverage weights in account when generating statistics from GeoJSON features
+
+## 0.14.1 (2023-09-14)
+
+### titiler.extension
+
+* add `GetFeatureInfo` capability in `wmsExtension` (author @benjaminleighton, https://github.com/developmentseed/titiler/pull/698)
+
+## 0.14.0 (2023-08-30)
+
+### titiler.core
+
+* replace `-` by `_` in query parameters **breaking change**
+  - `coord-crs` -> `coord_crs`
+  - `dst-crs` -> `dst_crs`
+
+* replace `buffer` and `color_formula` endpoint parameters by external dependencies (`BufferParams` and `ColorFormulaParams`)
+
+* add `titiler.core.utils.render_image` which allow non-binary alpha band created with custom colormap. `render_image` replace `ImageData.render` method.
+
+    ```python
+    # before
+    if cmap := colormap or dst_colormap:
+        image = image.apply_colormap(cmap)
+
+    if not format:
+        format = ImageType.jpeg if image.mask.all() else ImageType.png
+
+    content = image.render(
+        img_format=format.driver,
+        **format.profile,
+        **render_params,
+    )
+
+    # now
+    # render_image will:
+    # - apply the colormap
+    # - choose the right output format if `None`
+    # - create the binary data
+    content, media_type = render_image(
+        image,
+        output_format=format,
+        colormap=colormap or dst_colormap,
+        **render_params,
+    )
+    ```
+
+### titiler.extension
+
+* rename `geom-densify-pts` to `geometry_densify` **breaking change**
+* rename `geom-precision` to `geometry_precision` **breaking change**
+
+## 0.13.3 (2023-08-27)
+
+* fix Factories `url_for` method and avoid changing `Request.path_params` object
+
+## 0.13.2 (2023-08-24)
+
+### titiler.extensions
+
+* replace mapbox-gl by maplibre
+* replace Stamen by OpenStreetMap tiles
+* simplify band selection handling (author @tayden, https://github.com/developmentseed/titiler/pull/688)
+
+## 0.13.1 (2023-08-21)
+
+### titiler.core
+
+* fix `LowerCaseQueryStringMiddleware` unexpectedly truncating query parameters (authors @jthetzel and @jackharrhy, @https://github.com/developmentseed/titiler/pull/677)
+
+## titiler.application
+
+* add `cors_allow_methods` in `ApiSettings` to control the CORS allowed methods (author @ubi15, https://github.com/developmentseed/titiler/pull/684)
+
+## 0.13.0 (2023-07-27)
+
+* update core requirements to libraries using pydantic **~=2.0**
+
+### titiler.core
+
+* update requirements:
+  * fastapi `>=0.95.1` --> `>=0.100.0`
+  * pydantic `~=1.0` --> `~=2.0`
+  * rio-tiler `>=5.0,<6.0` --> `>=6.0,<7.0`
+  * morecantile`>=4.3,<5.0` --> `>=5.0,<6.0`
+  * geojson-pydantic `>=0.4,<0.7` --> `>=1.0,<2.0`
+  * typing_extensions `>=4.6.1`
+
+### titiler.extension
+
+* update requirements:
+  * rio-cogeo `>=4.0,<5.0"` --> `>=5.0,<6.0"`
+
+### titiler.mosaic
+
+* update requirements:
+  * cogeo-mosaic `>=6.0,<7.0` --> `>=7.0,<8.0`
+
+### titiler.application
+
+* use `/api` and `/api.html` for documentation (instead of `/openapi.json` and `/docs`)
+* update landing page
+
+## 0.12.0 (2023-07-17)
+
+* use `Annotated` Type for Query/Path parameters
+* replace variable `TileMatrixSetId` by `tileMatrixSetId`
+
+### titiler.core
+
+* update FastAPI dependency to `>=0.95.1`
+* set `pydantic` dependency to `~=1.0`
+* update `rio-tiler` dependency to `>=5.0,<6.0`
+* update TMS endpoints to match OGC Tiles specification
+
+### titiler.extensions
+
+* use TiTiler's custom JSONResponse for the `/validate` endpoint to avoid issue when COG has `NaN` nodata value
+* update `rio-cogeo` dependency to `>=4.0,<5.0`
+* update `rio-stac` requirement to `>=0.8,<0.9` and add `geom-densify-pts` and `geom-precision` options
+
+## titiler.mosaic
+
+* update `cogeo-mosaic` dependency to `>=6.0,<7.0`
+* remove `titiler.mosaic.resources.enum.PixelSelectionMethod` and use `rio_tiler.mosaic.methods.PixelSelectionMethod`
+* allow more TileMatrixSet (than only `WebMercatorQuad`)
+
 ## 0.11.7 (2023-05-18)
 
 ### titiler.core
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a7105795c..394a6b4a8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -47,7 +47,7 @@ python -m pytest src/titiler/application --cov=titiler.application --cov-report=
 ```bash
 git clone https://github.com/developmentseed/titiler.git
 cd titiler
-python -m pip install nbconvert mkdocs mkdocs-material mkdocs-jupyter pygments pdocs
+python -m pip install -r requirements/requirements-docs.txt
 ```
 
 Hot-reloading docs:
@@ -62,33 +62,3 @@ Actions deploys automatically for new commits.):
 ```bash
 mkdocs gh-deploy -f docs/mkdocs.yml
 ```
-
-```bash
-pdocs as_markdown \
-   --output_dir docs/src/api \
-   --exclude_source \
-   --overwrite \
-   titiler.core.dependencies \
-   titiler.core.factory \
-   titiler.core.utils \
-   titiler.core.routing \
-   titiler.core.errors \
-   titiler.core.resources.enums \
-   titiler.core.middleware
-
-pdocs as_markdown \
-   --output_dir docs/src/api \
-   --exclude_source \
-   --overwrite \
-   titiler.extensions.cogeo \
-   titiler.extensions.viewer \
-   titiler.extensions.stac
-
-pdocs as_markdown \
-   --output_dir docs/src/api \
-   --exclude_source \
-   --overwrite \
-   titiler.mosaic.factory \
-   titiler.mosaic.resources.enums \
-   titiler.mosaic.errors
-```
diff --git a/README.md b/README.md
index 44f4aed17..c38adc3d9 100644
--- a/README.md
+++ b/README.md
@@ -114,7 +114,7 @@ docker run --name titiler \
 git clone https://github.com/developmentseed/titiler.git
 cd titiler
 
-docker-compose up --build titiler  # or titiler-uvicorn
+docker compose up --build titiler  # or titiler-uvicorn
 ```
 
 Some options can be set via environment variables, see: https://github.com/tiangolo/uvicorn-gunicorn-docker#advanced-usage
diff --git a/deployment/aws/cdk/app.py b/deployment/aws/cdk/app.py
index 235baa35f..3317d8424 100644
--- a/deployment/aws/cdk/app.py
+++ b/deployment/aws/cdk/app.py
@@ -34,7 +34,7 @@ def __init__(
         id: str,
         memory: int = 1024,
         timeout: int = 30,
-        runtime: aws_lambda.Runtime = aws_lambda.Runtime.PYTHON_3_10,
+        runtime: aws_lambda.Runtime = aws_lambda.Runtime.PYTHON_3_11,
         concurrent: Optional[int] = None,
         permissions: Optional[List[iam.PolicyStatement]] = None,
         environment: Optional[Dict] = None,
diff --git a/deployment/aws/cdk/config.py b/deployment/aws/cdk/config.py
index fa016b193..d3fe0f151 100644
--- a/deployment/aws/cdk/config.py
+++ b/deployment/aws/cdk/config.py
@@ -2,17 +2,17 @@
 
 from typing import Dict, List, Optional
 
-import pydantic
+from pydantic_settings import BaseSettings, SettingsConfigDict
 
 
-class StackSettings(pydantic.BaseSettings):
+class StackSettings(BaseSettings):
     """Application settings"""
 
     name: str = "titiler"
     stage: str = "production"
 
-    owner: Optional[str]
-    client: Optional[str]
+    owner: Optional[str] = None
+    client: Optional[str] = None
 
     # Default options are optimized for CloudOptimized GeoTIFF
     # For more information on GDAL env see: https://gdal.org/user/configoptions.html
@@ -70,7 +70,7 @@ class StackSettings(pydantic.BaseSettings):
     # Override the automatic definition of number of workers.
     # Set to the number of CPU cores in the current server multiplied by the environment variable WORKERS_PER_CORE.
     # So, in a server with 2 cores, by default it will be set to 2.
-    web_concurrency: Optional[int]
+    web_concurrency: Optional[int] = None
 
     image_version: str = "latest"
 
@@ -83,10 +83,6 @@ class StackSettings(pydantic.BaseSettings):
 
     # The maximum of concurrent executions you want to reserve for the function.
     # Default: - No specific limit - account limit.
-    max_concurrent: Optional[int]
+    max_concurrent: Optional[int] = None
 
-    class Config:
-        """model config"""
-
-        env_file = ".env"
-        env_prefix = "TITILER_STACK_"
+    model_config = SettingsConfigDict(env_prefix="TITILER_STACK_", env_file=".env")
diff --git a/deployment/aws/lambda/Dockerfile b/deployment/aws/lambda/Dockerfile
index 9e8fc276e..51657f7e8 100644
--- a/deployment/aws/lambda/Dockerfile
+++ b/deployment/aws/lambda/Dockerfile
@@ -1,11 +1,14 @@
-ARG PYTHON_VERSION=3.10
+ARG PYTHON_VERSION=3.11
 
 FROM --platform=linux/amd64 public.ecr.aws/lambda/python:${PYTHON_VERSION}
 
 WORKDIR /tmp
 
-RUN pip install pip -U
-RUN pip install "titiler.application==0.11.7" "mangum>=0.10.0" -t /asset --no-binary pydantic
+# Install system dependencies to compile (numexpr)
+RUN yum install -y gcc-c++
+
+RUN python -m pip install pip -U
+RUN python -m pip install "titiler.application==0.19.0.dev" "mangum>=0.10.0" -t /asset --no-binary pydantic
 
 # Reduce package size and remove useless files
 RUN cd /asset && find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//'); cp $f $n; done;
@@ -14,6 +17,9 @@ RUN cd /asset && find . -type f -a -name '*.py' -print0 | xargs -0 rm -f
 RUN find /asset -type d -a -name 'tests' -print0 | xargs -0 rm -rf
 RUN rm -rdf /asset/numpy/doc/ /asset/boto3* /asset/botocore* /asset/bin /asset/geos_license /asset/Misc
 
+# Remove system dependencies
+RUN yum remove -y gcc-c++
+
 COPY lambda/handler.py /asset/handler.py
 
 CMD ["echo", "hello world"]
diff --git a/deployment/aws/lambda/handler.py b/deployment/aws/lambda/handler.py
index 4e66c7b2e..933807be5 100644
--- a/deployment/aws/lambda/handler.py
+++ b/deployment/aws/lambda/handler.py
@@ -5,8 +5,11 @@
 from mangum import Mangum
 
 from titiler.application.main import app
+from titiler.application.settings import ApiSettings
 
 logging.getLogger("mangum.lifespan").setLevel(logging.ERROR)
 logging.getLogger("mangum.http").setLevel(logging.ERROR)
 
-handler = Mangum(app, lifespan="auto")
+api_settings = ApiSettings()
+
+handler = Mangum(app, api_gateway_base_path=api_settings.root_path, lifespan="auto")
diff --git a/deployment/aws/package-lock.json b/deployment/aws/package-lock.json
index ad5226462..f1aa3a81b 100644
--- a/deployment/aws/package-lock.json
+++ b/deployment/aws/package-lock.json
@@ -9,13 +9,13 @@
       "version": "0.1.0",
       "license": "MIT",
       "dependencies": {
-        "cdk": "2.76.0-alpha.0"
+        "cdk": "2.94.0"
       }
     },
     "node_modules/aws-cdk": {
-      "version": "2.76.0",
-      "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.76.0.tgz",
-      "integrity": "sha512-y6VHtqUpYenn6mGIBFbcGGXIoXfKA3o0eGL/eeD/gUJ9TcPrgMLQM1NxSMb5JVsOk5BPPXzGmvB0gBu40utGqg==",
+      "version": "2.94.0",
+      "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.94.0.tgz",
+      "integrity": "sha512-9bJkzxFDYZDwPDfZi/DSUODn4HFRzuXWPhpFgIIgRykfT18P+iAIJ1AEhaaCmlqrrog5yQgN+2iYd9BwDsiBeg==",
       "bin": {
         "cdk": "bin/cdk"
       },
@@ -27,17 +27,17 @@
       }
     },
     "node_modules/cdk": {
-      "version": "2.76.0-alpha.0",
-      "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.76.0-alpha.0.tgz",
-      "integrity": "sha512-HNfX5c7MU18LxthZRcapqEhG0IFgQeNOhtsTR1QiL/7dhy2TjvK26dYcJ67KIHfzMfm5EUjvOXdP1SPdW+eOOA==",
+      "version": "2.94.0",
+      "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.94.0.tgz",
+      "integrity": "sha512-dMgSTaMtfpPxY2biMSHlNrwrJcq0iJkihvkVuSSQyhlHyLmH0s9fSBzO8zrGFAoEp/cDofg6iDfGzmwrHQ55LA==",
       "dependencies": {
-        "aws-cdk": "2.76.0"
+        "aws-cdk": "2.94.0"
       },
       "bin": {
         "cdk": "bin/cdk"
       },
       "engines": {
-        "node": ">= 8.10.0"
+        "node": ">= 14.15.0"
       }
     },
     "node_modules/fsevents": {
@@ -56,19 +56,19 @@
   },
   "dependencies": {
     "aws-cdk": {
-      "version": "2.76.0",
-      "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.76.0.tgz",
-      "integrity": "sha512-y6VHtqUpYenn6mGIBFbcGGXIoXfKA3o0eGL/eeD/gUJ9TcPrgMLQM1NxSMb5JVsOk5BPPXzGmvB0gBu40utGqg==",
+      "version": "2.94.0",
+      "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.94.0.tgz",
+      "integrity": "sha512-9bJkzxFDYZDwPDfZi/DSUODn4HFRzuXWPhpFgIIgRykfT18P+iAIJ1AEhaaCmlqrrog5yQgN+2iYd9BwDsiBeg==",
       "requires": {
         "fsevents": "2.3.2"
       }
     },
     "cdk": {
-      "version": "2.76.0-alpha.0",
-      "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.76.0-alpha.0.tgz",
-      "integrity": "sha512-HNfX5c7MU18LxthZRcapqEhG0IFgQeNOhtsTR1QiL/7dhy2TjvK26dYcJ67KIHfzMfm5EUjvOXdP1SPdW+eOOA==",
+      "version": "2.94.0",
+      "resolved": "https://registry.npmjs.org/cdk/-/cdk-2.94.0.tgz",
+      "integrity": "sha512-dMgSTaMtfpPxY2biMSHlNrwrJcq0iJkihvkVuSSQyhlHyLmH0s9fSBzO8zrGFAoEp/cDofg6iDfGzmwrHQ55LA==",
       "requires": {
-        "aws-cdk": "2.76.0"
+        "aws-cdk": "2.94.0"
       }
     },
     "fsevents": {
diff --git a/deployment/aws/package.json b/deployment/aws/package.json
index 040bfa6b4..f551ad45a 100644
--- a/deployment/aws/package.json
+++ b/deployment/aws/package.json
@@ -5,7 +5,7 @@
   "license": "MIT",
   "private": true,
   "dependencies": {
-    "cdk": "2.76.0-alpha.0"
+    "cdk": "2.94.0"
   },
   "scripts": {
     "cdk": "cdk"
diff --git a/deployment/aws/requirements-cdk.txt b/deployment/aws/requirements-cdk.txt
index 973fc3433..c134c8726 100644
--- a/deployment/aws/requirements-cdk.txt
+++ b/deployment/aws/requirements-cdk.txt
@@ -1,9 +1,9 @@
 # aws cdk
-aws-cdk-lib==2.76.0
-aws_cdk-aws_apigatewayv2_alpha==2.76.0a0
-aws_cdk-aws_apigatewayv2_integrations_alpha==2.76.0a0
+aws-cdk-lib==2.94.0
+aws_cdk-aws_apigatewayv2_alpha==2.94.0a0
+aws_cdk-aws_apigatewayv2_integrations_alpha==2.94.0a0
 constructs>=10.0.0
 
 # pydantic settings
-pydantic
-python-dotenv
+pydantic~=2.0
+pydantic-settings~=2.0
diff --git a/deployment/azure/README.md b/deployment/azure/README.md
index 7516cde29..b7d395f69 100644
--- a/deployment/azure/README.md
+++ b/deployment/azure/README.md
@@ -1,13 +1,13 @@
 ### Function
 
-TiTiler is built on top of [FastAPI](https://github.com/tiangolo/fastapi), a modern, fast, Python web framework for building APIs. As for AWS Lambda we can make our FastAPI application work on Azure Function by wrapping it within the [Azure Function Python worker](https://github.com/Azure/azure-functions-python-worker).
+TiTiler is built on top of [FastAPI](https://github.com/tiangolo/fastapi), a modern, fast, Python web framework for building APIs. We can make our FastAPI application work as an Azure Function by wrapping it within the [Azure Function Python worker](https://github.com/Azure/azure-functions-python-worker).
 
 If you are not familiar with **Azure functions** we recommend checking https://docs.microsoft.com/en-us/azure/azure-functions/ first.
 
 Minimal TiTiler Azure function code:
 ```python
 import azure.functions as func
-from titiler.application.routers import cog, mosaic, stac, tms
+from titiler.application.main import cog, mosaic, stac, tms
 from fastapi import FastAPI
 
 
@@ -20,14 +20,12 @@ app.include_router(mosaic.router, prefix="/mosaicjson", tags=["MosaicJSON"])
 app.include_router(tms.router, tags=["TileMatrixSets"])
 
 
-def main(
+async def main(
     req: func.HttpRequest, context: func.Context,
 ) -> func.HttpResponse:
-    return func.AsgiMiddleware(app).handle(req, context)
+    return await func.AsgiMiddleware(app).handle_async(req, context)
 ```
 
-Note: there is a `bug` in `azure.functions.AsgiMiddleware` which prevent using `starlette.BaseHTTPMiddleware` middlewares (see: https://github.com/Azure/azure-functions-python-worker/issues/903).
-
 #### Requirements
 - Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
 - Azure Function Tool: https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local
@@ -42,8 +40,8 @@ $ cd titiler/deployment/azure
 
 $ az login
 $ az group create --name AzureFunctionsTiTiler-rg --location eastus
-$ az storage account create --name TiTilerStorage --sku Standard_LRS
-$ az functionapp create --consumption-plan-location eastus --runtime python --runtime-version 3.8 --functions-version 3 --name titiler --os-type linux
+$ az storage account create --name titilerstorage --sku Standard_LRS -g AzureFunctionsTiTiler-rg
+$ az functionapp create --consumption-plan-location eastus --runtime python --runtime-version 3.8 --functions-version 3 --name titiler --os-type linux -g AzureFunctionsTiTiler-rg -s titilerstorage
 $ func azure functionapp publish titiler
 ```
 
diff --git a/deployment/azure/app/__init__.py b/deployment/azure/app/__init__.py
index 5aab2727d..33e1d2939 100644
--- a/deployment/azure/app/__init__.py
+++ b/deployment/azure/app/__init__.py
@@ -8,17 +8,15 @@
 from starlette_cramjam.middleware import CompressionMiddleware
 
 from titiler.application import __version__ as titiler_version
-from titiler.application.custom import templates
-from titiler.application.routers import cog, mosaic, stac, tms
+from titiler.application.main import cog, mosaic, stac, templates, tms
 from titiler.application.settings import ApiSettings
 from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers
-
-# from titiler.core.middleware import (
-#     CacheControlMiddleware,
-#     LoggerMiddleware,
-#     LowerCaseQueryStringMiddleware,
-#     TotalTimeMiddleware,
-# )
+from titiler.core.middleware import (
+    CacheControlMiddleware,
+    LoggerMiddleware,
+    LowerCaseQueryStringMiddleware,
+    TotalTimeMiddleware,
+)
 from titiler.mosaic.errors import MOSAIC_STATUS_CODES
 
 api_settings = ApiSettings()
@@ -68,19 +66,18 @@
     },
 )
 
-# see https://github.com/encode/starlette/issues/1320
-# app.add_middleware(
-#     CacheControlMiddleware,
-#     cachecontrol=api_settings.cachecontrol,
-#     exclude_path={r"/healthz"},
-# )
+app.add_middleware(
+    CacheControlMiddleware,
+    cachecontrol=api_settings.cachecontrol,
+    exclude_path={r"/healthz"},
+)
 
-# if api_settings.debug:
-#     app.add_middleware(LoggerMiddleware, headers=True, querystrings=True)
-#     app.add_middleware(TotalTimeMiddleware)
+if api_settings.debug:
+    app.add_middleware(LoggerMiddleware, headers=True, querystrings=True)
+    app.add_middleware(TotalTimeMiddleware)
 
-# if api_settings.lower_case_query_parameters:
-#     app.add_middleware(LowerCaseQueryStringMiddleware)
+if api_settings.lower_case_query_parameters:
+    app.add_middleware(LowerCaseQueryStringMiddleware)
 
 
 @app.get("/healthz", description="Health Check", tags=["Health Check"])
@@ -99,9 +96,9 @@ def landing(request: Request):
     )
 
 
-def main(
+async def main(
     req: func.HttpRequest,
     context: func.Context,
 ) -> func.HttpResponse:
     """Run App in AsgiMiddleware."""
-    return func.AsgiMiddleware(app).handle(req, context)
+    return await func.AsgiMiddleware(app).handle_async(req, context)
diff --git a/deployment/azure/host.json b/deployment/azure/host.json
index 8e588272b..6e86c559b 100644
--- a/deployment/azure/host.json
+++ b/deployment/azure/host.json
@@ -10,7 +10,7 @@
   },
   "extensionBundle": {
     "id": "Microsoft.Azure.Functions.ExtensionBundle",
-    "version": "[2.*, 3.0.0)"
+    "version": "[3.*, 4.0.0)"
   },
   "extensions": {
     "http": {
diff --git a/deployment/k8s/charts/Chart.yaml b/deployment/k8s/charts/Chart.yaml
index febe06e3e..52741facc 100644
--- a/deployment/k8s/charts/Chart.yaml
+++ b/deployment/k8s/charts/Chart.yaml
@@ -1,8 +1,8 @@
 apiVersion: v1
-appVersion: 0.11.7
+appVersion: 0.19.0.dev
 description: A dynamic Web Map tile server
 name: titiler
-version: 1.1.0
+version: 1.1.3
 icon: https://raw.githubusercontent.com/developmentseed/titiler/main/docs/logos/TiTiler_logo_small.png
 maintainers:
   - name: emmanuelmathot  # Emmanuel Mathot
diff --git a/deployment/k8s/charts/templates/deployment.yaml b/deployment/k8s/charts/templates/deployment.yaml
index 9858c13a6..fca1a0b86 100644
--- a/deployment/k8s/charts/templates/deployment.yaml
+++ b/deployment/k8s/charts/templates/deployment.yaml
@@ -14,10 +14,14 @@ spec:
       labels:
         {{- include "titiler.selectorLabels" . | nindent 8 }}
     spec:
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
       containers:
         - name: {{ .Chart.Name }}
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
           imagePullPolicy: {{ .Values.image.pullPolicy }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
           env:
           {{- range $key, $val := .Values.env }}
             - name: {{ $key }}
@@ -49,10 +53,31 @@ spec:
             - mountPath: /config
               name: config
               readOnly: true
+          {{- range .Values.extraHostPathMounts }}
+            - name: {{ .name }}
+              mountPath: {{ .mountPath }}
+              readOnly: {{ .readOnly }}
+            {{- if .mountPropagation }}
+              mountPropagation: {{ .mountPropagation }}
+            {{- end }}
+          {{- end }}
+      terminationGracePeriodSeconds: {{ .Values.env.terminationGracePeriodSeconds }}
       volumes:
         - name: config
           configMap:
             name: {{ include "titiler.fullname" . }}-configmap
+      {{- range .Values.extraHostPathMounts }}
+        - name: {{ .name }}
+          hostPath:
+            path: {{ .hostPath }}
+            type: Directory
+      {{- end }}
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- range . }}
+      - name: {{ .name }}
+        {{- end }}
+      {{- end }}
       {{- with .Values.serviceAccountName }}
       serviceAccountName: {{ . | quote }}
       {{- end }}
diff --git a/deployment/k8s/charts/values-test.yaml b/deployment/k8s/charts/values-test.yaml
index 47bd601e4..b8865ba9c 100644
--- a/deployment/k8s/charts/values-test.yaml
+++ b/deployment/k8s/charts/values-test.yaml
@@ -14,6 +14,17 @@ ingress:
      hosts:
        - titiler.charter.uat.esaportal.eu
 
+terminationGracePeriodSeconds: 30
+
+extraHostPathMounts: []
+  # - name: map-sources
+  #   mountPath: /map-sources/
+  #   hostPath: /home/ubuntu/map-sources
+  #   readOnly: false
+  #   mountPropagation: HostToContainer # OPTIONAL
+
+imagePullSecrets: []
+
 env:
   PORT: 80
   CPL_TMPDIR: /tmp
diff --git a/deployment/k8s/charts/values.yaml b/deployment/k8s/charts/values.yaml
index b10995183..ac3a54f6b 100644
--- a/deployment/k8s/charts/values.yaml
+++ b/deployment/k8s/charts/values.yaml
@@ -9,6 +9,8 @@ image:
 nameOverride: ""
 fullnameOverride: ""
 
+terminationGracePeriodSeconds: 30
+
 service:
   type: ClusterIP
   port: 80
@@ -26,6 +28,15 @@ ingress:
   #    hosts:
   #      - titiler.local
 
+extraHostPathMounts: []
+  # - name: map-sources
+  #   mountPath: /map-sources/
+  #   hostPath: /home/ubuntu/map-sources
+  #   readOnly: false
+  #   mountPropagation: HostToContainer # OPTIONAL
+
+imagePullSecrets: []
+
 env:
   PORT: 80
   CPL_TMPDIR: /tmp
@@ -54,3 +65,17 @@ nodeSelector: {}
 tolerations: []
 
 affinity: {}
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #     - ALL
+  # readOnlyRootFilesystem: true
+  # allowPrivilegeEscalation: false
+  # runAsNonRoot: true
+  # runAsUser: 1001
+
+podSecurityContext: {}
+  # fsGroup: 1001
+  # runAsNonRoot: true
+  # runAsUser: 1001
diff --git a/docker-compose.yml b/docker-compose.yml
index 9e3207e76..ba9b9513a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,6 +2,7 @@ version: '3'
 
 services:
   titiler:
+    # TODO: remove once https://github.com/rasterio/rasterio-wheels/issues/69 is resolved
     # See https://github.com/developmentseed/titiler/discussions/387
     platform: linux/amd64
     build:
@@ -47,6 +48,7 @@ services:
       # - RIO_TILER_MAX_THREADS=
 
   titiler-uvicorn:
+    # TODO: remove once https://github.com/rasterio/rasterio-wheels/issues/69 is resolved
     # See https://github.com/developmentseed/titiler/discussions/387
     platform: linux/amd64
     build:
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 1d3aa53f7..9d91f4104 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -10,6 +10,22 @@ edit_uri: "blob/main/docs/src/"
 site_url: "https://developmentseed.org/titiler/"
 
 extra:
+  analytics:
+    provider: plausible
+    domain: developmentseed.org/titiler
+
+    feedback:
+      title: Was this page helpful?
+      ratings:
+        - icon: material/emoticon-happy-outline
+          name: This page was helpful
+          data: good
+          note: Thanks for your feedback!
+
+        - icon: material/emoticon-sad-outline
+          name: This page could be improved
+          data: bad
+          note: Thanks for your feedback!
   social:
     - icon: "fontawesome/brands/github"
       link: "https://github.com/developmentseed"
@@ -28,7 +44,7 @@ nav:
     - Output data format: "output_format.md"
 
   - Advanced User Guide:
-      - Tiler Factories: "advanced/tiler_factories.md"
+      - Endpoints Factories: "advanced/endpoints_factories.md"
       - Dependencies: "advanced/dependencies.md"
       - Customization: "advanced/customization.md"
       - Performance Tuning: "advanced/performance_tuning.md"
@@ -37,6 +53,14 @@ nav:
       - Rendering: "advanced/rendering.md"
       # - APIRoute and environment variables: "advanced/APIRoute_and_environment_variables.md"
 
+  - Endpoints documentation:
+    - /cog: "endpoints/cog.md"
+    - /stac: "endpoints/stac.md"
+    - /mosaicjson: "endpoints/mosaic.md"
+    - /tileMatrixSets: "endpoints/tms.md"
+    - /algorithms: "endpoints/algorithms.md"
+    - /colormaps: "endpoints/colormaps.md"
+
   - Examples:
     - Create dynamic tilers with TiTiler:
       - Minimal COG Tiler: "examples/code/mini_cog_tiler.md"
@@ -68,22 +92,23 @@ nav:
       - factory: api/titiler/core/factory.md
       - routing: api/titiler/core/routing.md
       - errors: api/titiler/core/errors.md
-      - enums: api/titiler/core/resources/enums.md
       - middleware: api/titiler/core/middleware.md
+      - resources:
+        - enums: api/titiler/core/resources/enums.md
+        - responses: api/titiler/core/resources/responses.md
+      - models:
+        - OGC: api/titiler/core/models/OGC.md
+        - Mapbox/MapLibre: api/titiler/core/models/mapbox.md
+        - responses: api/titiler/core/models/responses.md
     - titiler.extensions:
       - cogeo: api/titiler/extensions/cogeo.md
       - stac: api/titiler/extensions/stac.md
       - viewer: api/titiler/extensions/viewer.md
     - titiler.mosaic:
       - factory: api/titiler/mosaic/factory.md
-      - enums: api/titiler/mosaic/resources/enums.md
       - errors: api/titiler/mosaic/errors.md
-
-  - titiler.application:
-    - /cog: "endpoints/cog.md"
-    - /stac: "endpoints/stac.md"
-    - /mosaicjson: "endpoints/mosaic.md"
-    - /tileMatrixSets: "endpoints/tms.md"
+      - models:
+        - responses: api/titiler/mosaic/models/responses.md
 
   - Deployment:
     - Amazon Web Services:
@@ -97,11 +122,40 @@ nav:
   - External links: "external_links.md"
   - Development - Contributing: "contributing.md"
   - Release Notes: "release-notes.md"
+  - Performance Benchmarks: benchmark.html
 
 plugins:
   - search
+  - social
   - mkdocs-jupyter:
-      include_source: True
+      include_source: true
+      ignore: ["**/.ipynb_checkpoints/*.ipynb"]
+  - mkdocstrings:
+      enable_inventory: true
+      handlers:
+        python:
+          paths: [src]
+          options:
+            filters:
+            - "!^__post_init__"
+            docstring_section_style: list
+            docstring_style: google
+            line_length: 100
+            separate_signature: true
+            show_root_heading: true
+            show_signature_annotations: true
+            show_source: false
+            show_symbol_type_toc: true
+            signature_crossrefs: true
+            extensions:
+              - griffe_inherited_docstrings
+          import:
+            - https://docs.python.org/3/objects.inv
+            - https://numpy.org/doc/stable/objects.inv
+            - https://rasterio.readthedocs.io/en/stable/objects.inv
+            - https://docs.pydantic.dev/latest/objects.inv
+            - https://fastapi.tiangolo.com/objects.inv
+            - https://cogeotiff.github.io/rio-tiler/objects.inv
 
 theme:
   name: material
@@ -111,6 +165,15 @@ theme:
   custom_dir: 'src/overrides'
   favicon: img/favicon.png
 
+  features:
+    - content.code.annotate
+    - content.code.copy
+    - navigation.indexes
+    - navigation.instant
+    - navigation.tracking
+    - search.suggest
+    - search.share
+
 # https://github.com/kylebarron/cogeo-mosaic/blob/mkdocs/mkdocs.yml#L50-L75
 markdown_extensions:
   - admonition
@@ -124,7 +187,9 @@ markdown_extensions:
   - pymdownx.caret:
       insert: false
   - pymdownx.details
-  - pymdownx.emoji
+  - pymdownx.emoji:
+      emoji_index: !!python/name:material.extensions.emoji.twemoji
+      emoji_generator: !!python/name:material.extensions.emoji.to_svg
   - pymdownx.escapeall:
       hardbreak: true
       nbsp: true
diff --git a/docs/src/advanced/Extensions.md b/docs/src/advanced/Extensions.md
index 9af9a7c2f..531d612b0 100644
--- a/docs/src/advanced/Extensions.md
+++ b/docs/src/advanced/Extensions.md
@@ -89,6 +89,7 @@ See [titiler.application](../application) for a full example.
 from dataclasses import dataclass, field
 from typing import Tuple, List, Optional
 
+import rasterio
 from starlette.responses import Response
 from fastapi import Depends, FastAPI, Query
 from titiler.core.factory import BaseTilerFactory, FactoryExtension, TilerFactory
@@ -140,11 +141,11 @@ class thumbnailExtension(FactoryExtension):
             env=Depends(factory.environment_dependency),
         ):
             with rasterio.Env(**env):
-                with self.reader(src_path, **reader_params) as src_dst:
-                    im = src.preview(
+                with factory.reader(src_path, **reader_params.as_dict()) as src:
+                    image = src.preview(
                         max_size=self.max_size,
-                        **layer_params,
-                        **dataset_params,
+                        **layer_params.as_dict(),
+                        **dataset_params.as_dict(),
                     )
 
             if post_process:
@@ -160,9 +161,9 @@ class thumbnailExtension(FactoryExtension):
 
             content = image.render(
                 img_format=format.driver,
-                colormap=colormap or dst_colormap,
+                colormap=colormap,
                 **format.profile,
-                **render_params,
+                **render_params.as_dict(),
             )
 
             return Response(content, media_type=format.mediatype)
diff --git a/docs/src/advanced/customization.md b/docs/src/advanced/customization.md
index 13bcbfaa6..0daabfbd7 100644
--- a/docs/src/advanced/customization.md
+++ b/docs/src/advanced/customization.md
@@ -1,6 +1,33 @@
 
 `TiTiler` is designed to help user customize input/output for each endpoint. This section goes over some simple customization examples.
 
+### Custom Colormap
+
+Add user defined colormap to the default colormaps provided by rio-tiler
+
+```python
+from fastapi import FastAPI
+
+from rio_tiler.colormap import cmap as default_cmap
+
+from titiler.core.dependencies import create_colormap_dependency
+from titiler.core.factory import TilerFactory
+
+
+app = FastAPI(title="My simple app with custom TMS")
+
+cmap_values = {
+    "cmap1": {6: (4, 5, 6, 255)},
+}
+# add custom colormap `cmap1` to the default colormaps
+cmap = default_cmap.register(cmap_values)
+ColorMapParams = create_colormap_dependency(cmap)
+
+
+cog = TilerFactory(colormap_dependency=ColorMapParams)
+app.include_router(cog.router)
+```
+
 ### Custom DatasetPathParams for `reader_dependency`
 
 One common customization could be to create your own `path_dependency`. This dependency is used on all endpoint and pass inputs to the *Readers* (MosaicBackend, COGReader, STACReader...).
@@ -13,7 +40,6 @@ import re
 
 from fastapi import FastAPI, HTTPException, Query
 
-from titiler.core.dependencies import DefaultDependency
 from titiler.mosaic.factory import MosaicTilerFactory
 
 
diff --git a/docs/src/advanced/dependencies.md b/docs/src/advanced/dependencies.md
index 937395e1f..5e3fe1db3 100644
--- a/docs/src/advanced/dependencies.md
+++ b/docs/src/advanced/dependencies.md
@@ -5,31 +5,17 @@ In titiler `Factories`, we use the dependencies to define the inputs for each en
 
 Example:
 ```python
-# Custom Dependency
-
 from dataclasses import dataclass
-from typing import Optional
-
 from fastapi import Depends, FastAPI, Query
 from titiler.core.dependencies import DefaultDependency
-
-from rio_tiler.io import COGReader
+from typing_extensions import Annotated
+from rio_tiler.io import Reader
 
 @dataclass
 class ImageParams(DefaultDependency):
-    """Common Preview/Crop parameters."""
-
-    max_size: Optional[int] = Query(
-        1024, description="Maximum image size to read onto."
-    )
-    height: Optional[int] = Query(None, description="Force output image height.")
-    width: Optional[int] = Query(None, description="Force output image width.")
-
-    def __post_init__(self):
-        """Post Init."""
-        if self.width and self.height:
-            self.max_size = None
-
+    max_size: Annotated[
+        int, Query(description="Maximum image size to read onto.")
+    ] = 1024
 
 app = FastAPI()
 
@@ -39,15 +25,10 @@ def preview(
     url: str = Query(..., description="data set URL"),
     params: ImageParams = Depends(),
 ):
-
-    with COGReader(url) as cog:
-        img = cog.preview(**params)  # classes built with `DefaultDependency` can be unpacked
+    with Reader(url) as cog:
+        img = cog.preview(**params.as_dict())  # we use `DefaultDependency().as_dict()` to pass only non-None parameters
         # or
-        img = cog.preview(
-            max_size=params.max_size,
-            height=params.height,
-            width=params.width,
-        )
+        img = cog.preview(max_size=params.max_size)
     ...
 ```
 
@@ -55,238 +36,721 @@ def preview(
 
     In the example above, we create a custom `ImageParams` dependency which will then be injected to the `preview` endpoint to add  **max_size**, **height** and **width** query string parameters.
 
-    Using `titiler.core.dependencies.DefaultDependency`, we can `unpack` the class as if it was a dictionary, which helps with customization.
+    Using `titiler.core.dependencies.DefaultDependency`, we can use `.as_dict(exclude_none=True/False)` method to `unpack` the object parameters. This can be useful if method or reader do not take the same parameters.
+
+#### AssetsParams
+
+Define `assets`.
+
+| Name      | Type      | Required | Default
+| ------    | ----------|----------|--------------
+| **assets** | Query (str) | No | None
+
+
+ +```python +@dataclass +class AssetsParams(DefaultDependency): + """Assets parameters.""" + + assets: List[str] = Query( + None, + title="Asset names", + description="Asset's names.", + openapi_examples={ + "one-asset": { + "description": "Return results for asset `data`.", + "value": ["data"], + }, + "multi-assets": { + "description": "Return results for assets `data` and `cog`.", + "value": ["data", "cog"], + }, + }, + ) +``` +
-## Factories Dependencies -The `factories` allow users to set multiple default dependencies. Here is the list of common dependencies and their **default** values. +#### AssetsBidxParams -### BaseTilerFactory +Define `assets` with option of `per-asset` expression with `asset_expression` option. -#### path_dependency +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **assets** | Query (str) | No | None +| **asset_indexes** | Query (str) | No | None +| **asset_expression** | Query (str) | No | False -Set dataset path (url). +
```python -def DatasetPathParams( - url: str = Query(..., description="Dataset URL") -) -> str: - """Create dataset path from args""" - return url +@dataclass +class AssetsBidxParams(AssetsParams): + """Assets, Asset's band Indexes and Asset's band Expression parameters.""" + + asset_indexes: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band indexes", + description="Per asset band indexes", + alias="asset_bidx", + openapi_examples={ + "one-asset": { + "description": "Return indexes 1,2,3 of asset `data`.", + "value": ["data|1;2;3"], + }, + "multi-assets": { + "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", + "value": ["data|1;2;3", "cog|1"], + }, + }, + ), + ] = None + + asset_expression: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band expression", + description="Per asset band expression", + openapi_examples={ + "one-asset": { + "description": "Return results for expression `b1*b2+b3` of asset `data`.", + "value": ["data|b1*b2+b3"], + }, + "multi-assets": { + "description": "Return results for expressions `b1*b2+b3` for asset `data` and `b1+b3` for asset `cog`.", + "value": ["data|b1*b2+b3", "cog|b1+b3"], + }, + }, + ), + ] = None + + def __post_init__(self): + """Post Init.""" + if self.asset_indexes: + self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore + idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) + for idx in self.asset_indexes + } + + if self.asset_expression: + self.asset_expression: Dict[str, str] = { # type: ignore + idx.split("|")[0]: idx.split("|")[1] for idx in self.asset_expression + } ``` -#### layer_dependency +
+ +#### AssetsBidxExprParams + +Define `assets`. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **assets** | Query (str) | No\* | None +| **expression** | Query (str) | No\* | None +| **asset_indexes** | Query (str) | No | None +| **asset_as_band** | Query (bool) | No | False + +\* `assets` or `expression` is required. -Define band indexes or expression +
```python @dataclass -class BidxParams(DefaultDependency): - """Band Indexes parameters.""" +class AssetsBidxExprParams(AssetsParams): + """Assets, Expression and Asset's band Indexes parameters.""" + + expression: Annotated[ + Optional[str], + Query( + title="Band Math expression", + description="Band math expression between assets", + openapi_examples={ + "simple": { + "description": "Return results of expression between assets.", + "value": "asset1_b1 + asset2_b1 / asset3_b1", + }, + }, + ), + ] = None + + asset_indexes: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band indexes", + description="Per asset band indexes (coma separated indexes)", + alias="asset_bidx", + openapi_examples={ + "one-asset": { + "description": "Return indexes 1,2,3 of asset `data`.", + "value": ["data|1,2,3"], + }, + "multi-assets": { + "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", + "value": ["data|1,2,3", "cog|1"], + }, + }, + ), + ] = None - indexes: Optional[List[int]] = Query( - None, - title="Band indexes", - alias="bidx", - description="Dataset band indexes", - examples={"one-band": {"value": [1]}, "multi-bands": {"value": [1, 2, 3]}}, - ) + asset_as_band: Annotated[ + Optional[bool], + Query( + title="Consider asset as a 1 band dataset", + description="Asset as Band", + ), + ] = None + + def __post_init__(self): + """Post Init.""" + if not self.assets and not self.expression: + raise MissingAssets( + "assets must be defined either via expression or assets options." + ) + + if self.asset_indexes: + self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore + idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) + for idx in self.asset_indexes + } +``` + +
+ +#### AssetsBidxExprParamsOptional + +Define `assets`. Without requirement on assets nor expression. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **assets** | Query (str) | No | None +| **expression** | Query (str) | No | None +| **asset_indexes** | Query (str) | No | None +| **asset_as_band** | Query (bool) | No | False + +
+```python @dataclass -class ExpressionParams(DefaultDependency): - """Expression parameters.""" +class AssetsBidxExprParamsOptional(AssetsBidxExprParams): + """Assets, Expression and Asset's band Indexes parameters but with no requirement.""" + + def __post_init__(self): + """Post Init.""" + if self.asset_indexes: + self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore + idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) + for idx in self.asset_indexes + } +``` + +
+ + +#### BandsParams - expression: Optional[str] = Query( +Define `bands`. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **bands** | Query (str) | No | None + +
+ +```python +@dataclass +class BandsParams(DefaultDependency): + """Band names parameters.""" + + bands: List[str] = Query( None, - title="Band Math expression", - description="rio-tiler's band math expression", - examples={ - "simple": {"description": "Simple band math.", "value": "b1/b2"}, + title="Band names", + description="Band's names.", + openapi_examples={ + "one-band": { + "description": "Return results for band `B01`.", + "value": ["B01"], + }, "multi-bands": { - "description": "Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).", - "value": "b1/b2;b2+b3", + "description": "Return results for bands `B01` and `B02`.", + "value": ["B01", "B02"], }, }, ) +``` + +
+ +#### BandsExprParams + +Define `bands`. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **bands** | Query (str) | No\* | None +| **expression** | Query (str) | No\* | None + +\* `bands` or `expression` is required. + +
+ +```python @dataclass -class BidxExprParams(ExpressionParams, BidxParams): - """Band Indexes and Expression parameters.""" +class BandsExprParamsOptional(ExpressionParams, BandsParams): + """Optional Band names and Expression parameters.""" pass ``` -#### dataset_dependency +
-Overwrite nodata value, apply rescaling or change default resampling. +#### BandsExprParamsOptional + +Define `bands`. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **bands** | Query (str) | No | None +| **expression** | Query (str) | No | None + +
```python @dataclass -class DatasetParams(DefaultDependency): - """Low level WarpedVRT Optional parameters.""" +class BandsExprParamsOptional(ExpressionParams, BandsParams): + """Optional Band names and Expression parameters.""" - nodata: Optional[Union[str, int, float]] = Query( - None, title="Nodata value", description="Overwrite internal Nodata value" - ) - unscale: Optional[bool] = Query( - False, - title="Apply internal Scale/Offset", - description="Apply internal Scale/Offset", - ) - resampling_method: ResamplingName = Query( - ResamplingName.nearest, # type: ignore - alias="resampling", - description="Resampling method.", - ) + pass +``` - def __post_init__(self): - """Post Init.""" - if self.nodata is not None: - self.nodata = numpy.nan if self.nodata == "nan" else float(self.nodata) - self.resampling_method = self.resampling_method.value # type: ignore +
+ +#### `BidxParams` + +Define band indexes. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **bidx** | Query (int) | No | None + +
+ +```python +@dataclass +class BidxParams(DefaultDependency): + """Band Indexes parameters.""" + + indexes: Annotated[ + Optional[List[int]], + Query( + title="Band indexes", + alias="bidx", + description="Dataset band indexes", + openapi_examples={"one-band": {"value": [1]}, "multi-bands": {"value": [1, 2, 3]}}, + ), + ] = None ``` -#### render_dependency +
-Image rendering options. +#### `ExpressionParams` + +Define band expression. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **expression** | Query (str) | No | None + + +
```python @dataclass -class ImageRenderingParams(DefaultDependency): - """Image Rendering options.""" +class ExpressionParams(DefaultDependency): + """Expression parameters.""" - add_mask: bool = Query( - True, alias="return_mask", description="Add mask to the output data." - ) + expression: Annotated[ + Optional[str], + Query( + title="Band Math expression", + description="rio-tiler's band math expression", + openapi_examples={ + "simple": {"description": "Simple band math.", "value": "b1/b2"}, + "multi-bands": { + "description": "Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).", + "value": "b1/b2;b2+b3", + }, + }, + ), + ] = None ``` -#### colormap_dependency +
-Colormap options. +#### `BidxExprParams` + +Define band indexes or expression. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **bidx** | Query (int) | No | None +| **expression** | Query (str) | No | None + +
```python +@dataclass +class BidxExprParams(ExpressionParams, BidxParams): + """Band Indexes and Expression parameters.""" + + pass +``` + +
+ +#### `ColorFormulaParams` + +Color Formula option (see https://github.com/vincentsarago/color-operations). + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **color_formula** | Query (str) | No | None + +
+ +```python +def ColorFormulaParams( + color_formula: Annotated[ + Optional[str], + Query( + title="Color Formula", + description="rio-color formula (info: https://github.com/mapbox/rio-color)", + ), + ] = None, +) -> Optional[str]: + """ColorFormula Parameter.""" + return color_formula +``` + +
+ +#### `ColorMapParams` + +Colormap options. See [titiler.core.dependencies](https://github.com/developmentseed/titiler/blob/e46c35c8927b207f08443a274544901eb9ef3914/src/titiler/core/titiler/core/dependencies.py#L18-L54). + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **colormap_name** | Query (str) | No | None +| **colormap** | Query (encoded json) | No | None + +
+ +```python +cmap = {} + def ColorMapParams( - colormap_name: ColorMapName = Query(None, description="Colormap name"), - colormap: str = Query(None, description="JSON encoded custom Colormap"), -) -> Optional[Union[Dict, Sequence]]: - """Colormap Dependency.""" + colormap_name: Annotated[ # type: ignore + Literal[tuple(cmap.list())], + Query(description="Colormap name"), + ] = None, + colormap: Annotated[ + Optional[str], Query(description="JSON encoded custom Colormap") + ] = None, +): if colormap_name: - return cmap.get(colormap_name.value) + return cmap.get(colormap_name) if colormap: try: - return json.loads( + c = json.loads( colormap, - object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()}, + object_hook=lambda x: { + int(k): parse_color(v) for k, v in x.items() + }, ) - except json.JSONDecodeError: + + # Make sure to match colormap type + if isinstance(c, Sequence): + c = [(tuple(inter), parse_color(v)) for (inter, v) in c] + + return c + except json.JSONDecodeError as e: raise HTTPException( status_code=400, detail="Could not parse the colormap value." - ) + ) from e return None ``` -#### reader_dependency +
-Additional reader options. Defaults to `DefaultDependency` (empty). +#### CoordCRSParams +Define input Coordinate Reference System. -#### Other Attributes +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **crs** | Query (str) | No | None -##### Supported TMS -The TMS dependency sets the available TMS for a tile endpoint. +
```python -# Allow all morecantile TMS -from morecantile import tms as default_tms +def CoordCRSParams( + crs: Annotated[ + Optional[str], + Query( + alias="coord_crs", + description="Coordinate Reference System of the input coords. Default to `epsg:4326`.", + ), + ] = None, +) -> Optional[CRS]: + """Coordinate Reference System Coordinates Param.""" + if crs: + return CRS.from_user_input(crs) + + return None +``` + +
+ +#### `DatasetParams` -tiler = TilerFactory(supported_tms=default_tms) +Overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **nodata** | Query (str, int, float) | No | None +| **unscale** | Query (bool) | No | False +| **resampling** | Query (str) | No | 'nearest' +| **reproject** | Query (str) | No | 'nearest' -# Restrict the TMS to `WebMercatorQuad` only -from morecantile import tms -from morecantile.defaults import TileMatrixSets +
-# Construct a TileMatrixSets object with only the `WebMercatorQuad` tms -default_tms = TileMatrixSets({"WebMercatorQuad": tms.get("WebMercatorQuad")}) -tiler = TilerFactory(supported_tms=default_tms) +```python +@dataclass +class DatasetParams(DefaultDependency): + """Low level WarpedVRT Optional parameters.""" + + nodata: Annotated[ + Optional[Union[str, int, float]], + Query( + title="Nodata value", + description="Overwrite internal Nodata value", + ), + ] = None + unscale: Annotated[ + bool, + Query( + title="Apply internal Scale/Offset", + description="Apply internal Scale/Offset. Defaults to `False` in rio-tiler.", + ), + ] = False + resampling_method: Annotated[ + Optional[RIOResampling], + Query( + alias="resampling", + description="RasterIO resampling algorithm. Defaults to `nearest` in rio-tiler.", + ), + ] = None + reproject_method: Annotated[ + Optional[WarpResampling], + Query( + alias="reproject", + description="WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest` in rio-tiler.", + ), + ] = None + + def __post_init__(self): + """Post Init.""" + if self.nodata is not None: + self.nodata = numpy.nan if self.nodata == "nan" else float(self.nodata) + + if self.unscale is not None: + self.unscale = bool(self.unscale) ``` -##### Default TMS +
+ +#### `DatasetPathParams` + +Set dataset path. + +| Name | Type | Required | Default +| ------ | ----------|--------------------- |-------------- +| **url** | Query (str) | :warning: **Yes** :warning: | - + -Set the default's TMS Identifier (default to `WebMercatorQuad`). +
```python -# Create a Tile with it's default TMS being `WGS1984Quad` -tiler = TilerFactory(default_tms="WGS1984Quad") +def DatasetPathParams( + url: Annotated[str, Query(description="Dataset URL")] +) -> str: + """Create dataset path from args""" + return url ``` -### TilerFactory +
+ -The `TilerFactory` inherits dependency from `BaseTilerFactory`. +#### DstCRSParams -#### metadata_dependency +Define output Coordinate Reference System. -`rio_tiler.io.BaseReader.metadata()` methods options. +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **crs** | Query (str) | No | None + + +
+ +```python +def DstCRSParams( + crs: Annotated[ + Optional[str], + Query( + alias="dst_crs", + description="Output Coordinate Reference System.", + ), + ] = None, +) -> Optional[CRS]: + """Coordinate Reference System Coordinates Param.""" + if crs: + return CRS.from_user_input(crs) + + return None +``` + +
+ +#### HistogramParams + +Define *numpy*'s histogram options. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **histogram_bins** | Query (encoded list of Number) | No | 10 +| **histogram_range** | Query (encoded list of Number) | No | None + +
```python @dataclass -class MetadataParams(DefaultDependency): - """Common Metadada parameters.""" +class HistogramParams(DefaultDependency): + """Numpy Histogram options.""" + + bins: Annotated[ + Optional[str], + Query( + alias="histogram_bins", + title="Histogram bins.", + description=""" +Defines the number of equal-width bins in the given range (10, by default). + +If bins is a sequence (comma `,` delimited values), it defines a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform bin widths. + +link: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html + """, + openapi_examples={ + "simple": { + "description": "Defines the number of equal-width bins", + "value": 8, + }, + "array": { + "description": "Defines custom bin edges (comma `,` delimited values)", + "value": "0,100,200,300", + }, + }, + ), + ] = None - # Required params - pmin: float = Query(2.0, description="Minimum percentile") - pmax: float = Query(98.0, description="Maximum percentile") + range: Annotated[ + Optional[str], + Query( + alias="histogram_range", + title="Histogram range", + description=""" +Comma `,` delimited range of the bins. - # Optional params - max_size: Optional[int] = Query( - None, description="Maximum image size to read onto." - ) - histogram_bins: Optional[int] = Query(None, description="Histogram bins.") - histogram_range: Optional[str] = Query( - None, description="comma (',') delimited Min,Max histogram bounds" - ) - bounds: Optional[str] = Query( - None, - descriptions="comma (',') delimited Bounding box coordinates from which to calculate image statistics.", - ) +The lower and upper range of the bins. If not provided, range is simply (a.min(), a.max()). + +Values outside the range are ignored. The first element of the range must be less than or equal to the second. +range affects the automatic bin computation as well. + +link: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html + """, + examples="0,1000", + ), + ] = None def __post_init__(self): """Post Init.""" - if self.max_size is not None: - self.kwargs["max_size"] = self.max_size - - if self.bounds: - self.kwargs["bounds"] = tuple(map(float, self.bounds.split(","))) - - hist_options = {} - if self.histogram_bins: - hist_options.update(dict(bins=self.histogram_bins)) - if self.histogram_range: - hist_options.update( - dict(range=list(map(float, self.histogram_range.split(",")))) - ) - if hist_options: - self.kwargs["hist_options"] = hist_options + if self.bins: + bins = self.bins.split(",") + if len(bins) == 1: + self.bins = int(bins[0]) # type: ignore + else: + self.bins = list(map(float, bins)) # type: ignore + else: + self.bins = 10 + + if self.range: + self.range = list(map(float, self.range.split(","))) # type: ignore ``` -#### img_dependency +
-Used in Crop/Preview to define size of the output image. +#### `ImageRenderingParams` + +Control output image rendering options. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **return_mask** | Query (bool) | No | False + +
```python @dataclass -class ImageParams(DefaultDependency): - """Common Preview/Crop parameters.""" +class ImageRenderingParams(DefaultDependency): + """Image Rendering options.""" - max_size: Optional[int] = Query( - 1024, description="Maximum image size to read onto." - ) - height: Optional[int] = Query(None, description="Force output image height.") - width: Optional[int] = Query(None, description="Force output image width.") + add_mask: Annotated[ + Optional[bool], + Query( + alias="return_mask", + description="Add mask to the output data. Defaults to `True` in rio-tiler", + ), + ] = None +``` + +
+ +#### PartFeatureParams + +Same as `PreviewParams` but without default `max_size`. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **max_size** | Query (int) | No | None +| **height** | Query (int) | No | None +| **width** | Query (int) | No | None + +
+ +```python +@dataclass +class PartFeatureParams(DefaultDependency): + """Common parameters for bbox and feature.""" + + max_size: Annotated[Optional[int], "Maximum image size to read onto."] = None + height: Annotated[Optional[int], "Force output image height."] = None + width: Annotated[Optional[int], "Force output image width."] = None def __post_init__(self): """Post Init.""" @@ -294,70 +758,215 @@ class ImageParams(DefaultDependency): self.max_size = None ``` -### MultiBaseTilerFactory +
-The `MultiBaseTilerFactory` inherits dependency from `TilerFactory` and `BaseTilerFactory`. +#### PixelSelectionParams -#### assets_dependency +In `titiler.mosaic`, define pixel-selection method to apply. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **pixel_selection** | Query (str) | No | 'first' -Define `assets`. + +
+ +```python +def PixelSelectionParams( + pixel_selection: Annotated[ # type: ignore + Literal[tuple([e.name for e in PixelSelectionMethod])], + Query(description="Pixel selection method."), + ] = "first", +) -> MosaicMethodBase: + """ + Returns the mosaic method used to combine datasets together. + """ + return PixelSelectionMethod[pixel_selection].value() +``` + +
+ +#### PreviewParams + +Define image output size. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **max_size** | Query (int) | No | 1024 +| **height** | Query (int) | No | None +| **width** | Query (int) | No | None + +
```python @dataclass -class AssetsParams(DefaultDependency): - """Assets parameters.""" +class PreviewParams(DefaultDependency): + """Common Preview parameters.""" - assets: List[str] = Query( - None, - title="Asset names", - description="Asset's names.", - examples={ - "one-asset": { - "description": "Return results for asset `data`.", - "value": ["data"], - }, - "multi-assets": { - "description": "Return results for assets `data` and `cog`.", - "value": ["data", "cog"], - }, - }, - ) + max_size: Annotated[int, "Maximum image size to read onto."] = 1024 + height: Annotated[Optional[int], "Force output image height."] = None + width: Annotated[Optional[int], "Force output image width."] = None + + def __post_init__(self): + """Post Init.""" + if self.width and self.height: + self.max_size = None ``` -### MultiBandTilerFactory +
-The `MultiBaseTilerFactory` inherits dependency from `TilerFactory` and `BaseTilerFactory`. +#### `RescalingParams` -#### bands_dependency +Set Min/Max values to rescale from, to 0 -> 255. -Define `bands`. +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **rescale** | Query (str, comma delimited Numer) | No | None + +
+ +```python +def RescalingParams( + rescale: Annotated[ + Optional[List[str]], + Query( + title="Min/Max data Rescaling", + description="comma (',') delimited Min,Max range. Can set multiple time for multiple bands.", + examples=["0,2000", "0,1000", "0,10000"], # band 1 # band 2 # band 3 + ), + ] = None, +) -> Optional[RescaleType]: + """Min/Max data Rescaling""" + if rescale: + return [tuple(map(float, r.replace(" ", "").split(","))) for r in rescale] + + return None +``` + +
+ +#### StatisticsParams + +Define options for *rio-tiler*'s statistics method. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **categorical** | Query (bool) | No | False +| **categories** | Query (list of Number) | No | None +| **p** | Query (list of Number) | No | [2, 98] + +
```python @dataclass -class BandsParams(DefaultDependency): - """Band names parameters.""" +class StatisticsParams(DefaultDependency): + """Statistics options.""" + + categorical: Annotated[ + Optional[bool], + Query(description="Return statistics for categorical dataset. Defaults to `False` in rio-tiler"), + ] = None + categories: Annotated[ + Optional[List[Union[float, int]]], + Query( + alias="c", + title="Pixels values for categories.", + description="List of values for which to report counts.", + examples=[1, 2, 3], + ), + ] = None + percentiles: Annotated[ + Optional[List[int]], + Query( + alias="p", + title="Percentile values", + description="List of percentile values (default to [2, 98]).", + examples=[2, 5, 95, 98], + ), + ] = None - bands: List[str] = Query( - None, - title="Band names", - description="Band's names.", - examples={ - "one-band": { - "description": "Return results for band `B01`.", - "value": ["B01"], - }, - "multi-bands": { - "description": "Return results for bands `B01` and `B02`.", - "value": ["B01", "B02"], - }, - }, - ) + def __post_init__(self): + """Set percentiles default.""" + if not self.percentiles: + self.percentiles = [2, 98] ``` -### MosaicTilerFactory +
+ +#### TileParams -The `MultiBaseTilerFactory` inherits dependency from `BaseTilerFactory`. +Defile `buffer` and `padding` to apply at tile creation. + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **buffer** | Query (float) | No | None +| **padding** | Query (int) | No | None + +
+ +```python +@dataclass +class TileParams(DefaultDependency): + """Tile options.""" + + buffer: Annotated[ + Optional[float], + Query( + gt=0, + title="Tile buffer.", + description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", + ), + ] = None + + padding: Annotated[ + Optional[int], + Query( + gt=0, + title="Tile padding.", + description="Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`.", + ), + ] = None +``` + +
+ +#### `algorithm.dependency` + +Control which `algorithm` to apply to the data. + +See [titiler.core.algorithm](https://github.com/developmentseed/titiler/blob/e46c35c8927b207f08443a274544901eb9ef3914/src/titiler/core/titiler/core/algorithm/__init__.py#L54-L79). + +| Name | Type | Required | Default +| ------ | ----------|----------|-------------- +| **algorithm** | Query (str) | No | None +| **algorithm_params** | Query (encoded json) | No | None + +
+ +```python +algorithms = {} + +def post_process( + algorithm: Annotated[ + Literal[tuple(algorithms.keys())], + Query(description="Algorithm name"), + ] = None, + algorithm_params: Annotated[ + Optional[str], + Query(description="Algorithm parameter"), + ] = None, +) -> Optional[BaseAlgorithm]: + """Data Post-Processing options.""" + kwargs = json.loads(algorithm_params) if algorithm_params else {} + if algorithm: + try: + return algorithms.get(algorithm)(**kwargs) + + except ValidationError as e: + raise HTTPException(status_code=400, detail=str(e)) from e + + return None +``` -#### backend_dependency +
-Additional backend options. Defaults to `DefaultDependency` (empty). diff --git a/docs/src/advanced/endpoints_factories.md b/docs/src/advanced/endpoints_factories.md new file mode 100644 index 000000000..9d5bcdf1a --- /dev/null +++ b/docs/src/advanced/endpoints_factories.md @@ -0,0 +1,363 @@ + +TiTiler's endpoints factories are helper functions that let users create a FastAPI *router* (`fastapi.APIRouter`) with a minimal set of endpoints. + +!!! Important + + Most of `tiler` **Factories** are built around [`rio_tiler.io.BaseReader`](https://cogeotiff.github.io/rio-tiler/advanced/custom_readers/), which defines basic methods to access datasets (e.g COG or STAC). The default reader is `Reader` for `TilerFactory` and `MosaicBackend` for `MosaicTilerFactory`. + + Factories classes use [dependencies injection](dependencies.md) to define most of the endpoint options. + + +## BaseFactory + +class: `titiler.core.factory.BaseFactory` + +Most **Factories** are built from this [abstract based class](https://docs.python.org/3/library/abc.html) which is used to define commons attributes and utility functions shared between all factories. + +#### Attributes + +- **router**: FastAPI router. Defaults to `fastapi.APIRouter`. +- **router_prefix**: Set prefix to all factory's endpoint. Defaults to `""`. +- **route_dependencies**: Additional routes dependencies to add after routes creations. Defaults to `[]`. +- **extension**: TiTiler extensions to register after endpoints creations. Defaults to `[]`. + +#### Methods + +- **register_routes**: Abstract method which needs to be define by each factories. +- **url_for**: Method to construct endpoint URL +- **add_route_dependencies**: Add dependencies to routes. + +## TilerFactory + +class: `titiler.core.factory.TilerFactory` + +Factory meant to create endpoints for single dataset using [*rio-tiler*'s `Reader`](https://cogeotiff.github.io/rio-tiler/readers/#rio_tileriorasterioreader). + +#### Attributes + +- **reader**: Dataset Reader **required**. +- **reader_dependency**: Dependency to control options passed to the reader instance init. Defaults to `titiler.core.dependencies.DefaultDependency` +- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.core.dependencies.DatasetPathParams`. +- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.BidxExprParams`. +- **dataset_dependency**: Dependency to overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. Defaults to `titiler.core.dependencies.DatasetParams`. +- **tile_dependency**: Dependency to defile `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`. +- **stats_dependency**: Dependency to define options for *rio-tiler*'s statistics method used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.StatisticsParams`. +- **histogram_dependency**: Dependency to define *numpy*'s histogram options used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.HistogramParams`. +- **img_preview_dependency**: Dependency to define image size for `/preview` and `/statistics` endpoints. Defaults to `titiler.core.dependencies.PreviewParams`. +- **img_part_dependency**: Dependency to define image size for `/bbox` and `/feature` endpoints. Defaults to `titiler.core.dependencies.PartFeatureParams`. +- **process_dependency**: Dependency to control which `algorithm` to apply to the data. Defaults to `titiler.core.algorithm.algorithms.dependency`. +- **rescale_dependency**: Dependency to set Min/Max values to rescale from, to 0 -> 255. Defaults to `titiler.core.dependencies.RescalingParams`. +- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`. +- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams` +- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams` +- **environment_dependency**: Dependency to defile GDAL environment at runtime. Default to `lambda: {}`. +- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`. +- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`. +- **add_preview**: . Add `/preview` endpoint to the router. Defaults to `True`. +- **add_part**: . Add `/bbox` and `/feature` endpoints to the router. Defaults to `True`. +- **add_viewer**: . Add `/map` endpoints to the router. Defaults to `True`. + +#### Endpoints + +```python +from fastapi import FastAPI + +from titiler.core.factory import TilerFactory + +# Create FastAPI application +app = FastAPI() + +# Create router and register set of endpoints +cog = TilerFactory( + add_preview=True, + add_part=True, + add_viewer=True, +) + +# add router endpoint to the main application +app.include_router(cog.router) +``` + +| Method | URL | Output | Description +| ------ | --------------------------------------------------------------- |-------------------------------------------- |-------------- +| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds +| `GET` | `/info` | JSON ([Info][info_model]) | return dataset's basic info +| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return dataset's basic info as a GeoJSON feature +| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return dataset's statistics +| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return dataset's statistics for a GeoJSON +| `GET` | `/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset +| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional** +| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document +| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel values from a dataset +| `GET` | `/preview[.{format}]` | image/bin | create a preview image from a dataset **Optional** +| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional** +| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature **Optional** + + +## MultiBaseTilerFactory + +class: `titiler.core.factory.MultiBaseTilerFactory` + +Custom `TilerFactory` to be used with [`rio_tiler.io.MultiBaseReader`](https://cogeotiff.github.io/rio-tiler/advanced/custom_readers/#multibasereader) type readers (e.g [`rio_tiler.io.STACReader`](https://cogeotiff.github.io/rio-tiler/readers/#rio_tileriostacstacreader)). + +#### Attributes + +- **reader**: `rio_tiler.io.base.MultiBaseReader` Dataset Reader **required**. +- **layer_dependency**: Dependency to define assets or expression. Defaults to `titiler.core.dependencies.AssetsBidxExprParams`. +- **assets_dependency**: Dependency to define assets to be used. Defaults to `titiler.core.dependencies.AssetsParams`. + +#### Endpoints + +```python +from fastapi import FastAPI + +from rio_tiler.io import STACReader # STACReader is a MultiBaseReader + +from titiler.core.factory import MultiBaseTilerFactory + +app = FastAPI() +stac = MultiBaseTilerFactory(reader=STACReader) +app.include_router(stac.router) +``` + +| Method | URL | Output | Description +| ------ | --------------------------------------------------------------- |------------------------------------------------- |-------------- +| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds +| `GET` | `/assets` | JSON | return the list of available assets +| `GET` | `/info` | JSON ([Info][multiinfo_model]) | return assets basic info +| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][multiinfo_geojson_model]) | return assets basic info as a GeoJSON feature +| `GET` | `/asset_statistics` | JSON ([Statistics][multistats_model]) | return per asset statistics +| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return assets statistics (merged) +| `POST` | `/statistics` | GeoJSON ([Statistics][multistats_geojson_model]) | return assets statistics for a GeoJSON (merged) +| `GET` | `/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets +| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional** +| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document +| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/point/{lon},{lat}` | JSON ([Point][multipoint_model]) | return pixel values from assets +| `GET` | `/preview[.{format}]` | image/bin | create a preview image from assets **Optional** +| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets **Optional** +| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature intersecting assets **Optional** + + +## MultiBandTilerFactory + +class: `titiler.core.factory.MultiBandTilerFactory` + +Custom `TilerFactory` to be used with [`rio_tiler.io.MultiBandReader`](https://cogeotiff.github.io/rio-tiler/advanced/custom_readers/#multibandsreader) type readers. + +#### Attributes + +- **reader**: `rio_tiler.io.base.MultiBandReader` Dataset Reader **required**. +- **layer_dependency**: Dependency to define assets or expression. Defaults to `titiler.core.dependencies.BandsExprParams`. +- **bands_dependency**: Dependency to define bands to be used. Defaults to `titiler.core.dependencies.BandsParams`. + +#### Endpoints + +```python +from fastapi import FastAPI, Query + + +from rio_tiler_pds.landsat.aws import LandsatC2Reader # LandsatC2Reader is a MultiBandReader +from titiler.core.factory import MultiBandTilerFactory + + +def SceneIDParams( + sceneid: Annotated[ + str, + Query(description="Landsat Scene ID") + ] +) -> str: + """Use `sceneid` in query instead of url.""" + return sceneid + + +app = FastAPI() +landsat = MultiBandTilerFactory(reader=LandsatC2Reader, path_dependency=SceneIDParams) +app.include_router(landsat.router) +``` + +| Method | URL | Output | Description +| ------ | --------------------------------------------------------------- |--------------------------------------------- |-------------- +| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds +| `GET` | `/bands` | JSON | return the list of available bands +| `GET` | `/info` | JSON ([Info][info_model]) | return basic info for a dataset +| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return basic info for a dataset as a GeoJSON feature +| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return info and statistics for a dataset +| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return info and statistics for a dataset +| `GET` | `/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset +| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional** +| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document +| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel value from a dataset +| `GET` | `/preview[.{format}]` | image/bin | create a preview image from a dataset **Optional** +| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional** +| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature **Optional** + + +## MosaicTilerFactory + +class: `titiler.mosaic.factory.MosaicTilerFactory` + +Endpoints factory for mosaics, built on top of [MosaicJSON](https://github.com/developmentseed/mosaicjson-spec). + +#### Attributes + +- **backend**: `cogeo_mosaic.backends.BaseBackend` Mosaic backend. Defaults to `cogeo_mosaic.backend.MosaicBackend`. +- **backend_dependency**: Dependency to control options passed to the backend instance init. Defaults to `titiler.core.dependencies.DefaultDependency` +- **dataset_reader**: Dataset Reader. Defaults to `rio_tiler.io.Reader` +- **reader_dependency**: Dependency to control options passed to the reader instance init. Defaults to `titiler.core.dependencies.DefaultDependency` +- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.mosaic.factory.DatasetPathParams`. +- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.BidxExprParams`. +- **dataset_dependency**: Dependency to overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. Defaults to `titiler.core.dependencies.DatasetParams`. +- **tile_dependency**: Dependency to defile `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`. +- **process_dependency**: Dependency to control which `algorithm` to apply to the data. Defaults to `titiler.core.algorithm.algorithms.dependency`. +- **rescale_dependency**: Dependency to set Min/Max values to rescale from, to 0 -> 255. Defaults to `titiler.core.dependencies.RescalingParams`. +- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`. +- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams` +- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams` +- **pixel_selection_dependency**: Dependency to select the `pixel_selection` method. Defaults to `titiler.mosaic.factory.PixelSelectionParams`. +- **environment_dependency**: Dependency to defile GDAL environment at runtime. Default to `lambda: {}`. +- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`. +- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`. +- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`. +- **optional_headers**: List of OptionalHeader which endpoints could add (if implemented). Defaults to `[]`. +- **add_viewer**: . Add `/map` endpoints to the router. Defaults to `True`. + +#### Endpoints + +| Method | URL | Output | Description +| ------ | --------------------------------------------------------------- |--------------------------------------------------- |-------------- +| `GET` | `/` | JSON [MosaicJSON][mosaic_model] | return a MosaicJSON document +| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return mosaic's bounds +| `GET` | `/info` | JSON ([Info][mosaic_info_model]) | return mosaic's basic info +| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][mosaic_geojson_info_model]) | return mosaic's basic info as a GeoJSON feature +| `GET` | `/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a MosaicJSON +| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional** +| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document +| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/point/{lon},{lat}` | JSON ([Point][mosaic_point]) | return pixel value from a MosaicJSON dataset +| `GET` | `/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile +| `GET` | `/{lon},{lat}/assets` | JSON | return list of assets intersecting a point +| `GET` | `/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box + + +## TMSFactory + +class: `titiler.core.factory.TMSFactory` + +Endpoints factory for OGC `TileMatrixSets`. + +#### Attributes + +- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`. + +```python +from fastapi import FastAPI + +from titiler.core.factory import TMSFactory + +app = FastAPI() +tms = TMSFactory() +app.include_router(tms.router) +``` + +#### Endpoints + +| Method | URL | Output | Description +| ------ | ------------------------------------- |----------------------------------------------- |-------------- +| `GET` | `/tileMatrixSets` | JSON ([TileMatrixSetList][tilematrixset_list]) | retrieve the list of available tiling schemes (tile matrix sets) +| `GET` | `/tileMatrixSets/{tileMatrixSetId}` | JSON ([TileMatrixSet][tilematrixset]) | retrieve the definition of the specified tiling scheme (tile matrix set) + + +## AlgorithmFactory + +class: `titiler.core.factory.AlgorithmFactory` + +Endpoints factory for custom algorithms. + +#### Attributes + +- **supported_algorithm**: List of available `Algorithm`. Defaults to `titiler.core.algorithm.algorithms`. + +```python +from fastapi import FastAPI + +from titiler.core.factory import AlgorithmFactory + +app = FastAPI() +algo = AlgorithmFactory() +app.include_router(algo.router) +``` + +#### Endpoints + +| Method | URL | Output | Description +| ------ | ---------------------------- |--------------------------------------------------------- |-------------- +| `GET` | `/algorithms` | JSON (Dict of [Algorithm Metadata][algorithm_metadata]) | retrieve the list of available Algorithms +| `GET` | `/algorithms/{algorithmId}` | JSON ([Algorithm Metadata][algorithm_metadata]) | retrieve the metadata of the specified algorithm. + + +## ColorMapFactory + +class: `titiler.core.factory.ColorMapFactory` + +Endpoints factory for colorMaps metadata. + +#### Attributes + +- **supported_colormaps**: List of available `ColorMaps`. Defaults to `rio_tiler.colormap.cmap`. + +```python +from fastapi import FastAPI + +from titiler.core.factory import ColorMapFactory + +app = FastAPI() +colormap = ColorMapFactory() +app.include_router(colormap.router) +``` + +#### Endpoints + +| Method | URL | Output | Description +| ------ | ---------------------------- |-------------------------------------- |-------------- +| `GET` | `/colorMaps` | JSON ([colorMapList][colormap_list]) | retrieve the list of available colorMaps +| `GET` | `/colorMaps/{colorMapId}` | JSON ([colorMap][colormap]) | retrieve the metadata or image of the specified colorMap. + + + +[bounds_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L43-L46 +[info_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L56-L72 +[info_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L30 +[tilejson_model]: https://github.com/developmentseed/titiler/blob/2335048a407f17127099cbbc6c14e1328852d619/src/titiler/core/titiler/core/models/mapbox.py#L16-L38 +[point_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L11-L20 +[stats_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L32 +[stats_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L46-L49 + +[multiinfo_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L52 +[multiinfo_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L53 +[multipoint_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L23-L27 +[multistats_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L55 +[multistats_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L56-L59 + +[mosaic_info_model]: https://github.com/developmentseed/cogeo-mosaic/blob/1dc3c873472c8cf7634ad893b9cdc40105ca3874/cogeo_mosaic/models.py#L9-L17 +[mosaic_geojson_info_model]: https://github.com/developmentseed/titiler/blob/2bd1b159a9cf0932ad14e9eabf1e4e66498adbdc/src/titiler/mosaic/titiler/mosaic/factory.py#L130 +[mosaic_model]: https://github.com/developmentseed/cogeo-mosaic/blob/1dc3c873472c8cf7634ad893b9cdc40105ca3874/cogeo_mosaic/mosaic.py#L55-L72 +[mosaic_point]: https://github.com/developmentseed/titiler/blob/2bd1b159a9cf0932ad14e9eabf1e4e66498adbdc/src/titiler/mosaic/titiler/mosaic/models/responses.py#L8-L17 + +[tilematrixset_list]: https://github.com/developmentseed/titiler/blob/ffd67af34c2807a6e1447817f943446a58441ed8/src/titiler/core/titiler/core/models/OGC.py#L33-L40 +[tilematrixset]: https://github.com/developmentseed/morecantile/blob/eec54326ce2b134cfbc03dd69a3e2938e4109101/morecantile/models.py#L399-L490 + +[algorithm_metadata]: https://github.com/developmentseed/titiler/blob/ffd67af34c2807a6e1447817f943446a58441ed8/src/titiler/core/titiler/core/algorithm/base.py#L32-L40 + +[colormap_list]: https://github.com/developmentseed/titiler/blob/535304fd7e1b0bfbb791bdec8cbfb6e78b4a6eb5/src/titiler/core/titiler/core/models/responses.py#L51-L55 +[colormap]: https://github.com/cogeotiff/rio-tiler/blob/6343b571a367ef63a10d6807e3d907c3283ebb20/rio_tiler/types.py#L24-L27 diff --git a/docs/src/advanced/rendering.md b/docs/src/advanced/rendering.md index ea31bfd3a..fa131ff4d 100644 --- a/docs/src/advanced/rendering.md +++ b/docs/src/advanced/rendering.md @@ -16,19 +16,22 @@ Titiler supports both default colormaps (each with a name) and custom color maps ### Default Colormaps -Default colormaps pre-made, each with a given name. These maps come from the `rio-tiler` library, which has taken colormaps packaged with Matplotlib and has added others that are commonly used with raster data. +Default colormaps pre-made, each with a given name. These maps come from the `rio-tiler` library, which has taken colormaps packaged with Matplotlib and has added others that are commonly used with raster data. A list of available color maps can be found in Titiler's Swagger docs, or in the [rio-tiler documentation](https://cogeotiff.github.io/rio-tiler/colormap/#default-rio-tilers-colormaps). To use a default colormap, simply use the parameter `colormap_name`: -```python3 -import requests +```python +import httpx -resp = requests.get("titiler.xyz/cog/preview", params={ - "url": "", - "colormap_name": "" # e.g. autumn_r -}) +resp = httpx.get( + "https://titiler.xyz/cog/preview", + params={ + "url": "", + "colormap_name": "" # e.g. autumn_r + } +) ``` You can take any of the colormaps listed on `rio-tiler`, and add `_r` to reverse it. @@ -37,19 +40,19 @@ You can take any of the colormaps listed on `rio-tiler`, and add `_r` to reverse If you'd like to specify your own colormap, you can specify your own using an encoded JSON: -```python3 -import requests +```python +import httpx -response = requests.get( - f"titiler.xyz/cog/preview", +response = httpx.get( + "https://titiler.xyz/cog/preview", params={ - "url": "", + "url": "", "bidx": "1", - "colormap": { - "0": "#e5f5f9", - "10": "#99d8c9", - "255": "#2ca25f", - } + "colormap": json.dumps({ + "0": "#e5f5f9", + "10": "#99d8c9", + "255": "#2ca25f", + }) } ) ``` @@ -75,13 +78,13 @@ Titiler supports color formulae as defined in [Mapbox's `rio-color` plugin](http In Titiler, color_formulae are applied through the `color_formula` parameter as a string. An example of this option in action: -```python3 -import requests +```python +import httpx -response = requests.get( - f"titiler.xyz/cog/preview", +response = httpx.get( + "https://titiler.xyz/cog/preview", params={ - "url": "", + "url": "", "color_formula": "gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5" } ) @@ -91,25 +94,41 @@ response = requests.get( Rescaling is the act of adjusting the minimum and maximum values when rendering an image. In an image with a single band, the rescaled minimum value will be set to black, and the rescaled maximum value will be set to white. This is useful if you want to accentuate features that only appear at a certain pixel value (e.g. you have a DEM, but you want to highlight how the terrain changes between sea level and 100m). -Titiler supports rescaling on a per-band basis, using the `rescaling` parameter. The input is a list of comma-delimited min-max ranges (e.g. ["0,100", "100,200", "0,1000]). +All titiler endpoinds returning *image* support `rescale` parameter. The parameter should be in form of `"rescale={min},{max}"`. -```python3 -import requests +```python +import httpx -response = requests.get( - f"titiler.xyz/cog/preview", +response = httpx.get( + "https;//titiler.xyz/cog/preview", params={ - "url": "", - "rescaling": ["0,100", "0,1000", "0,10000"] - } + "url": "", + "rescale": "0,100", + }, +) +``` + +Titiler supports rescaling on a per-band basis, using multiple `rescale` parameters. + +```python +import httpx + +response = httpx.get( + "https;//titiler.xyz/cog/preview", + params=( + ("url", ""), + ("rescale", "0,100"), + ("rescale", "0,1000"), + ("rescale", "0,10000"), + ), ) ``` By default, Titiler will rescale the bands using the min/max values of the input datatype. For example, PNG images 8- or 16-bit unsigned pixels, -giving a possible range of 0 to 255 or 0 to 65,536, so Titiler will use these ranges to rescale to the output format. +giving a possible range of 0 to 255 or 0 to 65,536, so Titiler will use these ranges to rescale to the output format. -For certain datasets (e.g. DEMs) this default behaviour can make the image seem washed out (or even entirely one color), +For certain datasets (e.g. DEMs) this default behaviour can make the image seem washed out (or even entirely one color), so if you see this happen look into rescaling your images to something that makes sense for your data. It is also possible to add a [rescaling dependency](../../api/titiler/core/dependencies/#rescalingparams) to automatically apply -a default rescale. \ No newline at end of file +a default rescale. diff --git a/docs/src/advanced/tiler_factories.md b/docs/src/advanced/tiler_factories.md deleted file mode 100644 index c97dadd15..000000000 --- a/docs/src/advanced/tiler_factories.md +++ /dev/null @@ -1,147 +0,0 @@ - -Tiler factories are helper functions that let users create a FastAPI router (`fastapi.APIRouter`) with a minimal set of endpoints. - -### `titiler.core.factory.TilerFactory` - -```python -from fastapi import FastAPI - -from titiler.core.factory import TilerFactory - -app = FastAPI(description="A lightweight Cloud Optimized GeoTIFF tile server") -cog = TilerFactory() -app.include_router(cog.router, tags=["Cloud Optimized GeoTIFF"]) -``` - -| Method | URL | Output | Description -| ------ | --------------------------------------------------------------- |-------------------------------------------- |-------------- -| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds -| `GET` | `/info` | JSON ([Info][info_model]) | return dataset's basic info -| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return dataset's basic info as a GeoJSON feature -| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return dataset's statistics -| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return dataset's statistics for a GeoJSON -| `GET` | `/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset -| `GET` | `[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document -| `GET` | `[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities -| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel values from a dataset -| `GET` | `/preview[.{format}]` | image/bin | create a preview image from a dataset (**Optional**) -| `GET` | `/crop/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset (**Optional**) -| `POST` | `/crop[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature (**Optional**) -| `GET` | `/map` | HTML | return a simple map viewer -| `GET` | `[/{TileMatrixSetId}]/map` | HTML | return a simple map viewer - -### `titiler.core.factory.MultiBaseTilerFactory` - -Custom `TilerFactory` to be used with `rio_tiler.io.MultiBaseReader` type readers. - -```python -from fastapi import FastAPI -from rio_tiler.io import STACReader # rio_tiler.io.STACReader is a MultiBaseReader - -from titiler.core.factory import MultiBaseTilerFactory - -app = FastAPI(description="A lightweight STAC tile server") -cog = MultiBaseTilerFactory(reader=STACReader) -app.include_router(cog.router, tags=["STAC"]) -``` - -| Method | URL | Output | Description -| ------ | --------------------------------------------------------------- |------------------------------------------------- |-------------- -| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds -| `GET` | `/assets` | JSON | return the list of available assets -| `GET` | `/info` | JSON ([Info][multiinfo_model]) | return assets basic info -| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][multiinfo_geojson_model]) | return assets basic info as a GeoJSON feature -| `GET` | `/asset_statistics` | JSON ([Statistics][multistats_model]) | return per asset statistics -| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return assets statistics (merged) -| `POST` | `/statistics` | GeoJSON ([Statistics][multistats_geojson_model]) | return assets statistics for a GeoJSON (merged) -| `GET` | `/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets -| `GET` | `/[{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document -| `GET` | `/{TileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities -| `GET` | `/point/{lon},{lat}` | JSON ([Point][multipoint_model]) | return pixel values from assets -| `GET` | `/preview[.{format}]` | image/bin | create a preview image from assets (**Optional**) -| `GET` | `/crop/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets (**Optional**) -| `POST` | `/crop[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature intersecting assets (**Optional**) -| `GET` | `[/{TileMatrixSetId}]/map` | HTML | return a simple map viewer - -### `titiler.core.factory.MultiBandTilerFactory` - -Custom `TilerFactory` to be used with `rio_tiler.io.MultiBandReader` type readers. - -```python -from fastapi import FastAPI, Query -from rio_tiler_pds.landsat.aws import LandsatC2Reader # rio_tiler_pds.landsat.aws.LandsatC2Reader is a MultiBandReader - -from titiler.core.factory import MultiBandTilerFactory - - -def SceneIDParams(sceneid: str = Query(..., description="Landsat Scene ID")) -> str: - """Use `sceneid` in query instead of url.""" - return sceneid - - -app = FastAPI(description="A lightweight Landsat Collection 2 tile server") -cog = MultiBandTilerFactory(reader=LandsatC2Reader, path_dependency=SceneIDParams) -app.include_router(cog.router, tags=["Landsat"]) -``` - -| Method | URL | Output | Description -| ------ | --------------------------------------------------------------- |--------------------------------------------- |-------------- -| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds -| `GET` | `/bands` | JSON | return the list of available bands -| `GET` | `/info` | JSON ([Info][info_model]) | return basic info for a dataset -| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return basic info for a dataset as a GeoJSON feature -| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return info and statistics for a dataset -| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return info and statistics for a dataset -| `GET` | `/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset -| `GET` | `/[{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document -| `GET` | `/{TileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities -| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel value from a dataset -| `GET` | `/preview[.{format}]` | image/bin | create a preview image from a dataset -| `GET` | `/crop/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset -| `POST` | `/crop[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature -| `GET` | `[/{TileMatrixSetId}]/map` | HTML | return a simple map viewer - -### `titiler.mosaic.factory.MosaicTilerFactory` - - -| Method | URL | Output | Description -| ------ | --------------------------------------------------------------- |--------------------------------------------------- |-------------- -| `GET` | `/` | JSON [MosaicJSON][mosaic_model] | return a MosaicJSON document -| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return mosaic's bounds -| `GET` | `/info` | JSON ([Info][mosaic_info_model]) | return mosaic's basic info -| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][mosaic_geojson_info_model]) | return mosaic's basic info as a GeoJSON feature -| `GET` | `/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a MosaicJSON -| `GET` | `[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document -| `GET` | `[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities -| `GET` | `/point/{lon},{lat}` | JSON ([Point][mosaic_point]) | return pixel value from a MosaicJSON dataset -| `GET` | `/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile -| `GET` | `/{lon},{lat}/assets` | JSON | return list of assets intersecting a point -| `GET` | `/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box -| `GET` | `[/{TileMatrixSetId}]/map` | HTML | return a simple map viewer - - -!!! Important - - **Factories** are built around [`rio_tiler.io.BaseReader`](https://cogeotiff.github.io/rio-tiler/advanced/custom_readers/), which defines basic methods to access datasets (e.g COG or STAC). The default reader is `COGReader` for `TilerFactory` and `MosaicBackend` for `MosaicTilerFactory`. - - Factories classes use [dependencies injection](dependencies.md) to define most of the endpoint options. - - -[bounds_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L43-L46 -[info_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L56-L72 -[info_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L30 -[tilejson_model]: https://github.com/developmentseed/titiler/blob/2335048a407f17127099cbbc6c14e1328852d619/src/titiler/core/titiler/core/models/mapbox.py#L16-L38 -[point_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L11-L20 -[stats_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L32 -[stats_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L46-L49 - -[multiinfo_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L52 -[multiinfo_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L53 -[multipoint_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L23-L27 -[multistats_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L55 -[multistats_geojson_model]: https://github.com/developmentseed/titiler/blob/c97e251c46b51703d41b1c9e66bc584649aa231c/src/titiler/core/titiler/core/models/responses.py#L56-L59 - -[mosaic_info_model]: https://github.com/developmentseed/cogeo-mosaic/blob/1dc3c873472c8cf7634ad893b9cdc40105ca3874/cogeo_mosaic/models.py#L9-L17 -[mosaic_geojson_info_model]: https://github.com/developmentseed/titiler/blob/2bd1b159a9cf0932ad14e9eabf1e4e66498adbdc/src/titiler/mosaic/titiler/mosaic/factory.py#L130 -[mosaic_model]: https://github.com/developmentseed/cogeo-mosaic/blob/1dc3c873472c8cf7634ad893b9cdc40105ca3874/cogeo_mosaic/mosaic.py#L55-L72 -[mosaic_point]: https://github.com/developmentseed/titiler/blob/2bd1b159a9cf0932ad14e9eabf1e4e66498adbdc/src/titiler/mosaic/titiler/mosaic/models/responses.py#L8-L17 diff --git a/docs/src/api/titiler/core/dependencies.md b/docs/src/api/titiler/core/dependencies.md index 51ae7afb2..3e4e260f6 100644 --- a/docs/src/api/titiler/core/dependencies.md +++ b/docs/src/api/titiler/core/dependencies.md @@ -1,1471 +1,5 @@ -# Module titiler.core.dependencies -Common dependency. +::: titiler.core.dependencies + options: + show_source: true -None - -## Functions - - -### ColorMapParams - -```python3 -def ColorMapParams( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict, Sequence, NoneType] -``` - - -Colormap Dependency. - - -### DatasetPathParams - -```python3 -def DatasetPathParams( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -### TMSParams - -```python3 -def TMSParams( - TileMatrixSetId: titiler.core.dependencies.TileMatrixSetName = Query(TileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -### WebMercatorTMSParams - -```python3 -def WebMercatorTMSParams( - TileMatrixSetId: titiler.core.dependencies.WebMercatorTileMatrixSetName = Query(WebMercatorTileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - -## Classes - -### AssetsBidxExprParams - -```python3 -class AssetsBidxExprParams( - assets: Union[List[str], NoneType] = Query(None), - expression: Union[str, NoneType] = Query(None), - asset_indexes: Union[Sequence[str], NoneType] = Query(None), - asset_expression: Union[Sequence[str], NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -asset_expression -``` - -```python3 -asset_indexes -``` - -```python3 -assets -``` - -```python3 -expression -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### AssetsBidxParams - -```python3 -class AssetsBidxParams( - assets: List[str] = Query(None), - asset_indexes: Union[Sequence[str], NoneType] = Query(None), - asset_expression: Union[Sequence[str], NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.AssetsParams -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -asset_expression -``` - -```python3 -asset_indexes -``` - -```python3 -assets -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### AssetsParams - -```python3 -class AssetsParams( - assets: List[str] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Descendants - -* titiler.core.dependencies.AssetsBidxParams - -#### Class variables - -```python3 -assets -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### BandsExprParams - -```python3 -class BandsExprParams( - bands: List[str] = Query(None), - expression: Union[str, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.ExpressionParams -* titiler.core.dependencies.BandsParams -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -bands -``` - -```python3 -expression -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### BandsExprParamsOptional - -```python3 -class BandsExprParamsOptional( - bands: List[str] = Query(None), - expression: Union[str, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.ExpressionParams -* titiler.core.dependencies.BandsParams -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -bands -``` - -```python3 -expression -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### BandsParams - -```python3 -class BandsParams( - bands: List[str] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Descendants - -* titiler.core.dependencies.BandsExprParamsOptional -* titiler.core.dependencies.BandsExprParams - -#### Class variables - -```python3 -bands -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### BidxExprParams - -```python3 -class BidxExprParams( - indexes: Union[List[int], NoneType] = Query(None), - expression: Union[str, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.ExpressionParams -* titiler.core.dependencies.BidxParams -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -expression -``` - -```python3 -indexes -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### BidxParams - -```python3 -class BidxParams( - indexes: Union[List[int], NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Descendants - -* titiler.core.dependencies.BidxExprParams - -#### Class variables - -```python3 -indexes -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### ColorMapName - -```python3 -class ColorMapName( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* enum.Enum - -#### Class variables - -```python3 -accent -``` - -```python3 -accent_r -``` - -```python3 -afmhot -``` - -```python3 -afmhot_r -``` - -```python3 -autumn -``` - -```python3 -autumn_r -``` - -```python3 -binary -``` - -```python3 -binary_r -``` - -```python3 -blues -``` - -```python3 -blues_r -``` - -```python3 -bone -``` - -```python3 -bone_r -``` - -```python3 -brbg -``` - -```python3 -brbg_r -``` - -```python3 -brg -``` - -```python3 -brg_r -``` - -```python3 -bugn -``` - -```python3 -bugn_r -``` - -```python3 -bupu -``` - -```python3 -bupu_r -``` - -```python3 -bwr -``` - -```python3 -bwr_r -``` - -```python3 -cfastie -``` - -```python3 -cividis -``` - -```python3 -cividis_r -``` - -```python3 -cmrmap -``` - -```python3 -cmrmap_r -``` - -```python3 -cool -``` - -```python3 -cool_r -``` - -```python3 -coolwarm -``` - -```python3 -coolwarm_r -``` - -```python3 -copper -``` - -```python3 -copper_r -``` - -```python3 -cubehelix -``` - -```python3 -cubehelix_r -``` - -```python3 -dark2 -``` - -```python3 -dark2_r -``` - -```python3 -flag -``` - -```python3 -flag_r -``` - -```python3 -gist_earth -``` - -```python3 -gist_earth_r -``` - -```python3 -gist_gray -``` - -```python3 -gist_gray_r -``` - -```python3 -gist_heat -``` - -```python3 -gist_heat_r -``` - -```python3 -gist_ncar -``` - -```python3 -gist_ncar_r -``` - -```python3 -gist_rainbow -``` - -```python3 -gist_rainbow_r -``` - -```python3 -gist_stern -``` - -```python3 -gist_stern_r -``` - -```python3 -gist_yarg -``` - -```python3 -gist_yarg_r -``` - -```python3 -gnbu -``` - -```python3 -gnbu_r -``` - -```python3 -gnuplot -``` - -```python3 -gnuplot2 -``` - -```python3 -gnuplot2_r -``` - -```python3 -gnuplot_r -``` - -```python3 -gray -``` - -```python3 -gray_r -``` - -```python3 -greens -``` - -```python3 -greens_r -``` - -```python3 -greys -``` - -```python3 -greys_r -``` - -```python3 -hot -``` - -```python3 -hot_r -``` - -```python3 -hsv -``` - -```python3 -hsv_r -``` - -```python3 -inferno -``` - -```python3 -inferno_r -``` - -```python3 -jet -``` - -```python3 -jet_r -``` - -```python3 -magma -``` - -```python3 -magma_r -``` - -```python3 -name -``` - -```python3 -nipy_spectral -``` - -```python3 -nipy_spectral_r -``` - -```python3 -ocean -``` - -```python3 -ocean_r -``` - -```python3 -oranges -``` - -```python3 -oranges_r -``` - -```python3 -orrd -``` - -```python3 -orrd_r -``` - -```python3 -paired -``` - -```python3 -paired_r -``` - -```python3 -pastel1 -``` - -```python3 -pastel1_r -``` - -```python3 -pastel2 -``` - -```python3 -pastel2_r -``` - -```python3 -pink -``` - -```python3 -pink_r -``` - -```python3 -piyg -``` - -```python3 -piyg_r -``` - -```python3 -plasma -``` - -```python3 -plasma_r -``` - -```python3 -prgn -``` - -```python3 -prgn_r -``` - -```python3 -prism -``` - -```python3 -prism_r -``` - -```python3 -pubu -``` - -```python3 -pubu_r -``` - -```python3 -pubugn -``` - -```python3 -pubugn_r -``` - -```python3 -puor -``` - -```python3 -puor_r -``` - -```python3 -purd -``` - -```python3 -purd_r -``` - -```python3 -purples -``` - -```python3 -purples_r -``` - -```python3 -rainbow -``` - -```python3 -rainbow_r -``` - -```python3 -rdbu -``` - -```python3 -rdbu_r -``` - -```python3 -rdgy -``` - -```python3 -rdgy_r -``` - -```python3 -rdpu -``` - -```python3 -rdpu_r -``` - -```python3 -rdylbu -``` - -```python3 -rdylbu_r -``` - -```python3 -rdylgn -``` - -```python3 -rdylgn_r -``` - -```python3 -reds -``` - -```python3 -reds_r -``` - -```python3 -rplumbo -``` - -```python3 -schwarzwald -``` - -```python3 -seismic -``` - -```python3 -seismic_r -``` - -```python3 -set1 -``` - -```python3 -set1_r -``` - -```python3 -set2 -``` - -```python3 -set2_r -``` - -```python3 -set3 -``` - -```python3 -set3_r -``` - -```python3 -spectral -``` - -```python3 -spectral_r -``` - -```python3 -spring -``` - -```python3 -spring_r -``` - -```python3 -summer -``` - -```python3 -summer_r -``` - -```python3 -tab10 -``` - -```python3 -tab10_r -``` - -```python3 -tab20 -``` - -```python3 -tab20_r -``` - -```python3 -tab20b -``` - -```python3 -tab20b_r -``` - -```python3 -tab20c -``` - -```python3 -tab20c_r -``` - -```python3 -terrain -``` - -```python3 -terrain_r -``` - -```python3 -twilight -``` - -```python3 -twilight_r -``` - -```python3 -twilight_shifted -``` - -```python3 -twilight_shifted_r -``` - -```python3 -value -``` - -```python3 -viridis -``` - -```python3 -viridis_r -``` - -```python3 -winter -``` - -```python3 -winter_r -``` - -```python3 -wistia -``` - -```python3 -wistia_r -``` - -```python3 -ylgn -``` - -```python3 -ylgn_r -``` - -```python3 -ylgnbu -``` - -```python3 -ylgnbu_r -``` - -```python3 -ylorbr -``` - -```python3 -ylorbr_r -``` - -```python3 -ylorrd -``` - -```python3 -ylorrd_r -``` - -### DatasetParams - -```python3 -class DatasetParams( - nodata: Union[str, int, float, NoneType] = Query(None), - unscale: Union[bool, NoneType] = Query(False), - resampling_method: titiler.core.dependencies.ResamplingName = Query(ResamplingName.nearest) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -nodata -``` - -```python3 -resampling_method -``` - -```python3 -unscale -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### DefaultDependency - -```python3 -class DefaultDependency( - -) -``` - -#### Descendants - -* titiler.core.dependencies.BidxParams -* titiler.core.dependencies.ExpressionParams -* titiler.core.dependencies.AssetsParams -* titiler.core.dependencies.AssetsBidxExprParams -* titiler.core.dependencies.BandsParams -* titiler.core.dependencies.ImageParams -* titiler.core.dependencies.DatasetParams -* titiler.core.dependencies.ImageRenderingParams -* titiler.core.dependencies.PostProcessParams - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### ExpressionParams - -```python3 -class ExpressionParams( - expression: Union[str, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Descendants - -* titiler.core.dependencies.BidxExprParams -* titiler.core.dependencies.BandsExprParamsOptional -* titiler.core.dependencies.BandsExprParams - -#### Class variables - -```python3 -expression -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### ImageParams - -```python3 -class ImageParams( - max_size: Union[int, NoneType] = Query(1024), - height: Union[int, NoneType] = Query(None), - width: Union[int, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -height -``` - -```python3 -max_size -``` - -```python3 -width -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### ImageRenderingParams - -```python3 -class ImageRenderingParams( - add_mask: bool = Query(True) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -add_mask -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### PostProcessParams - -```python3 -class PostProcessParams( - in_range: Union[List[str], NoneType] = Query(None), - color_formula: Union[str, NoneType] = Query(None) -) -``` - -#### Ancestors (in MRO) - -* titiler.core.dependencies.DefaultDependency - -#### Class variables - -```python3 -color_formula -``` - -```python3 -in_range -``` - -#### Methods - - -#### keys - -```python3 -def keys( - self -) -``` - - -Return Keys. - -### ResamplingName - -```python3 -class ResamplingName( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* enum.Enum - -#### Class variables - -```python3 -average -``` - -```python3 -bilinear -``` - -```python3 -cubic -``` - -```python3 -cubic_spline -``` - -```python3 -gauss -``` - -```python3 -lanczos -``` - -```python3 -max -``` - -```python3 -med -``` - -```python3 -min -``` - -```python3 -mode -``` - -```python3 -name -``` - -```python3 -nearest -``` - -```python3 -q1 -``` - -```python3 -q3 -``` - -```python3 -rms -``` - -```python3 -sum -``` - -```python3 -value -``` - -### TileMatrixSetName - -```python3 -class TileMatrixSetName( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* enum.Enum - -#### Class variables - -```python3 -CanadianNAD83_LCC -``` - -```python3 -EuropeanETRS89_LAEAQuad -``` - -```python3 -LINZAntarticaMapTilegrid -``` - -```python3 -NZTM2000 -``` - -```python3 -NZTM2000Quad -``` - -```python3 -UPSAntarcticWGS84Quad -``` - -```python3 -UPSArcticWGS84Quad -``` - -```python3 -UTM31WGS84Quad -``` - -```python3 -WebMercatorQuad -``` - -```python3 -WorldCRS84Quad -``` - -```python3 -WorldMercatorWGS84Quad -``` - -```python3 -name -``` - -```python3 -value -``` - -### WebMercatorTileMatrixSetName - -```python3 -class WebMercatorTileMatrixSetName( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* enum.Enum - -#### Class variables - -```python3 -WebMercatorQuad -``` - -```python3 -name -``` - -```python3 -value -``` \ No newline at end of file diff --git a/docs/src/api/titiler/core/errors.md b/docs/src/api/titiler/core/errors.md index 6b897df9c..16fe3296f 100644 --- a/docs/src/api/titiler/core/errors.md +++ b/docs/src/api/titiler/core/errors.md @@ -1,162 +1,2 @@ -# Module titiler.core.errors -Titiler error classes. - -None - -## Variables - -```python3 -DEFAULT_STATUS_CODES -``` - -```python3 -logger -``` - -## Functions - - -### add_exception_handlers - -```python3 -def add_exception_handlers( - app: fastapi.applications.FastAPI, - status_codes: Dict[Type[Exception], int] -) -> None -``` - - -Add exception handlers to the FastAPI app. - - -### exception_handler_factory - -```python3 -def exception_handler_factory( - status_code: int -) -> Callable -``` - - -Create a FastAPI exception handler from a status code. - -## Classes - -### BadRequestError - -```python3 -class BadRequestError( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* titiler.core.errors.TilerError -* builtins.Exception -* builtins.BaseException - -#### Class variables - -```python3 -args -``` - -#### Methods - - -#### with_traceback - -```python3 -def with_traceback( - ... -) -``` - - -Exception.with_traceback(tb) -- - -set self.__traceback__ to tb and return self. - -### TileNotFoundError - -```python3 -class TileNotFoundError( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* titiler.core.errors.TilerError -* builtins.Exception -* builtins.BaseException - -#### Class variables - -```python3 -args -``` - -#### Methods - - -#### with_traceback - -```python3 -def with_traceback( - ... -) -``` - - -Exception.with_traceback(tb) -- - -set self.__traceback__ to tb and return self. - -### TilerError - -```python3 -class TilerError( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.Exception -* builtins.BaseException - -#### Descendants - -* titiler.core.errors.TileNotFoundError -* titiler.core.errors.BadRequestError - -#### Class variables - -```python3 -args -``` - -#### Methods - - -#### with_traceback - -```python3 -def with_traceback( - ... -) -``` - - -Exception.with_traceback(tb) -- - -set self.__traceback__ to tb and return self. \ No newline at end of file +::: titiler.core.errors diff --git a/docs/src/api/titiler/core/factory.md b/docs/src/api/titiler/core/factory.md index 5f6737af5..80ff1f59d 100644 --- a/docs/src/api/titiler/core/factory.md +++ b/docs/src/api/titiler/core/factory.md @@ -1,1000 +1,2 @@ -# Module titiler.core.factory -TiTiler Router factories. - -None - -## Variables - -```python3 -img_endpoint_params -``` - -```python3 -templates -``` - -## Classes - -### BaseTilerFactory - -```python3 -class BaseTilerFactory( - reader: Type[rio_tiler.io.base.BaseReader], - reader_options: Dict = , - router: fastapi.routing.APIRouter = , - path_dependency: Callable[..., str] = , - dataset_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - layer_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - render_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - colormap_dependency: Callable[..., Union[Dict, NoneType]] = , - process_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - tms_dependency: Callable[..., morecantile.models.TileMatrixSet] = , - additional_dependency: Callable[..., Dict] = at 0x163f42310>, - router_prefix: str = '', - gdal_config: Dict = , - optional_headers: List[titiler.core.resources.enums.OptionalHeader] = -) -``` - -#### Descendants - -* titiler.core.factory.TilerFactory - -#### Class variables - -```python3 -dataset_dependency -``` - -```python3 -layer_dependency -``` - -```python3 -process_dependency -``` - -```python3 -render_dependency -``` - -```python3 -router_prefix -``` - -#### Methods - - -#### additional_dependency - -```python3 -def additional_dependency( - -) -``` - - - - -#### colormap_dependency - -```python3 -def colormap_dependency( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict, Sequence, NoneType] -``` - - -Colormap Dependency. - - -#### path_dependency - -```python3 -def path_dependency( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -Register Tiler Routes. - - -#### tms_dependency - -```python3 -def tms_dependency( - TileMatrixSetId: titiler.core.dependencies.WebMercatorTileMatrixSetName = Query(WebMercatorTileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - -### MultiBandTilerFactory - -```python3 -class MultiBandTilerFactory( - reader: Type[rio_tiler.io.base.MultiBandReader] = , - reader_options: Dict = , - router: fastapi.routing.APIRouter = , - path_dependency: Callable[..., str] = , - dataset_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - layer_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - render_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - colormap_dependency: Callable[..., Union[Dict, NoneType]] = , - process_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - tms_dependency: Callable[..., morecantile.models.TileMatrixSet] = , - additional_dependency: Callable[..., Dict] = at 0x163f42310>, - router_prefix: str = '', - gdal_config: Dict = , - optional_headers: List[titiler.core.resources.enums.OptionalHeader] = , - img_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - add_preview: bool = True, - add_part: bool = True, - add_statistics: bool = True, - bands_dependency: Type[titiler.core.dependencies.DefaultDependency] = -) -``` - -#### Ancestors (in MRO) - -* titiler.core.factory.TilerFactory -* titiler.core.factory.BaseTilerFactory - -#### Class variables - -```python3 -add_part -``` - -```python3 -add_preview -``` - -```python3 -add_statistics -``` - -```python3 -bands_dependency -``` - -```python3 -dataset_dependency -``` - -```python3 -img_dependency -``` - -```python3 -layer_dependency -``` - -```python3 -process_dependency -``` - -```python3 -reader -``` - -```python3 -render_dependency -``` - -```python3 -router_prefix -``` - -#### Methods - - -#### additional_dependency - -```python3 -def additional_dependency( - -) -``` - - - - -#### bounds - -```python3 -def bounds( - self -) -``` - - -Register /bounds endpoint. - - -#### colormap_dependency - -```python3 -def colormap_dependency( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict, Sequence, NoneType] -``` - - -Colormap Dependency. - - -#### info - -```python3 -def info( - self -) -``` - - -Register /info endpoint. - - -#### part - -```python3 -def part( - self -) -``` - - -Register /crop endpoint. - - -#### path_dependency - -```python3 -def path_dependency( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -#### point - -```python3 -def point( - self -) -``` - - -Register /point endpoints. - - -#### preview - -```python3 -def preview( - self -) -``` - - -Register /preview endpoint. - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -This Method register routes to the router. - -Because we wrap the endpoints in a class we cannot define the routes as -methods (because of the self argument). The HACK is to define routes inside -the class method and register them after the class initialisation. - - -#### statistics - -```python3 -def statistics( - self -) -``` - - -add statistics endpoints. - - -#### tile - -```python3 -def tile( - self -) -``` - - -Register /tiles endpoint. - - -#### tilejson - -```python3 -def tilejson( - self -) -``` - - -Register /tilejson.json endpoint. - - -#### tms_dependency - -```python3 -def tms_dependency( - TileMatrixSetId: titiler.core.dependencies.TileMatrixSetName = Query(TileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - - -#### wmts - -```python3 -def wmts( - self -) -``` - - -Register /wmts endpoint. - -### MultiBaseTilerFactory - -```python3 -class MultiBaseTilerFactory( - reader: Type[rio_tiler.io.base.MultiBaseReader] = , - reader_options: Dict = , - router: fastapi.routing.APIRouter = , - path_dependency: Callable[..., str] = , - dataset_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - layer_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - render_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - colormap_dependency: Callable[..., Union[Dict, NoneType]] = , - process_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - tms_dependency: Callable[..., morecantile.models.TileMatrixSet] = , - additional_dependency: Callable[..., Dict] = at 0x163f42310>, - router_prefix: str = '', - gdal_config: Dict = , - optional_headers: List[titiler.core.resources.enums.OptionalHeader] = , - img_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - add_preview: bool = True, - add_part: bool = True, - add_statistics: bool = True, - assets_dependency: Type[titiler.core.dependencies.DefaultDependency] = -) -``` - -#### Ancestors (in MRO) - -* titiler.core.factory.TilerFactory -* titiler.core.factory.BaseTilerFactory - -#### Class variables - -```python3 -add_part -``` - -```python3 -add_preview -``` - -```python3 -add_statistics -``` - -```python3 -assets_dependency -``` - -```python3 -dataset_dependency -``` - -```python3 -img_dependency -``` - -```python3 -layer_dependency -``` - -```python3 -process_dependency -``` - -```python3 -reader -``` - -```python3 -render_dependency -``` - -```python3 -router_prefix -``` - -#### Methods - - -#### additional_dependency - -```python3 -def additional_dependency( - -) -``` - - - - -#### bounds - -```python3 -def bounds( - self -) -``` - - -Register /bounds endpoint. - - -#### colormap_dependency - -```python3 -def colormap_dependency( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict, Sequence, NoneType] -``` - - -Colormap Dependency. - - -#### info - -```python3 -def info( - self -) -``` - - -Register /info endpoint. - - -#### part - -```python3 -def part( - self -) -``` - - -Register /crop endpoint. - - -#### path_dependency - -```python3 -def path_dependency( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -#### point - -```python3 -def point( - self -) -``` - - -Register /point endpoints. - - -#### preview - -```python3 -def preview( - self -) -``` - - -Register /preview endpoint. - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -This Method register routes to the router. - -Because we wrap the endpoints in a class we cannot define the routes as -methods (because of the self argument). The HACK is to define routes inside -the class method and register them after the class initialisation. - - -#### statistics - -```python3 -def statistics( - self -) -``` - - -Register /statistics endpoint. - - -#### tile - -```python3 -def tile( - self -) -``` - - -Register /tiles endpoint. - - -#### tilejson - -```python3 -def tilejson( - self -) -``` - - -Register /tilejson.json endpoint. - - -#### tms_dependency - -```python3 -def tms_dependency( - TileMatrixSetId: titiler.core.dependencies.TileMatrixSetName = Query(TileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - - -#### wmts - -```python3 -def wmts( - self -) -``` - - -Register /wmts endpoint. - -### TMSFactory - -```python3 -class TMSFactory( - supported_tms: Type[titiler.core.dependencies.TileMatrixSetName] = , - tms_dependency: Callable[..., morecantile.models.TileMatrixSet] = , - router: fastapi.routing.APIRouter = , - router_prefix: str = '' -) -``` - -#### Class variables - -```python3 -router_prefix -``` - -```python3 -supported_tms -``` - -#### Methods - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -Register TMS endpoint routes. - - -#### tms_dependency - -```python3 -def tms_dependency( - TileMatrixSetId: titiler.core.dependencies.TileMatrixSetName = Query(TileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - -### TilerFactory - -```python3 -class TilerFactory( - reader: Type[rio_tiler.io.base.BaseReader] = , - reader_options: Dict = , - router: fastapi.routing.APIRouter = , - path_dependency: Callable[..., str] = , - dataset_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - layer_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - render_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - colormap_dependency: Callable[..., Union[Dict, NoneType]] = , - process_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - tms_dependency: Callable[..., morecantile.models.TileMatrixSet] = , - additional_dependency: Callable[..., Dict] = at 0x163f42310>, - router_prefix: str = '', - gdal_config: Dict = , - optional_headers: List[titiler.core.resources.enums.OptionalHeader] = , - img_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - add_preview: bool = True, - add_part: bool = True, - add_statistics: bool = True -) -``` - -#### Ancestors (in MRO) - -* titiler.core.factory.BaseTilerFactory - -#### Descendants - -* titiler.core.factory.MultiBaseTilerFactory -* titiler.core.factory.MultiBandTilerFactory - -#### Class variables - -```python3 -add_part -``` - -```python3 -add_preview -``` - -```python3 -add_statistics -``` - -```python3 -dataset_dependency -``` - -```python3 -img_dependency -``` - -```python3 -layer_dependency -``` - -```python3 -process_dependency -``` - -```python3 -reader -``` - -```python3 -render_dependency -``` - -```python3 -router_prefix -``` - -#### Methods - - -#### additional_dependency - -```python3 -def additional_dependency( - -) -``` - - - - -#### bounds - -```python3 -def bounds( - self -) -``` - - -Register /bounds endpoint. - - -#### colormap_dependency - -```python3 -def colormap_dependency( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict, Sequence, NoneType] -``` - - -Colormap Dependency. - - -#### info - -```python3 -def info( - self -) -``` - - -Register /info endpoint. - - -#### part - -```python3 -def part( - self -) -``` - - -Register /crop endpoint. - - -#### path_dependency - -```python3 -def path_dependency( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -#### point - -```python3 -def point( - self -) -``` - - -Register /point endpoints. - - -#### preview - -```python3 -def preview( - self -) -``` - - -Register /preview endpoint. - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -This Method register routes to the router. - -Because we wrap the endpoints in a class we cannot define the routes as -methods (because of the self argument). The HACK is to define routes inside -the class method and register them after the class initialisation. - - -#### statistics - -```python3 -def statistics( - self -) -``` - - -add statistics endpoints. - - -#### tile - -```python3 -def tile( - self -) -``` - - -Register /tiles endpoint. - - -#### tilejson - -```python3 -def tilejson( - self -) -``` - - -Register /tilejson.json endpoint. - - -#### tms_dependency - -```python3 -def tms_dependency( - TileMatrixSetId: titiler.core.dependencies.TileMatrixSetName = Query(TileMatrixSetName.WebMercatorQuad) -) -> morecantile.models.TileMatrixSet -``` - - -TileMatrixSet Dependency. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - - -#### wmts - -```python3 -def wmts( - self -) -``` - - -Register /wmts endpoint. \ No newline at end of file +::: titiler.core.factory diff --git a/docs/src/api/titiler/core/middleware.md b/docs/src/api/titiler/core/middleware.md new file mode 100644 index 000000000..9c06108b0 --- /dev/null +++ b/docs/src/api/titiler/core/middleware.md @@ -0,0 +1,2 @@ + +::: titiler.core.middleware diff --git a/docs/src/api/titiler/core/models/OGC.md b/docs/src/api/titiler/core/models/OGC.md new file mode 100644 index 000000000..a0fcf3bdb --- /dev/null +++ b/docs/src/api/titiler/core/models/OGC.md @@ -0,0 +1,2 @@ + +::: titiler.core.models.OGC diff --git a/docs/src/api/titiler/core/models/mapbox.md b/docs/src/api/titiler/core/models/mapbox.md new file mode 100644 index 000000000..67b9ac6e3 --- /dev/null +++ b/docs/src/api/titiler/core/models/mapbox.md @@ -0,0 +1,2 @@ + +::: titiler.core.models.mapbox diff --git a/docs/src/api/titiler/core/models/responses.md b/docs/src/api/titiler/core/models/responses.md new file mode 100644 index 000000000..fc34ecb70 --- /dev/null +++ b/docs/src/api/titiler/core/models/responses.md @@ -0,0 +1,2 @@ + +::: titiler.core.models.responses diff --git a/docs/src/api/titiler/core/resources/enums.md b/docs/src/api/titiler/core/resources/enums.md index eb427e10f..1faab5f5d 100644 --- a/docs/src/api/titiler/core/resources/enums.md +++ b/docs/src/api/titiler/core/resources/enums.md @@ -1,239 +1,2 @@ -# Module titiler.core.resources.enums -Titiler.core Enums. - -None - -## Classes - -### ImageDriver - -```python3 -class ImageDriver( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.str -* enum.Enum - -#### Class variables - -```python3 -jp2 -``` - -```python3 -jpeg -``` - -```python3 -jpg -``` - -```python3 -name -``` - -```python3 -npy -``` - -```python3 -png -``` - -```python3 -pngraw -``` - -```python3 -tif -``` - -```python3 -value -``` - -```python3 -webp -``` - -### ImageType - -```python3 -class ImageType( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.str -* enum.Enum - -#### Class variables - -```python3 -jp2 -``` - -```python3 -jpeg -``` - -```python3 -jpg -``` - -```python3 -name -``` - -```python3 -npy -``` - -```python3 -png -``` - -```python3 -pngraw -``` - -```python3 -tif -``` - -```python3 -value -``` - -```python3 -webp -``` - -### MediaType - -```python3 -class MediaType( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.str -* enum.Enum - -#### Class variables - -```python3 -geojson -``` - -```python3 -html -``` - -```python3 -jp2 -``` - -```python3 -jpeg -``` - -```python3 -jpg -``` - -```python3 -json -``` - -```python3 -mvt -``` - -```python3 -name -``` - -```python3 -npy -``` - -```python3 -pbf -``` - -```python3 -png -``` - -```python3 -pngraw -``` - -```python3 -text -``` - -```python3 -tif -``` - -```python3 -value -``` - -```python3 -webp -``` - -```python3 -xml -``` - -### OptionalHeader - -```python3 -class OptionalHeader( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.str -* enum.Enum - -#### Class variables - -```python3 -name -``` - -```python3 -server_timing -``` - -```python3 -value -``` - -```python3 -x_assets -``` \ No newline at end of file +::: titiler.core.resources.enums diff --git a/docs/src/api/titiler/core/resources/responses.md b/docs/src/api/titiler/core/resources/responses.md new file mode 100644 index 000000000..e3ddaa410 --- /dev/null +++ b/docs/src/api/titiler/core/resources/responses.md @@ -0,0 +1,2 @@ + +::: titiler.core.resources.responses diff --git a/docs/src/api/titiler/core/routing.md b/docs/src/api/titiler/core/routing.md index 839b09283..89e89a435 100644 --- a/docs/src/api/titiler/core/routing.md +++ b/docs/src/api/titiler/core/routing.md @@ -1,26 +1,2 @@ -# Module titiler.core.routing -Custom routing classes. - -None - -## Functions - - -### apiroute_factory - -```python3 -def apiroute_factory( - env: Union[Dict, NoneType] = None -) -> Type[fastapi.routing.APIRoute] -``` - - -Create Custom API Route class with custom Env. - -Because we cannot create middleware for specific router we need to create -a custom APIRoute which add the `rasterio.Env(` block before the endpoint is -actually called. This way we set the env outside the threads and we make sure -that event multithreaded Reader will get the environment set. - -Note: This has been tested in python 3.6 and 3.7 only. \ No newline at end of file +::: titiler.core.routing diff --git a/docs/src/api/titiler/extensions/cogeo.md b/docs/src/api/titiler/extensions/cogeo.md new file mode 100644 index 000000000..7c24ed38a --- /dev/null +++ b/docs/src/api/titiler/extensions/cogeo.md @@ -0,0 +1 @@ +::: titiler.extensions.cogeo diff --git a/docs/src/api/titiler/extensions/stac.md b/docs/src/api/titiler/extensions/stac.md new file mode 100644 index 000000000..2f930fc6e --- /dev/null +++ b/docs/src/api/titiler/extensions/stac.md @@ -0,0 +1 @@ +::: titiler.extensions.stac diff --git a/docs/src/api/titiler/extensions/viewer.md b/docs/src/api/titiler/extensions/viewer.md new file mode 100644 index 000000000..e303253e3 --- /dev/null +++ b/docs/src/api/titiler/extensions/viewer.md @@ -0,0 +1 @@ +::: titiler.extensions.viewer diff --git a/docs/src/api/titiler/mosaic/errors.md b/docs/src/api/titiler/mosaic/errors.md index 2f3e10c48..e1ef9b87e 100644 --- a/docs/src/api/titiler/mosaic/errors.md +++ b/docs/src/api/titiler/mosaic/errors.md @@ -1,11 +1 @@ -# Module titiler.mosaic.errors - -Titiler mosaic errors. - -None - -## Variables - -```python3 -MOSAIC_STATUS_CODES -``` \ No newline at end of file +::: titiler.mosaic.errors diff --git a/docs/src/api/titiler/mosaic/factory.md b/docs/src/api/titiler/mosaic/factory.md index ad00ccce1..cf1ddcc8b 100644 --- a/docs/src/api/titiler/mosaic/factory.md +++ b/docs/src/api/titiler/mosaic/factory.md @@ -1,353 +1 @@ -# Module titiler.mosaic.factory - -TiTiler.mosaic Router factories. - -None - -## Variables - -```python3 -MAX_THREADS -``` - -```python3 -img_endpoint_params -``` - -```python3 -mosaic_tms -``` - -## Functions - - -### PixelSelectionParams - -```python3 -def PixelSelectionParams( - pixel_selection: titiler.mosaic.resources.enums.PixelSelectionMethod = Query(first) -) -> rio_tiler.mosaic.methods.base.MosaicMethodBase -``` - - -Returns the mosaic method used to combine datasets together. - -## Classes - -### MosaicTilerFactory - -```python3 -class MosaicTilerFactory( - reader: Type[cogeo_mosaic.backends.base.BaseBackend] = , - router: fastapi.routing.APIRouter = , - path_dependency: Callable[..., Any] = , - dataset_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - layer_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - render_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - colormap_dependency: Callable[..., Union[Dict[int, Tuple[int, int, int, int]], Sequence[Tuple[Tuple[Union[float, int], Union[float, int]], Tuple[int, int, int, int]]], NoneType]] = , - process_dependency: Callable[..., Optional[titiler.core.algorithm.base.BaseAlgorithm]] = .post_process at 0x146adc670>, - reader_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - environment_dependency: Callable[..., Dict] = at 0x146adc5e0>, - supported_tms: morecantile.defaults.TileMatrixSets = TileMatrixSets(tms={'WebMercatorQuad': }), - default_tms: str = 'WebMercatorQuad', - router_prefix: str = '', - optional_headers: List[titiler.core.resources.enums.OptionalHeader] = , - route_dependencies: List[Tuple[List[titiler.core.routing.EndpointScope], List[fastapi.params.Depends]]] = , - extensions: List[titiler.core.factory.FactoryExtension] = , - dataset_reader: Union[Type[rio_tiler.io.base.BaseReader], Type[rio_tiler.io.base.MultiBaseReader], Type[rio_tiler.io.base.MultiBandReader]] = , - backend_dependency: Type[titiler.core.dependencies.DefaultDependency] = , - pixel_selection_dependency: Callable[..., rio_tiler.mosaic.methods.base.MosaicMethodBase] = , - add_viewer: bool = True -) -``` - -#### Ancestors (in MRO) - -* titiler.core.factory.BaseTilerFactory - -#### Class variables - -```python3 -add_viewer -``` - -```python3 -backend_dependency -``` - -```python3 -dataset_dependency -``` - -```python3 -dataset_reader -``` - -```python3 -default_tms -``` - -```python3 -layer_dependency -``` - -```python3 -reader_dependency -``` - -```python3 -render_dependency -``` - -```python3 -router_prefix -``` - -```python3 -supported_tms -``` - -#### Methods - - -#### add_route_dependencies - -```python3 -def add_route_dependencies( - self, - *, - scopes: List[titiler.core.routing.EndpointScope], - dependencies=typing.List[fastapi.params.Depends] -) -``` - - -Add dependencies to routes. - -Allows a developer to add dependencies to a route after the route has been defined. - - -#### assets - -```python3 -def assets( - self -) -``` - - -Register /assets endpoint. - - -#### bounds - -```python3 -def bounds( - self -) -``` - - -Register /bounds endpoint. - - -#### colormap_dependency - -```python3 -def colormap_dependency( - colormap_name: titiler.core.dependencies.ColorMapName = Query(None), - colormap: str = Query(None) -) -> Union[Dict[int, Tuple[int, int, int, int]], Sequence[Tuple[Tuple[Union[float, int], Union[float, int]], Tuple[int, int, int, int]]], NoneType] -``` - - -Colormap Dependency. - - -#### environment_dependency - -```python3 -def environment_dependency( - -) -``` - - - - -#### info - -```python3 -def info( - self -) -``` - - -Register /info endpoint - - -#### map_viewer - -```python3 -def map_viewer( - self -) -``` - - -Register /map endpoint. - - -#### path_dependency - -```python3 -def path_dependency( - url: str = Query(Ellipsis) -) -> str -``` - - -Create dataset path from args - - -#### pixel_selection_dependency - -```python3 -def pixel_selection_dependency( - pixel_selection: titiler.mosaic.resources.enums.PixelSelectionMethod = Query(first) -) -> rio_tiler.mosaic.methods.base.MosaicMethodBase -``` - - -Returns the mosaic method used to combine datasets together. - - -#### point - -```python3 -def point( - self -) -``` - - -Register /point endpoint. - - -#### process_dependency - -```python3 -def process_dependency( - algorithm: Literal['hillshade', 'contours', 'normalizedIndex', 'terrarium', 'terrainrgb'] = Query(None), - algorithm_params: str = Query(None) -) -> Optional[titiler.core.algorithm.base.BaseAlgorithm] -``` - - -Data Post-Processing options. - - -#### read - -```python3 -def read( - self -) -``` - - -Register / (Get) Read endpoint. - - -#### reader - -```python3 -def reader( - input: str, - *args: Any, - **kwargs: Any -) -> cogeo_mosaic.backends.base.BaseBackend -``` - - -Select mosaic backend for input. - - -#### register_routes - -```python3 -def register_routes( - self -) -``` - - -This Method register routes to the router. - -Because we wrap the endpoints in a class we cannot define the routes as -methods (because of the self argument). The HACK is to define routes inside -the class method and register them after the class initialization. - - -#### tile - -```python3 -def tile( - self -) -``` - - -Register /tiles endpoints. - - -#### tilejson - -```python3 -def tilejson( - self -) -``` - - -Add tilejson endpoint. - - -#### url_for - -```python3 -def url_for( - self, - request: starlette.requests.Request, - name: str, - **path_params: Any -) -> str -``` - - -Return full url (with prefix) for a specific endpoint. - - -#### validate - -```python3 -def validate( - self -) -``` - - -Register /validate endpoint. - - -#### wmts - -```python3 -def wmts( - self -) -``` - - -Add wmts endpoint. \ No newline at end of file +::: titiler.mosaic.factory diff --git a/docs/src/api/titiler/mosaic/models/responses.md b/docs/src/api/titiler/mosaic/models/responses.md new file mode 100644 index 000000000..2b32fc0c6 --- /dev/null +++ b/docs/src/api/titiler/mosaic/models/responses.md @@ -0,0 +1 @@ +::: titiler.mosaic.models.responses diff --git a/docs/src/api/titiler/mosaic/resources/enums.md b/docs/src/api/titiler/mosaic/resources/enums.md deleted file mode 100644 index d25733aeb..000000000 --- a/docs/src/api/titiler/mosaic/resources/enums.md +++ /dev/null @@ -1,56 +0,0 @@ -# Module titiler.mosaic.resources.enums - -Titiler.mosaic Enums. - -None - -## Classes - -### PixelSelectionMethod - -```python3 -class PixelSelectionMethod( - /, - *args, - **kwargs -) -``` - -#### Ancestors (in MRO) - -* builtins.str -* enum.Enum - -#### Class variables - -```python3 -first -``` - -```python3 -highest -``` - -```python3 -lowest -``` - -```python3 -mean -``` - -```python3 -median -``` - -```python3 -name -``` - -```python3 -stdev -``` - -```python3 -value -``` \ No newline at end of file diff --git a/docs/src/benchmark.html b/docs/src/benchmark.html new file mode 100644 index 000000000..9c1d0cb5c --- /dev/null +++ b/docs/src/benchmark.html @@ -0,0 +1,292 @@ + + + + + + + Benchmarks + + + + +
+ + + + + diff --git a/docs/src/endpoints/algorithms.md b/docs/src/endpoints/algorithms.md new file mode 100644 index 000000000..58e375eee --- /dev/null +++ b/docs/src/endpoints/algorithms.md @@ -0,0 +1,116 @@ +In addition to the `/cog`, `/stac` and `/mosaicjson` endpoints, the `titiler.application` package FastAPI application commes with additional metadata endpoints. + +# Algorithms + +## API + +| Method | URL | Output | Description +| ------ | ---------------------------- |---------------- |-------------- +| `GET` | `/algorithms` | JSON | retrieve the list of available Algorithms +| `GET` | `/algorithms/{algorithmId}` | JSON | retrieve the metadata of the specified algorithm. + +## Description + + +### List Algorithm + +`:endpoint:/algorithm` - Get the list of supported TileMatrixSet + +```bash +$ curl https://myendpoint/algorithms | jq + +{ + "hillshade": { + "title": "Hillshade", + "description": "Create hillshade from DEM dataset.", + "inputs": { + "nbands": 1 + }, + "outputs": { + "nbands": 1, + "dtype": "uint8", + "min": null, + "max": null + }, + "parameters": { + "azimuth": { + "default": 90, + "maximum": 360, + "minimum": 0, + "title": "Azimuth", + "type": "integer" + }, + "angle_altitude": { + "default": 90.0, + "maximum": 90.0, + "minimum": -90.0, + "title": "Angle Altitude", + "type": "number" + }, + "buffer": { + "default": 3, + "maximum": 99, + "minimum": 0, + "title": "Buffer", + "type": "integer" + } + } + }, + ... +} +``` + +### Get Algorithm info + +`:endpoint:/algorithms/{algorithmId}` - Get the algorithm metadata + +- PathParams: + - **algorithmId**: algorithm name + +```bash +$ curl http://127.0.0.1:8000/algorithms/contours | jq + +{ + "title": "Contours", + "description": "Create contours from DEM dataset.", + "inputs": { + "nbands": 1 + }, + "outputs": { + "nbands": 3, + "dtype": "uint8", + "min": null, + "max": null + }, + "parameters": { + "increment": { + "default": 35, + "maximum": 999, + "minimum": 0, + "title": "Increment", + "type": "integer" + }, + "thickness": { + "default": 1, + "maximum": 10, + "minimum": 0, + "title": "Thickness", + "type": "integer" + }, + "minz": { + "default": -12000, + "maximum": 99999, + "minimum": -99999, + "title": "Minz", + "type": "integer" + }, + "maxz": { + "default": 8000, + "maximum": 99999, + "minimum": -99999, + "title": "Maxz", + "type": "integer" + } + } +} +``` diff --git a/docs/src/endpoints/cog.md b/docs/src/endpoints/cog.md index a1add20b0..0209fdd90 100644 --- a/docs/src/endpoints/cog.md +++ b/docs/src/endpoints/cog.md @@ -14,14 +14,16 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog | `GET` | `/cog/info.geojson` | GeoJSON | return dataset's basic info as a GeoJSON feature | `GET` | `/cog/statistics` | JSON | return dataset's statistics | `POST` | `/cog/statistics` | GeoJSON | return dataset's statistics for a GeoJSON -| `GET` | `/cog/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset -| `GET` | `/cog[/{TileMatrixSetId}]/tilejson.json` | JSON | return a Mapbox TileJSON document -| `GET` | `/cog[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/cog/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/cog/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset +| `GET` | `/cog/{tileMatrixSetId}/map` | HTML | simple map viewer +| `GET` | `/cog/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document +| `GET` | `/cog/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities | `GET` | `/cog/point/{lon},{lat}` | JSON | return pixel values from a dataset | `GET` | `/cog/preview[.{format}]` | image/bin | create a preview image from a dataset -| `GET` | `/cog/crop/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset -| `POST` | `/cog/crop[/{width}x{height}][].{format}]` | image/bin | create an image from a GeoJSON feature -| `GET` | `/cog[/{TileMatrixSetId}]/map` | HTML | simple map viewer +| `GET` | `/cog/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset +| `POST` | `/cog/feature[/{width}x{height}][].{format}]` | image/bin | create an image from a GeoJSON feature | `GET` | `/cog/validate` | JSON | validate a COG and return dataset info (from `titiler.extensions.cogValidateExtension`) | `GET` | `/cog/viewer` | HTML | demo webpage (from `titiler.extensions.cogViewerExtension`) | `GET` | `/cog/stac` | GeoJSON | create STAC Items from a dataset (from `titiler.extensions.stacExtension`) @@ -30,10 +32,10 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog ### Tiles -`:endpoint:/cog/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` +`:endpoint:/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` - PathParams: - - **TileMatrixSetId** (str): TileMatrixSet name, default is `WebMercatorQuad`. **Optional** + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - **z** (int): TMS tile's zoom level. - **x** (int): TMS tile's column. - **y** (int): TMS tile's row. @@ -43,23 +45,25 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. Example: -- `https://myendpoint/cog/tiles/1/2/3?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/tiles/1/2/3.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2` +- `https://myendpoint/cog/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2` - `https://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3@2x.png?url=https://somewhere.com/mycog.tif` - `https://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` @@ -73,13 +77,14 @@ Example: - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). - **max_size** (int): Max image size, default is 1024. - **height** (int): Force output image height. - **width** (int): Force output image width. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -97,11 +102,11 @@ Example: - `https://myendpoint/cog/preview.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2` - `https://myendpoint/cog/preview?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` -### Crop / Part +### BBOX/Feature -`:endpoint:/cog/crop/{minx},{miny},{maxx},{maxy}.{format}` +`:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}` -`:endpoint:/cog/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` +`:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` - PathParams: - **minx,miny,maxx,maxy** (str): Comma (',') delimited bounding box in WGS84. @@ -112,12 +117,14 @@ Example: - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). - - **coord-crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size, default is 1024. + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). + - **coord_crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -131,11 +138,11 @@ Example: Example: -- `https://myendpoint/cog/crop/0,0,10,10.png?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/crop/0,0,10,10.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` +- `https://myendpoint/cog/bbox/0,0,10,10.png?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/bbox/0,0,10,10.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` -`:endpoint:/cog/crop[/{width}x{height}][].{format}] - [POST]` +`:endpoint:/cog/feature[/{width}x{height}][].{format}] - [POST]` - Body: - **feature** (JSON): A valid GeoJSON feature (Polygon or MultiPolygon) @@ -148,12 +155,14 @@ Example: - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). - - **coord-crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size, default is 1024. + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). + - **coord_crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -167,9 +176,9 @@ Example: Example: -- `https://myendpoint/cog/crop?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/crop.png?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/crop/100x100.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` +- `https://myendpoint/cog/feature?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/feature.png?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/feature/100x100.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie` Note: if `height` and `width` are provided `max_size` will be ignored. @@ -183,11 +192,12 @@ Note: if `height` and `width` are provided `max_size` will be ignored. - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). - - **coord-crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). + - **coord_crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. Example: @@ -196,10 +206,10 @@ Example: ### TilesJSON -`:endpoint:/cog[/{TileMatrixSetId}]/tilejson.json` tileJSON document +`:endpoint:/cog/{tileMatrixSetId}/tilejson.json` tileJSON document - PathParams: - - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. **Optional** + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** @@ -208,32 +218,34 @@ Example: - **minzoom** (int): Overwrite default minzoom. - **maxzoom** (int): Overwrite default maxzoom. - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. Example: -- `https://myendpoint/cog/tilejson.json?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/tilejson.json?url=https://somewhere.com/mycog.tif&tile_format=png` +- `https://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tif&tile_format=png` - `https://myendpoint/cog/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3` ### Map -`:endpoint:/cog[/{TileMatrixSetId}]/map` Simple viewer +`:endpoint:/cog/{tileMatrixSetId}/map` Simple viewer - PathParams: - - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. **Optional** + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** @@ -242,24 +254,26 @@ Example: - **minzoom** (int): Overwrite default minzoom. - **maxzoom** (int): Overwrite default maxzoom. - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. Example: -- `https://myendpoint/cog/map?url=https://somewhere.com/mycog.tif` -- `https://myendpoint/cog/map?url=https://somewhere.com/mycog.tif&tile_format=png` -- `https://myendpoint/cog/WebMercatorQuad/map?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3` +- `https://myendpoint/cog/WebMercatorQuad/map?url=https://somewhere.com/mycog.tif` +- `https://myendpoint/cog/WebMercatorQuad/map?url=https://somewhere.com/mycog.tif&tile_format=png` +- `https://myendpoint/cog/WorldCRS84Quad/map?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3` ### Bounds @@ -268,6 +282,7 @@ Example: - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** + - **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`. Example: @@ -277,17 +292,23 @@ Example: `:endpoint:/cog/info` general raster info +- QueryParams: + - **url** (str): Cloud Optimized GeoTIFF URL. **Required** + +Example: + +- `https://myendpoint/cog/info?url=https://somewhere.com/mycog.tif` + `:endpoint:/cog/info.geojson` general raster info as a GeoJSON feature - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** + - **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`. Example: -- `https://myendpoint/cog/info?url=https://somewhere.com/mycog.tif` - `https://myendpoint/cog/info.geojson?url=https://somewhere.com/mycog.tif` - ### Statistics Advanced raster statistics @@ -297,13 +318,15 @@ Advanced raster statistics - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). - **max_size** (int): Max image size from which to calculate statistics, default is 1024. - **height** (int): Force image height from which to calculate statistics. - **width** (int): Force image width from which to calculate statistics. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **algorithm** (str): Custom algorithm name (e.g `hillshade`). + - **algorithm_params** (str): JSON encoded algorithm parameters. - **categorical** (bool): Return statistics for categorical dataset, default is false. - **c** (array[float]): Pixels values for categories. - **p** (array[int]): Percentile values. @@ -322,14 +345,18 @@ Example: - QueryParams: - **url** (str): Cloud Optimized GeoTIFF URL. **Required** - **bidx** (array[int]): Dataset band indexes (e.g `bidx=1`, `bidx=1&bidx=2&bidx=3`). - - **expression** (str): rio-tiler's band math expression (e.g B1/B2). - - **coord-crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size from which to calculate statistics, default is 1024. + - **expression** (str): rio-tiler's band math expression (e.g `expression=b1/b2`). + - **coord_crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size from which to calculate statistics. - **height** (int): Force image height from which to calculate statistics. - **width** (int): Force image width from which to calculate statistics. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. + - **algorithm** (str): Custom algorithm name (e.g `hillshade`). + - **algorithm_params** (str): JSON encoded algorithm parameters. - **categorical** (bool): Return statistics for categorical dataset, default is false. - **c** (array[float]): Pixels values for categories. - **p** (array[int]): Percentile values. diff --git a/docs/src/endpoints/colormaps.md b/docs/src/endpoints/colormaps.md new file mode 100644 index 000000000..8ad4f10db --- /dev/null +++ b/docs/src/endpoints/colormaps.md @@ -0,0 +1,96 @@ +In addition to the `/cog`, `/stac` and `/mosaicjson` endpoints, the `titiler.application` package FastAPI application commes with additional metadata endpoints. + +# Algorithms + +## API + +| Method | URL | Output | Description +| ------ | ---------------------------- |--------|-------------- +| `GET` | `/colorMaps` | JSON | retrieve the list of available colorMaps +| `GET` | `/colorMaps/{colorMapId}` | JSON | retrieve the metadata or image of the specified colorMap. + +## Description + + +### List colormaps + +`:endpoint:/colorMaps` - Get the list of supported ColorMaps + +```bash +$ curl https://myendpoint/colorMaps | jq + +{ + "colorMaps": [ + "dense_r", + "delta", + ... + ], + "links": [ + { + "href": "http://myendpoint/colorMaps", + "rel": "self", + "type": "application/json", + "title": "List of available colormaps" + }, + { + "href": "http://myendpoint/colorMaps/{colorMapId}", + "rel": "data", + "type": "application/json", + "templated": true, + "title": "Retrieve colormap metadata" + }, + { + "href": "http://myendpoint/colorMaps/{colorMapId}?format=png", + "rel": "data", + "type": "image/png", + "templated": true, + "title": "Retrieve colormap as image" + } + ] +} +``` + +### Get ColorMap metadata or as image + +`:endpoint:/colorMaps/{colorMapId}` - Get the ColorMap metadata or image + +- PathParams: + - **colorMapId**: colormap name + +- QueryParams: + - **format** (str): output image format (PNG/JPEG...). Defaults to JSON output. + - **orientation** (["vertical", "horizontal"]): image orientation. Defaults to `horizontal`. + - **height** (int): output image height. Default to 20px for horizontal or 256px for vertical. + - **width** (int): output image width. Defaults to 256px for horizontal or 20px for vertical. + +```bash +$ curl http://myendpoint/colorMaps/viridis | jq + +{ + "0": [ + 68, + 1, + 84, + 255 + ], + ... + "255": [ + 253, + 231, + 36, + 255 + ] +} +``` + +``` +curl http://myendpoint/colorMaps/viridis?format=png +``` + +``` +curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical +``` + +``` +curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical&width=100&height=1000 +``` diff --git a/docs/src/endpoints/mosaic.md b/docs/src/endpoints/mosaic.md index 47537129c..53249f781 100644 --- a/docs/src/endpoints/mosaic.md +++ b/docs/src/endpoints/mosaic.md @@ -13,14 +13,16 @@ Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The `mos | `GET` | `/mosaicjson/bounds` | JSON | return mosaic's bounds | `GET` | `/mosaicjson/info` | JSON | return mosaic's basic info | `GET` | `/mosaicjson/info.geojson` | GeoJSON | return mosaic's basic info as a GeoJSON feature -| `GET` | `/mosaicjson/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from mosaic assets -| `GET` | `/mosaicjson[/{TileMatrixSetId}]/tilejson.json` | JSON | return a Mapbox TileJSON document -| `GET` | `/mosaicjson[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/mosaicjson/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from mosaic assets +| `GET` | `/mosaicjson/{tileMatrixSetId}/map` | HTML | simple map viewer +| `GET` | `/mosaicjson/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document +| `GET` | `/mosaicjson/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities | `GET` | `/mosaicjson/point/{lon},{lat}` | JSON | return pixel value from a mosaic assets | `GET` | `/mosaicjson/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile | `GET` | `/mosaicjson/{lon},{lat}/assets` | JSON | return list of assets intersecting a point | `GET` | `/mosaicjson/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box -| `GET` | `/mosaicjson[/{TileMatrixSetId}]/map` | HTML | simple map viewer ## Description diff --git a/docs/src/endpoints/stac.md b/docs/src/endpoints/stac.md index b70a9700d..42bc64732 100644 --- a/docs/src/endpoints/stac.md +++ b/docs/src/endpoints/stac.md @@ -16,24 +16,26 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but | `GET` | `/stac/asset_statistics` | JSON | return per asset statistics | `GET` | `/stac/statistics` | JSON | return asset's statistics | `POST` | `/stac/statistics` | GeoJSON | return asset's statistics for a GeoJSON -| `GET` | `/stac/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets -| `GET` | `/stac[/{TileMatrixSetId}]/tilejson.json` | JSON | return a Mapbox TileJSON document -| `GET` | `/stac[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/stac/tiles` | JSON | List of OGC Tilesets available +| `GET` | `/stac/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata +| `GET` | `/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets +| `GET` | `/stac/{tileMatrixSetId}/map` | HTML | simple map viewer +| `GET` | `/stac/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document +| `GET` | `/stac/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities | `GET` | `/stac/point/{lon},{lat}` | JSON | return pixel value from assets | `GET` | `/stac/preview[.{format}]` | image/bin | create a preview image from assets -| `GET` | `/stac/crop/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets -| `POST` | `/stac/crop[/{width}x{height}][].{format}]` | image/bin | create an image from a geojson covering the assets -| `GET` | `/stac[/{TileMatrixSetId}]/map` | HTML | simple map viewer +| `GET` | `/stac/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets +| `POST` | `/stac/feature[/{width}x{height}][].{format}]` | image/bin | create an image from a geojson covering the assets | `GET` | `/stac/viewer` | HTML | demo webpage (from `titiler.extensions.stacViewerExtension`) ## Description ### Tiles -`:endpoint:/stac/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` +`:endpoint:/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` - PathParams: - - **TileMatrixSetId** (str): TileMatrixSet name, default is `WebMercatorQuad`. **Optional** + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - **z** (int): TMS tile's zoom level. - **x** (int): TMS tile's column. - **y** (int): TMS tile's row. @@ -48,13 +50,15 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but - **asset_bidx** (array[str]): Per asset band math expression (e.g `Asset1|1,2,3`). - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. @@ -63,8 +67,8 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but Example: -- `https://myendpoint/stac/tiles/1/2/3?url=https://somewhere.com/item.json&assets=B01&assets=B00` -- `https://myendpoint/stac/tiles/1/2/3.jpg?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/item.json&assets=B01&assets=B00` +- `https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/item.json&assets=B01` - `https://myendpoint/stac/tiles/WorldCRS84Quad/1/2/3@2x.png?url=https://somewhere.com/item.json&assets=B01` - `https://myendpoint/stac/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/item.json&expression=B01/B02&rescale=0,1000&colormap_name=cfastie` @@ -84,9 +88,11 @@ Example: - **max_size** (int): Max image size, default is 1024. - **height** (int): Force output image height. - **width** (int): Force output image width. + - **dst_crs** (str): Output Coordinate Reference System. Default to dataset's CRS. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -106,11 +112,11 @@ Example: - `https://myendpoint/stac/preview.jpg?url=https://somewhere.com/item.json&assets=B01` - `https://myendpoint/stac/preview?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie` -### Crop / Part +### BBOX/Feature -`:endpoint:/stac/crop/{minx},{miny},{maxx},{maxy}.{format}` +`:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}` -`:endpoint:/stac/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` +`:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` - PathParams: - **minx,miny,maxx,maxy** (str): Comma (',') delimited bounding box in WGS84. @@ -124,11 +130,13 @@ Example: - **expression** (str): rio-tiler's math expression with asset names (e.g `Asset1_b1/Asset2_b1`). - **asset_as_band** (bool): tell rio-tiler that each asset is a 1 band dataset, so expression `Asset1/Asset2` can be passed. - **asset_bidx** (array[str]): Per asset band math expression (e.g `Asset1|1,2,3`). - - **coord-crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size, default is 1024. + - **coord_crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -144,10 +152,10 @@ Example: Example: -- `https://myendpoint/stac/crop/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01` -- `https://myendpoint/stac/crop/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie` +- `https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie` -`:endpoint:/stac/crop[/{width}x{height}][].{format}] - [POST]` +`:endpoint:/stac/feature[/{width}x{height}][].{format}] - [POST]` - Body: - **feature** (JSON): A valid GeoJSON feature (Polygon or MultiPolygon) @@ -163,11 +171,13 @@ Example: - **expression** (str): rio-tiler's math expression with asset names (e.g `Asset1_b1/Asset2_b1`). - **asset_as_band** (bool): tell rio-tiler that each asset is a 1 band dataset, so expression `Asset1/Asset2` can be passed. - **asset_bidx** (array[str]): Per asset band math expression (e.g `Asset1|1,2,3`). - - **coord-crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size, default is 1024. + - **coord_crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. @@ -183,9 +193,9 @@ Example: Example: -- `https://myendpoint/stac/crop?url=https://somewhere.com/item.json&assets=B01` -- `https://myendpoint/stac/crop.png?url=https://somewhere.com/item.json&assets=B01` -- `https://myendpoint/stac/crop/100x100.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie` +- `https://myendpoint/stac/feature?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/feature.png?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/feature/100x100.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie` ### Point @@ -200,10 +210,10 @@ Example: - **expression** (str): rio-tiler's math expression with asset names (e.g `Asset1_b1/Asset2_b1`). - **asset_as_band** (bool): tell rio-tiler that each asset is a 1 band dataset, so expression `Asset1/Asset2` can be passed. - **asset_bidx** (array[str]): Per asset band math expression (e.g `Asset1|1,2,3`). - - **coord-crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. + - **coord_crs** (str): Coordinate Reference System of the input coordinates. Default to `epsg:4326`. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. !!! important **assets** OR **expression** is required @@ -214,10 +224,10 @@ Example: ### TilesJSON -`:endpoint:/stac[/{TileMatrixSetId}]/tilejson.json` tileJSON document +`:endpoint:/stac/{tileMatrixSetId}/tilejson.json` tileJSON document - PathParams: - - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - QueryParams: - **url** (str): STAC Item URL. **Required** @@ -231,13 +241,15 @@ Example: - **maxzoom** (int): Overwrite default maxzoom. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. @@ -246,16 +258,16 @@ Example: Example: -- `https://myendpoint/stac/tilejson.json?url=https://somewhere.com/item.json&assets=B01` -- `https://myendpoint/stac/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png` +- `https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png` - `https://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tile_scale=2&expression=B01/B02` ### Map -`:endpoint:/stac[/{TileMatrixSetId}]/map` Simple viewer +`:endpoint:/stac/{tileMatrixSetId}/map` Simple viewer - PathParams: - - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. **Optional** + - **tileMatrixSetId** (str): TileMatrixSet name (e.g `WebMercatorQuad`) - QueryParams: - **url** (str): STAC Item URL. **Required** @@ -269,13 +281,15 @@ Example: - **maxzoom** (int): Overwrite default maxzoom. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. - **rescale** (array[str]): Comma (',') delimited Min,Max range (e.g `rescale=0,1000`, `rescale=0,1000&rescale=0,3000&rescale=0,2000`). - **color_formula** (str): rio-color formula. - **colormap** (str): JSON encoded custom Colormap. - **colormap_name** (str): rio-tiler color map name. - **return_mask** (bool): Add mask to the output data. Default is True. - - **buffer** (float): Add buffer on each side of the tile (e.g 0.5 = 257x257, 1.0 = 258x258). + - **buffer** (float): Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258). + - **padding** (int): Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`. - **algorithm** (str): Custom algorithm name (e.g `hillshade`). - **algorithm_params** (str): JSON encoded algorithm parameters. @@ -284,8 +298,8 @@ Example: Example: -- `https://myendpoint/stac/tilejson.json?url=https://somewhere.com/item.json&assets=B01` -- `https://myendpoint/stac/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png` +- `https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01` +- `https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png` - `https://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tile_scale=2&expression=B01/B02` @@ -295,6 +309,7 @@ Example: - QueryParams: - **url** (str): STAC Item URL. **Required** + - **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`. Example: @@ -318,11 +333,7 @@ Example: - QueryParams: - **url** (str): STAC Item URL. **Required** - **assets** (array[str]): asset names. Default to all available assets. - -Example: - -- `https://myendpoint/stac/info.geojson?url=https://somewhere.com/item.json&assets=B01` - + - **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`. `:endpoint:/stac/assets` - Return the list of available assets @@ -344,7 +355,7 @@ Example: - **width** (int): Force image width from which to calculate statistics. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. - **categorical** (bool): Return statistics for categorical dataset, default is false. - **c** (array[float]): Pixels values for categories. - **p** (array[int]): Percentile values. @@ -369,7 +380,9 @@ Example: - **width** (int): Force image width from which to calculate statistics. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **algorithm** (str): Custom algorithm name (e.g `hillshade`). + - **algorithm_params** (str): JSON encoded algorithm parameters. - **categorical** (bool): Return statistics for categorical dataset, default is false. - **c** (array[float]): Pixels values for categories. - **p** (array[int]): Percentile values. @@ -392,13 +405,17 @@ Example: - **expression** (str): rio-tiler's math expression with asset names (e.g `Asset1_b1/Asset2_b1`). - **asset_as_band** (bool): tell rio-tiler that each asset is a 1 band dataset, so expression `Asset1/Asset2` can be passed. - **asset_bidx** (array[str]): Per asset band math expression (e.g `Asset1|1,2,3`). - - **coord-crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. - - **max_size** (int): Max image size from which to calculate statistics, default is 1024. + - **coord_crs** (str): Coordinate Reference System of the input geometry coordinates. Default to `epsg:4326`. + - **dst_crs** (str): Output Coordinate Reference System. Default to `coord_crs`. + - **max_size** (int): Max image size from which to calculate statistics. - **height** (int): Force image height from which to calculate statistics. - **width** (int): Force image width from which to calculate statistics. - **nodata** (str, int, float): Overwrite internal Nodata value. - **unscale** (bool): Apply dataset internal Scale/Offset. - - **resampling** (str): rasterio resampling method. Default is `nearest`. + - **resampling** (str): RasterIO resampling algorithm. Defaults to `nearest`. + - **reproject** (str): WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`. + - **algorithm** (str): Custom algorithm name (e.g `hillshade`). + - **algorithm_params** (str): JSON encoded algorithm parameters. - **categorical** (bool): Return statistics for categorical dataset, default is false. - **c** (array[float]): Pixels values for categories. - **p** (array[int]): Percentile values. diff --git a/docs/src/endpoints/tms.md b/docs/src/endpoints/tms.md index 41cd57516..3a634b682 100644 --- a/docs/src/endpoints/tms.md +++ b/docs/src/endpoints/tms.md @@ -1,24 +1,14 @@ -The `titiler.application` package comes with a full FastAPI application with COG, STAC and MosaicJSON supports. -# TileMatrixSets - -The `tms` router extend the default `titiler.core.factory.TMSFactory`, adding some custom TileMatrixSets. - -```python -from fastapi import FastAPI -from titiler.application.routers.tms import tms - -app = FastAPI() -app.include_router(tms.router, tags=["TileMatrixSets"]) -``` +In addition to the `/cog`, `/stac` and `/mosaicjson` endpoints, the `titiler.application` package FastAPI application comes with additional metadata endpoints. +# TileMatrixSets ## API | Method | URL | Output | Description | ------ | ----------------------------------- |---------- |-------------- | `GET` | `/tileMatrixSets` | JSON | return the list of supported TileMatrixSet -| `GET` | `/tileMatrixSets/{TileMatrixSetId}` | JSON | return the TileMatrixSet JSON document +| `GET` | `/tileMatrixSets/{tileMatrixSetId}` | JSON | return the TileMatrixSet JSON document ## Description @@ -50,10 +40,10 @@ $ curl https://myendpoint/tileMatrixSets | jq ### Get TMS info -`:endpoint:/tileMatrixSets/{TileMatrixSetId}` - Get the TileMatrixSet JSON document +`:endpoint:/tileMatrixSets/{tileMatrixSetId}` - Get the TileMatrixSet JSON document - PathParams: - - **TileMatrixSetId**: TileMatrixSet name + - **tileMatrixSetId**: TileMatrixSet name ```bash $ curl http://127.0.0.1:8000/tileMatrixSets/WebMercatorQuad | jq diff --git a/docs/src/examples/code/create_gdal_wmts_extension.md b/docs/src/examples/code/create_gdal_wmts_extension.md index 4a687c84d..396127538 100644 --- a/docs/src/examples/code/create_gdal_wmts_extension.md +++ b/docs/src/examples/code/create_gdal_wmts_extension.md @@ -29,19 +29,7 @@ class gdalwmtsExtension(FactoryExtension): """Register endpoint to the tiler factory.""" @factory.router.get( - "/wmts.xml", - response_class=XMLResponse, - responses={ - 200: { - "description": "GDAL WMTS service description XML file", - "content": { - "application/xml": {}, - }, - }, - }, - ) - @factory.router.get( - "/{TileMatrixSetId}/wmts.xml", + "/{tileMatrixSetId}/wmts.xml", response_class=XMLResponse, responses={ 200: { @@ -54,9 +42,8 @@ class gdalwmtsExtension(FactoryExtension): ) def gdal_wmts( request: Request, - TileMatrixSetId: Literal[tuple(factory.supported_tms.list())] = Query( # type: ignore - factory.default_tms, - description=f"TileMatrixSet Name (default: '{factory.default_tms}')", + tileMatrixSetId: Literal[tuple(factory.supported_tms.list())] = Path( # type: ignore + description="TileMatrixSet Name", ), url: str = Depends(factory.path_dependency), # noqa bandscount: int = Query( @@ -74,7 +61,7 @@ class gdalwmtsExtension(FactoryExtension): ): """Return a GDAL WMTS Service description.""" route_params = { - "TileMatrixSetId": TileMatrixSetId, + "tileMatrixSetId": tileMatrixSetId, } wmts_url = factory.url_for(request, "wmts", **route_params) @@ -151,7 +138,7 @@ add_exception_handlers(app, DEFAULT_STATUS_CODES) ```python from rio_tiler.io import Reader -with Reader("http://0.0.0.0/wmts.xml?url=file.tif&bidx=1&bandscount=1&datatype=float32&tile_format=tif") as src: +with Reader("http://0.0.0.0/WebMercatorQuad/wmts.xml?url=file.tif&bidx=1&bandscount=1&datatype=float32&tile_format=tif") as src: im = src.preview() ``` diff --git a/docs/src/examples/code/mosaic_from_urls.md b/docs/src/examples/code/mosaic_from_urls.md index 28f9e5913..211ed43b8 100644 --- a/docs/src/examples/code/mosaic_from_urls.md +++ b/docs/src/examples/code/mosaic_from_urls.md @@ -105,7 +105,7 @@ class MultiFilesBackend(BaseBackend): ```python """routes. -app/router.py +app/routers.py """ diff --git a/docs/src/examples/code/tiler_for_sentinel2.md b/docs/src/examples/code/tiler_for_sentinel2.md index d0e0256c3..c867e0a76 100644 --- a/docs/src/examples/code/tiler_for_sentinel2.md +++ b/docs/src/examples/code/tiler_for_sentinel2.md @@ -165,7 +165,7 @@ dates = [f["properties"]["datetime"][0:10] for f in data["features"]] # Fetch TileJSON # For this example we use the first `sceneid` returned from the STAC API # and we sent the Bands to B04,B03,B02 which are red,green,blue -data = httpx.get(f"{titiler_endpoint}/scenes/tilejson.json?sceneid={sceneid[4]}&bands=B04&bands=B03&bands=B02&rescale=0,2000").json() +data = httpx.get(f"{titiler_endpoint}/scenes/WebMercatorQuad/tilejson.json?sceneid={sceneid[4]}&bands=B04&bands=B03&bands=B02&rescale=0,2000").json() print(data) ``` @@ -239,6 +239,6 @@ with MosaicBackend(mosaic_file, mosaic_def=mosaic_doc) as mosaic: Use the mosaic in titiler ```python mosaic = str(pathlib.Path(mosaic_file).absolute()) -data = httpx.get(f"{titiler_endpoint}/mosaic/tilejson.json?url=file:///{mosaic}&bands=B01&rescale=0,1000").json() +data = httpx.get(f"{titiler_endpoint}/mosaic/WebMercatorQuad/tilejson.json?url=file:///{mosaic}&bands=B01&rescale=0,1000").json() print(data) ``` diff --git a/docs/src/examples/code/tiler_with_auth.md b/docs/src/examples/code/tiler_with_auth.md index b45acf690..fa26604bb 100644 --- a/docs/src/examples/code/tiler_with_auth.md +++ b/docs/src/examples/code/tiler_with_auth.md @@ -140,12 +140,12 @@ def DatasetPathParams( """Create dataset path from args""" if not api_key_query: - raise HTTPException(status_code=403, detail="Missing `access_token`") + raise HTTPException(status_code=401, detail="Missing `access_token`") try: AccessToken.from_string(api_key_query) except JWTError: - raise HTTPException(status_code=403, detail="Invalid `access_token`") + raise HTTPException(status_code=401, detail="Invalid `access_token`") return url ``` diff --git a/docs/src/examples/code/tiler_with_cache.md b/docs/src/examples/code/tiler_with_cache.md index 880ed6875..a29d7bd82 100644 --- a/docs/src/examples/code/tiler_with_cache.md +++ b/docs/src/examples/code/tiler_with_cache.md @@ -167,7 +167,7 @@ from rio_tiler.io import BaseReader, Reader from titiler.core.factory import img_endpoint_params from titiler.core.factory import TilerFactory as TiTilerFactory -from titiler.core.dependencies import ImageParams, RescalingParams +from titiler.core.dependencies import RescalingParams from titiler.core.models.mapbox import TileJSON from titiler.core.resources.enums import ImageType @@ -186,15 +186,15 @@ class TilerFactory(TiTilerFactory): @self.router.get(r"/tiles/{z}/{x}/{y}.{format}", **img_endpoint_params) @self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x", **img_endpoint_params) @self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params) - @self.router.get(r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params) + @self.router.get(r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params ) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params ) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params, ) # Add default cache config dictionary into cached alias. @@ -204,7 +204,7 @@ class TilerFactory(TiTilerFactory): z: int = Path(..., ge=0, le=30, description="TMS tiles's zoom level"), x: int = Path(..., description="TMS tiles's column"), y: int = Path(..., description="TMS tiles's row"), - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( + tileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( self.default_tms, description=f"TileMatrixSet Name (default: '{self.default_tms}')", ), @@ -235,7 +235,7 @@ class TilerFactory(TiTilerFactory): reader_params=Depends(self.reader_dependency), ): """Create map tile from a dataset.""" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) with self.reader(src_path, tms=tms, **reader_params) as src_dst: image = src_dst.tile( @@ -280,7 +280,7 @@ class TilerFactory(TiTilerFactory): response_model_exclude_none=True, ) @self.router.get( - "/{TileMatrixSetId}/tilejson.json", + "/{tileMatrixSetId}/tilejson.json", response_model=TileJSON, responses={200: {"description": "Return a tilejson"}}, response_model_exclude_none=True, @@ -288,7 +288,7 @@ class TilerFactory(TiTilerFactory): @cached(alias="default") def tilejson( request: Request, - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( + tileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( self.default_tms, description=f"TileMatrixSet Name (default: '{self.default_tms}')", ), @@ -332,7 +332,7 @@ class TilerFactory(TiTilerFactory): "x": "{x}", "y": "{y}", "scale": tile_scale, - "TileMatrixSetId": TileMatrixSetId, + "tileMatrixSetId": tileMatrixSetId, } if tile_format: route_params["format"] = tile_format.value @@ -354,7 +354,7 @@ class TilerFactory(TiTilerFactory): if qs: tiles_url += f"?{urlencode(qs)}" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) with self.reader(src_path, tms=tms, **reader_params) as src_dst: return { "bounds": src_dst.geographic_bounds, diff --git a/docs/src/examples/code/tiler_with_custom_algorithm.md b/docs/src/examples/code/tiler_with_custom_algorithm.md index e606982e6..547d5ffe5 100644 --- a/docs/src/examples/code/tiler_with_custom_algorithm.md +++ b/docs/src/examples/code/tiler_with_custom_algorithm.md @@ -32,7 +32,6 @@ class Multiply(BaseAlgorithm): # Create output ImageData return ImageData( data, - img.mask, assets=img.assets, crs=img.crs, bounds=img.bounds, diff --git a/docs/src/examples/code/tiler_with_custom_colormap.md b/docs/src/examples/code/tiler_with_custom_colormap.md index 8a9e5f86e..fd88ecc7b 100644 --- a/docs/src/examples/code/tiler_with_custom_colormap.md +++ b/docs/src/examples/code/tiler_with_custom_colormap.md @@ -15,7 +15,7 @@ cmap = urlencode( } ) response = requests.get( - f"http://127.0.0.1:8000/cog/tiles/8/53/50.png?url=https://myurl.com/cog.tif&bidx=1&rescale=0,10000&{cmap}" + f"http://127.0.0.1:8000/cog/tiles/WebMercatorQuad/8/53/50.png?url=https://myurl.com/cog.tif&bidx=1&rescale=0,10000&{cmap}" ) ``` @@ -33,34 +33,34 @@ app/dependencies.py """ import json -from enum import Enum -from typing import Dict, Optional + +from typing import Dict, Optional, Literal +from typing_extensions import Annotated import numpy import matplotlib -from rio_tiler.colormap import cmap, parse_color +from rio_tiler.colormap import parse_color +from rio_tiler.colormap import cmap as default_cmap from fastapi import HTTPException, Query -ColorMapName = Enum( # type: ignore - "ColorMapName", [(a, a) for a in sorted(cmap.list())] -) - -class ColorMapType(str, Enum): - """Colormap types.""" - - explicit = "explicit" - linear = "linear" - - def ColorMapParams( - colormap_name: ColorMapName = Query(None, description="Colormap name"), - colormap: str = Query(None, description="JSON encoded custom Colormap"), - colormap_type: ColorMapType = Query(ColorMapType.explicit, description="User input colormap type."), + colormap_name: Annotated[ # type: ignore + Literal[tuple(default_cmap.list())], + Query(description="Colormap name"), + ] = None, + colormap: Annotated[ + str, + Query(description="JSON encoded custom Colormap"), + ] = None, + colormap_type: Annotated[ + Literal["explicit", "linear"], + Query(description="User input colormap type."), + ] = "explicit", ) -> Optional[Dict]: """Colormap Dependency.""" if colormap_name: - return cmap.get(colormap_name.value) + return default_cmap.get(colormap_name) if colormap: try: @@ -73,7 +73,7 @@ def ColorMapParams( status_code=400, detail="Could not parse the colormap value." ) - if colormap_type == ColorMapType.linear: + if colormap_type == "linear": # input colormap has to start from 0 to 255 ? cm = matplotlib.colors.LinearSegmentedColormap.from_list( 'custom', diff --git a/docs/src/examples/code/tiler_with_custom_tms.md b/docs/src/examples/code/tiler_with_custom_tms.md index eb4edacc1..0813dee9c 100644 --- a/docs/src/examples/code/tiler_with_custom_tms.md +++ b/docs/src/examples/code/tiler_with_custom_tms.md @@ -20,15 +20,14 @@ from pyproj import CRS EPSG6933 = TileMatrixSet.custom( (-17357881.81713629, -7324184.56362408, 17357881.81713629, 7324184.56362408), CRS.from_epsg(6933), - identifier="EPSG6933", + id="EPSG6933", matrix_scale=[1, 1], ) - # 2. Register TMS -tms = tms.register([EPSG6933]) +tms = tms.register({EPSG6933.id:EPSG6933}) -tms = TMSFactory(supported_tms=tms) -cog = TilerFactory(supported_tms=tms) +tms_factory = TMSFactory(supported_tms=tms) +cog_factory = TilerFactory(supported_tms=tms) ``` 2 - Create app and register our custom endpoints @@ -44,11 +43,11 @@ from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers from fastapi import FastAPI -from .routes import cog, tms +from .routes import cog_factory, tms_factory app = FastAPI(title="My simple app with custom TMS") -app.include_router(cog.router, tags=["Cloud Optimized GeoTIFF"]) -app.include_router(tms.router, tags=["Tiling Schemes"]) +app.include_router(cog_factory.router, tags=["Cloud Optimized GeoTIFF"]) +app.include_router(tms_factory.router, tags=["Tiling Schemes"]) add_exception_handlers(app, DEFAULT_STATUS_CODES) ``` diff --git a/docs/src/examples/notebooks/Working_with_Algorithm.ipynb b/docs/src/examples/notebooks/Working_with_Algorithm.ipynb index cb0fe237c..b76b379d2 100644 --- a/docs/src/examples/notebooks/Working_with_Algorithm.ipynb +++ b/docs/src/examples/notebooks/Working_with_Algorithm.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "55915667", "metadata": {}, "outputs": [], @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "3ac532e8", "metadata": {}, "outputs": [], @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "5c65b3d5", "metadata": {}, "outputs": [], @@ -62,18 +62,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "7abeb9c0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'bounds': [7.090624928537461, 45.916058441028206, 7.1035698381384185, 45.925093000254144], 'minzoom': 15, 'maxzoom': 18, 'band_metadata': [['b1', {'STATISTICS_COVARIANCES': '10685.98787505646', 'STATISTICS_EXCLUDEDVALUES': '-9999', 'STATISTICS_MAXIMUM': '2015.0944824219', 'STATISTICS_MEAN': '1754.471184271', 'STATISTICS_MINIMUM': '1615.8128662109', 'STATISTICS_SKIPFACTORX': '1', 'STATISTICS_SKIPFACTORY': '1', 'STATISTICS_STDDEV': '103.37305197708'}]], 'band_descriptions': [['b1', '']], 'dtype': 'float32', 'nodata_type': 'Nodata', 'colorinterp': ['gray'], 'width': 2000, 'nodata_value': -9999.0, 'overviews': [2, 4, 8], 'count': 1, 'height': 2000, 'driver': 'GTiff'}\n" - ] - } - ], + "outputs": [], "source": [ "# Fetch dataset Metadata\n", "r = httpx.get(\n", @@ -98,95 +90,13 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "80803e00", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " }\n", @@ -198,108 +108,25 @@ " zoom_start=r[\"minzoom\"]\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"Office fédéral de topographie swisstopo\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", "m" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "id": "64c2faab", "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " # rio-tiler cannot rescale automatically the data when using a colormap\n", @@ -333,29 +160,12 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "26ef9eef", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Available algorithm\n", - "['hillshade', 'contours', 'normalizedIndex', 'terrarium', 'terrainrgb']\n", - "\n", - "Metadata from `Hillshade` algorithm\n", - "Inputs\n", - "{'nbands': 1}\n", - "Outputs\n", - "{'nbands': 1, 'dtype': 'uint8'}\n", - "Parameters\n", - "{'azimuth': {'title': 'Azimuth', 'default': 90, 'type': 'integer'}, 'angle_altitude': {'title': 'Angle Altitude', 'default': 90, 'type': 'number'}, 'buffer': {'title': 'Buffer', 'default': 3, 'type': 'integer'}}\n" - ] - } - ], + "outputs": [], "source": [ "# Fetch algorithms\n", "print(\"Available algorithm\")\n", @@ -381,101 +191,19 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "id": "4cc8c900", "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " \"algorithm\": \"hillshade\",\n", - " # Hillshade algorithm use a 3pixel buffer so we need \n", + " # Hillshade algorithm use a 3pixel buffer so we need\n", " # to tell the tiler to apply a 3 pixel buffer around each tile\n", " \"buffer\": 3,\n", " }\n", @@ -506,95 +234,13 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "id": "54d674e9", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " \"algorithm\": \"contours\",\n", @@ -615,106 +261,23 @@ " zoom_start=r[\"minzoom\"]\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"Yo!!\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", "m" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "id": "1c80efe0", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " \"algorithm\": \"contours\",\n", @@ -735,12 +298,11 @@ " zoom_start=r[\"minzoom\"]\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"Yo!!\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", "m" ] }, @@ -755,9 +317,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 with Fil", + "display_name": "py39", "language": "python", - "name": "filprofile" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -769,12 +331,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13 (main, May 24 2022, 21:13:51) \n[Clang 13.1.6 (clang-1316.0.21.2)]" - }, - "vscode": { - "interpreter": { - "hash": "8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1" - } + "version": "3.9.18" } }, "nbformat": 4, diff --git a/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF.ipynb b/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF.ipynb index 94b3be3bc..75084fbe3 100644 --- a/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF.ipynb +++ b/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF.ipynb @@ -42,6 +42,7 @@ "outputs": [], "source": [ "import os\n", + "import datetime\n", "import json\n", "import urllib.parse\n", "from io import BytesIO\n", @@ -49,7 +50,7 @@ "from concurrent import futures\n", "\n", "import httpx\n", - "\n", + "import numpy\n", "from boto3.session import Session as boto3_session\n", "\n", "from rasterio.plot import reshape_as_image\n", @@ -57,9 +58,10 @@ "\n", "from tqdm.notebook import tqdm\n", "\n", - "from folium import Map, TileLayer\n", + "from folium import Map, TileLayer, GeoJson\n", "\n", - "%pylab inline" + "import matplotlib.pyplot as plt\n", + "import matplotlib.dates as mdates" ] }, { @@ -131,11 +133,14 @@ "metadata": {}, "outputs": [], "source": [ - "Map(\n", + "m = Map(\n", " tiles=\"OpenStreetMap\",\n", " location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n", " zoom_start=6\n", - ")" + ")\n", + "\n", + "GeoJson(geojson).add_to(m)\n", + "m" ] }, { @@ -181,10 +186,16 @@ " for subset in paginator.paginate(Bucket=bucket, Prefix=prefix):\n", " files.extend(subset.get(\"Contents\", []))\n", "\n", - " return [r[\"Key\"] for r in files]\n", + " return files\n", + "\n", + "list_files = list_objects(bucket, \"OMI-Aura_L3\")\n", + "\n", + "print(\"Archive Size\")\n", + "files = [r[\"Key\"] for r in list_files]\n", + "print(f\"Found {len(files)} OMI-NO2 files\")\n", "\n", - "files = list_objects(bucket, \"OMI-Aura_L3\")\n", - "print(f\"Found : {len(files)}\")" + "size = sum([r[\"Size\"]/1000000. for r in list_files])\n", + "print(f\"Size of the archive: {size} Mo ({size / 1000} Go)\")" ] }, { @@ -238,10 +249,10 @@ "source": [ "### DATA Endpoint\n", "\n", - "`{endpoint}/cog/tiles/{z}/{x}/{y}.{format}?url={cog}&{otherquery params}`\n", + "`{endpoint}/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={cog}&{otherquery params}`\n", "\n", "\n", - "`{endpoint}/cog/crop/{minx},{miny},{maxx},{maxy}.{format}?url={cog}&{otherquery params}`\n", + "`{endpoint}/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={cog}&{otherquery params}`\n", "\n", "\n", "`{endpoint}/cog/point/{minx},{miny}?url={cog}&{otherquery params}`\n" @@ -291,7 +302,7 @@ "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": _url(files[2]),\n", " \"rescale\": \"0,3000000000000000\",\n", @@ -304,12 +315,14 @@ " zoom_start=6\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"NASA\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", + "\n", + "GeoJson(geojson, style_function=lambda feature: {\"fill\": False, \"color\": \"red\"}).add_to(m)\n", + "\n", "m" ] }, @@ -335,7 +348,7 @@ "xmin, ymin, xmax, ymax = bounds\n", "\n", "def fetch_bbox(file):\n", - " url = f\"{titiler_endpoint}/cog/crop/{xmin},{ymin},{xmax},{ymax}.npy\"\n", + " url = f\"{titiler_endpoint}/cog/bbox/{xmin},{ymin},{xmax},{ymax}.npy\"\n", " params = {\n", " \"url\": _url(file),\n", " \"bidx\": \"1\",\n", @@ -344,8 +357,10 @@ " r = httpx.get(url, params=params)\n", " data = numpy.load(BytesIO(r.content))\n", " s = _stats(data[0:-1], data[-1])\n", - " return s[1], file.split(\"_\")[2]\n", - "\n", + " return (\n", + " _stats(data[0:-1], data[-1]),\n", + " datetime.datetime.strptime(file.split(\"_\")[2].replace(\"m\", \"\"), \"%Y%m%d\"),\n", + " )\n", "\n", "# small tool to filter invalid response from the API\n", "def _filter_futures(tasks):\n", @@ -386,15 +401,21 @@ " executor.submit(fetch_bbox, file) for file in files_15\n", " ]\n", "\n", - " for f in tqdm(futures.as_completed(future_work), total=len(future_work)): \n", + " for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n", " pass\n", "\n", "values, dates = zip(*list(_filter_futures(future_work)))\n", "\n", - "fig, ax1 = plt.subplots(dpi=150)\n", + "max_values = [\n", + " v[1]\n", + " for v in values\n", + "]\n", + "\n", + "fig, ax1 = plt.subplots(dpi=300)\n", "fig.autofmt_xdate()\n", "\n", - "ax1.plot(dates, values, label=\"No2\")\n", + "ax1.plot(dates, max_values, label=\"No2\")\n", + "ax1.xaxis.set_major_locator(mdates.YearLocator(1,7))\n", "\n", "ax1.set_xlabel(\"Dates\")\n", "ax1.set_ylabel(\"No2\")\n", @@ -420,15 +441,21 @@ " executor.submit(fetch_bbox, file) for file in files\n", " ]\n", "\n", - " for f in tqdm(futures.as_completed(future_work), total=len(future_work)): \n", + " for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n", " pass\n", "\n", "values, dates = zip(*list(_filter_futures(future_work)))\n", "\n", + "max_values = [\n", + " v[1]\n", + " for v in values\n", + "]\n", + "\n", "fig, ax1 = plt.subplots(dpi=150)\n", "fig.autofmt_xdate()\n", "\n", - "ax1.plot(dates, values, label=\"No2\")\n", + "ax1.plot(dates, max_values, label=\"No2\")\n", + "ax1.xaxis.set_major_locator(mdates.YearLocator())\n", "\n", "ax1.set_xlabel(\"Dates\")\n", "ax1.set_ylabel(\"No2\")\n", @@ -460,7 +487,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple.ipynb b/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple.ipynb index 78c4509ea..61022008b 100644 --- a/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple.ipynb +++ b/docs/src/examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple.ipynb @@ -117,7 +117,7 @@ "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " }\n", @@ -128,12 +128,12 @@ " zoom_start=13\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"DigitalGlobe OpenData\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", + "\n", "m" ] }, @@ -184,7 +184,7 @@ " }\n", ").json()\n", "\n", - "print(json.dumps(r[\"1\"], indent=4))" + "print(json.dumps(r[\"b1\"], indent=4))" ] }, { @@ -194,44 +194,7 @@ "### Display Tiles\n", "\n", "\n", - "1. Without `rescaling` values, TiTiler will return black/grey tiles because it will rescale the data base on min/max value of the datatype." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", - " params = {\n", - " \"url\": url,\n", - " }\n", - ").json()\n", - "\n", - "bounds = r[\"bounds\"]\n", - "m = Map(\n", - " location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n", - " zoom_start=r[\"minzoom\"] + 1\n", - ")\n", - "\n", - "aod_layer = TileLayer(\n", - " tiles=r[\"tiles\"][0],\n", - " opacity=1,\n", - " attr=\"Swisstopo\"\n", - ")\n", - "aod_layer.add_to(m)\n", - "m" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2. Apply linear rescaling using Min/Max value \n", - "\n", - "This is needed to rescale the value to byte (0 -> 255) which can then be encoded in JPEG or PNG" + "Note: By default if the metadata has `min/max` statistics, titiler will use those to rescale the data" ] }, { @@ -241,10 +204,9 @@ "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", - " \"rescale\": f\"{minv},{maxv}\"\n", " }\n", ").json()\n", "\n", @@ -254,12 +216,11 @@ " zoom_start=r[\"minzoom\"] + 1\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"Swisstopo\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", "m" ] }, @@ -267,7 +228,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "3. Apply ColorMap\n", + "Apply ColorMap\n", "\n", "Now that the data is rescaled to byte values (0 -> 255) we can apply a colormap" ] @@ -279,7 +240,7 @@ "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " \"rescale\": f\"{minv},{maxv}\",\n", @@ -293,12 +254,11 @@ " zoom_start=r[\"minzoom\"] + 1\n", ")\n", "\n", - "aod_layer = TileLayer(\n", + "TileLayer(\n", " tiles=r[\"tiles\"][0],\n", " opacity=1,\n", " attr=\"Swisstopo\"\n", - ")\n", - "aod_layer.add_to(m)\n", + ").add_to(m)\n", "m" ] }, @@ -306,7 +266,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "4. Apply non-linear colormap (intervals)\n", + "Apply non-linear colormap (intervals)\n", "\n", "see https://cogeotiff.github.io/rio-tiler/colormap/#intervals-colormaps" ] @@ -332,7 +292,7 @@ "# https://colorbrewer2.org/#type=sequential&scheme=YlGnBu&n=5\n", "\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\",\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url,\n", " \"colormap\": cmap\n", @@ -378,7 +338,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/docs/src/examples/notebooks/Working_with_MosaicJSON.ipynb b/docs/src/examples/notebooks/Working_with_MosaicJSON.ipynb index b62c9220e..e6ce954be 100644 --- a/docs/src/examples/notebooks/Working_with_MosaicJSON.ipynb +++ b/docs/src/examples/notebooks/Working_with_MosaicJSON.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -30,7 +29,7 @@ "\n", "By default, TiTiler has `mosaicjson` endpoints.\n", "\n", - "Docs: https://api.cogeo.xyz/docs#/MosaicJSON" + "Docs: https://titiler.xyz/api.html#/MosaicJSON" ] }, { @@ -163,8 +162,8 @@ "outputs": [], "source": [ "# We can derive the `bbox` from the filename\n", - "# s3://noaa-eri-pds/2020_Nashville_Tornado/20200307a_RGB/20200307aC0870130w361200n.tif \n", - "# -> 20200307aC0870130w361200n.tif \n", + "# s3://noaa-eri-pds/2020_Nashville_Tornado/20200307a_RGB/20200307aC0870130w361200n.tif\n", + "# -> 20200307aC0870130w361200n.tif\n", "# -> 20200307aC \"0870130w\" \"361200n\" .tif\n", "# -> 0870130w -> 87.025 (West)\n", "# -> 361200n -> 36.2 (Top)\n", @@ -176,7 +175,7 @@ "\n", "def dms_to_degree(v: str) -> float:\n", " \"\"\"convert degree minute second to decimal degrees.\n", - " \n", + "\n", " '0870130w' -> 87.025\n", " \"\"\"\n", " deg = int(v[0:3])\n", @@ -195,7 +194,7 @@ "\n", " return Feature(\n", " geometry=Polygon.from_bounds(\n", - " lon, lat - 0.025, lon + 0.025, lat \n", + " lon, lat - 0.025, lon + 0.025, lat\n", " ),\n", " properties={\n", " \"path\": src_path,\n", @@ -300,10 +299,10 @@ "titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n", "\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/mosaicjson/tilejson.json\",\n", + " f\"{titiler_endpoint}/mosaicjson/WebMercatorQuad/tilejson.json\",\n", " params={\n", " # For this demo we are use the same mosaic but stored on the web\n", - " \"url\": \"https://gist.githubusercontent.com/vincentsarago/c6ace3ccd29a82a4a5531693bbcd61fc/raw/e0d0174a64a9acd2fb820f2c65b1830aab80f52b/NOAA_Nashville_Tornado.json\" \n", + " \"url\": \"https://gist.githubusercontent.com/vincentsarago/c6ace3ccd29a82a4a5531693bbcd61fc/raw/e0d0174a64a9acd2fb820f2c65b1830aab80f52b/NOAA_Nashville_Tornado.json\"\n", " }\n", ").json()\n", "print(r)\n", @@ -342,7 +341,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -356,7 +355,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.9.17" } }, "nbformat": 4, diff --git a/docs/src/examples/notebooks/Working_with_NumpyTile.ipynb b/docs/src/examples/notebooks/Working_with_NumpyTile.ipynb index b6b580506..6c6b4944e 100755 --- a/docs/src/examples/notebooks/Working_with_NumpyTile.ipynb +++ b/docs/src/examples/notebooks/Working_with_NumpyTile.ipynb @@ -64,7 +64,7 @@ "metadata": {}, "outputs": [], "source": [ - "r = httpx.get(f\"{titiler_endpoint}/cog/tilejson.json?url={url}\").json()\n", + "r = httpx.get(f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json?url={url}\").json()\n", "print(r)" ] }, @@ -88,7 +88,7 @@ "# Call TiTiler endpoint using the first tile\n", "\n", "tile = tiles[0]\n", - "r = httpx.get(f\"{titiler_endpoint}/cog/tiles/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\")" + "r = httpx.get(f\"{titiler_endpoint}/cog/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\")" ] }, { diff --git a/docs/src/examples/notebooks/Working_with_STAC.ipynb b/docs/src/examples/notebooks/Working_with_STAC.ipynb index 4d4660d52..7a656dd46 100644 --- a/docs/src/examples/notebooks/Working_with_STAC.ipynb +++ b/docs/src/examples/notebooks/Working_with_STAC.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -106,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -116,20 +115,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "%pylab is deprecated, use %matplotlib inline and import the required libraries.\n", - "Populating the interactive namespace from numpy and matplotlib\n" - ] - } - ], + "outputs": [], "source": [ "import os\n", "import json\n", @@ -155,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -179,104 +169,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "geojson = {\n", " \"type\": \"FeatureCollection\",\n", @@ -337,64 +232,11 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Results context:\n", - "{'page': 1, 'limit': 100, 'matched': 85, 'returned': 85}\n", - "\n", - "Example of item:\n", - "{\n", - " \"bbox\": [\n", - " 30.155974613579858,\n", - " 28.80949327971016,\n", - " 31.050481437029678,\n", - " 29.815791988006527\n", - " ],\n", - " \"geometry\": {\n", - " \"coordinates\": [\n", - " [\n", - " [\n", - " 30.155974613579858,\n", - " 28.80949327971016\n", - " ],\n", - " [\n", - " 30.407037927198104,\n", - " 29.805008695373978\n", - " ],\n", - " [\n", - " 31.031551610920825,\n", - " 29.815791988006527\n", - " ],\n", - " [\n", - " 31.050481437029678,\n", - " 28.825387639743422\n", - " ],\n", - " [\n", - " 30.155974613579858,\n", - " 28.80949327971016\n", - " ]\n", - " ]\n", - " ],\n", - " \"type\": \"Polygon\"\n", - " },\n", - " \"id\": \"S2B_36RTT_20191205_0_L2A\",\n", - " \"collection\": \"sentinel-s2-l2a-cogs\",\n", - " \"type\": \"Feature\",\n", - " \"properties\": {\n", - " \"datetime\": \"2019-12-05T08:42:04Z\",\n", - " \"eo:cloud_cover\": 2.75\n", - " }\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "start = datetime.datetime.strptime(\"2019-01-01\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT00:00:00Z\")\n", "end = datetime.datetime.strptime(\"2019-12-11\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT23:59:59Z\")\n", @@ -437,111 +279,9 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "m = Map(\n", " tiles=\"OpenStreetMap\",\n", @@ -568,30 +308,9 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAFyCAYAAADf8CGJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAC1M0lEQVR4nOz9d5gc5ZnvjX8rd+6ePBpplFBECRACZHIGkTE2YExyNvLu+njPb/dwzr676/PuLnvevd59ffYsOAM2wRhMNBgwUWQQQaCcszQaTeqezl1Vz++P1vR0qNzVYUbP57p0XdNV1U89mumuuut+7vv7ZQghBBQKhUKhUChNBNvoCVAoFAqFQqGUQwMUCoVCoVAoTQcNUCgUCoVCoTQdNEChUCgUCoXSdNAAhUKhUCgUStNBAxQKhUKhUChNBw1QKBQKhUKhNB00QKFQKBQKhdJ08PU+oaqqOHToEILBIBiGqffpKRQKhUKhOIAQgtHRUfT09IBla5/fqHuAcujQIfT29tb7tBQKhUKhUFxg//79mDZtWs3PU/cAJRgMAsj/B0OhUL1PT6FQKBQKxQGxWAy9vb2F+3itqXuAMrasEwqFaIBCoVAoFMoEo17lGbRIlkKhUCgUStNBAxQKhUKhUChNh60A5R//8R/BMEzJvwULFtRqbhQKhUKhUI5TbNegLFq0CK+++ur4AHzdy1goFAqF4hKKoiCXyzV6GpQmQBAEcBzX6GkUsB1d8DyP7u7uWsyFQqFQKHWCEIK+vj6MjIw0eiqUJiISiaC7u7spdMpsByjbt29HT08PPB4PVq5ciXvuuQfTp0/XPT6TySCTyRRex2IxZzOlUCgUimuMBSednZ3w+XxNcUOiNA5CCJLJJPr7+wEAU6ZMafCMbAYop59+Oh588EHMnz8fhw8fxo9//GOcffbZ2LBhg25f9D333IMf//jHrkyWQqFQKNWjKEohOGlra2v0dChNgtfrBQD09/ejs7Oz4cs9DCGEOH3zyMgIZsyYgX//93/HN7/5Tc1jtDIovb29iEajVAeFQqFQGkA6ncbu3bsxc+bMwk2JQgGAVCqFPXv2YNasWfB4PCX7YrEYwuFw3e7fVVW4RiIRzJs3Dzt27NA9RpIkSJJUzWkoFAqFUgPosg6lnGb6TFSlgxKPx7Fz586mWKuiUCgUCoUyebAVoPzX//pfsWbNGuzZswfvvfcerrvuOnAch5tvvrlW86NQKBQKxRYMw+CZZ56p+XlmzpyJn/zkJzU/z/GKrQDlwIEDuPnmmzF//nx89atfRVtbGz744AN0dHTUan4UiiNyR44gvmZNo6dBoVBcpq+vD3/xF3+B2bNnQ5Ik9Pb24qqrrsJrr73W6KlpEovF8D/+x//AggUL4PF40N3djYsuughPPfUUqigBPS6wVYPy2GOP1WoeFIqryAMDiL/7LgLnntvoqVAoFJfYs2cPzjzzTEQiEfzbv/0blixZglwuh5dffhmrV6/Gli1bGj3FEkZGRnDWWWchGo3in/7pn7BixQrwPI81a9bgb/7mb3DBBRcgEok0ZG7ZbBaiKDbk3FahXjwTAHlwkGYD7JLLQRkYbPQsKBSKi9x1111gGAYfffQRvvzlL2PevHlYtGgRfvSjH+GDDz7Qfd/69etxwQUXwOv1oq2tDd/5zncQj8cL+8877zz88Ic/LHnPtddeizvuuKPwur+/H1dddRW8Xi9mzZqFRx55xHS+//2//3fs2bMHH374IW6//XaceOKJmDdvHr797W9j3bp1CAQCAIDh4WHcdtttaGlpgc/nw+WXX47t27cDyGdgvF4vXnzxxZKxn376aQSDQSSTSQDA/v378dWvfhWRSAStra245pprsGfPnsLxd9xxB6699lr88z//M3p6ejB//nzT+TcaGqBMADJbt9IAxSZcewd8Z5ze6GlQKBSXGBoawksvvYTVq1fD7/dX7NfLRCQSCVx66aVoaWnB2rVr8cQTT+DVV1/FD37wA1vnv+OOO7B//3688cYb+MMf/oD77ruvIGqmhaqqeOyxx3DLLbegp6enYn8gEChYxdxxxx34+OOP8dxzz+H9998HIQSrVq1CLpdDKBTClVdeiUcffbTk/Y888giuvfZa+Hw+5HI5XHrppQgGg3j77bfx7rvvIhAI4LLLLkM2my2857XXXsPWrVvxyiuv4Pnnn7f1/28E1EhnAiBMmwae1vnYIrtzB7J79jZ6GhQKxSV27NgBQohtg9pHH30U6XQav/3tbwuBzX/+53/iqquuwv/6X/8LXV1dpmNs27YNL774Ij766COsWLECAPDrX/8aCxcu1H3PwMAAhoeHTee7fft2PPfcc3j33XfxpS99CUA++Ojt7cUzzzyDr3zlK7jllltw6623IplMwufzIRaL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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "fig = plt.figure(dpi=100)\n", "fig.autofmt_xdate()\n", @@ -607,12 +326,12 @@ "source": [ "## Use Titiler endpoint\n", "\n", - "https://api.cogeo.xyz/docs#/SpatioTemporal%20Asset%20Catalog\n", + "https://titiler.xyz/api.html#/SpatioTemporal%20Asset%20Catalog\n", "\n", - "`{endpoint}/stac/tiles/{z}/{x}/{y}.{format}?url={stac_item}&{otherquery params}`\n", + "`{endpoint}/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={stac_item}&{otherquery params}`\n", "\n", "\n", - "`{endpoint}/stac/crop/{minx},{miny},{maxx},{maxy}.{format}?url={stac_item}&{otherquery params}`\n", + "`{endpoint}/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={stac_item}&{otherquery params}`\n", "\n", "\n", "`{endpoint}/stac/point/{minx},{miny}?url={stac_item}&{otherquery params}`\n" @@ -620,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -636,107 +355,17 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2B_36RTT_20190102_0_L2A\n", - "{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['http://127.0.0.1:8081/stac/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=https%3A%2F%2Fearth-search.aws.element84.com%2Fv0%2Fcollections%2Fsentinel-s2-l2a-cogs%2Fitems%2FS2B_36RTT_20190102_0_L2A&assets=B04&assets=B03&assets=B02&color_formula=Gamma+RGB+3.5+Saturation+1.7+Sigmoidal+RGB+15+0.35'], 'minzoom': 8, 'maxzoom': 14, 'bounds': [29.896473859714554, 28.804454491507947, 31.006314627204915, 29.815413491817537], 'center': [30.451394243459735, 29.309933991662742, 8]}\n" - ] - }, - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "item = url_template.format(id=sceneid[-1])\n", "print(item)\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = (\n", " (\"url\", item),\n", " # Simple RGB combination (True Color)\n", @@ -768,103 +397,14 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['http://127.0.0.1:8081/stac/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=https%3A%2F%2Fearth-search.aws.element84.com%2Fv0%2Fcollections%2Fsentinel-s2-l2a-cogs%2Fitems%2FS2B_36RTT_20191205_0_L2A&assets=B08&assets=B04&assets=B03&color_formula=Gamma+RGB+3.5+Saturation+1.7+Sigmoidal+RGB+15+0.35'], 'minzoom': 8, 'maxzoom': 14, 'bounds': [30.155974613579858, 28.80949327971016, 31.050481437029678, 29.815791988006527], 'center': [30.603228025304766, 29.312642633858346, 8]}\n" - ] - }, - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = (\n", " (\"url\", url_template.format(id=sceneid[0])),\n", " # False Color Infrared\n", @@ -896,107 +436,18 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['http://127.0.0.1:8081/stac/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=https%3A%2F%2Fearth-search.aws.element84.com%2Fv0%2Fcollections%2Fsentinel-s2-l2a-cogs%2Fitems%2FS2B_36RTT_20191205_0_L2A&expression=%28B08-B04%29%2F%28B08%2BB04%29&asset_as_band=true&rescale=-1%2C1&colormap_name=viridis'], 'minzoom': 8, 'maxzoom': 14, 'bounds': [30.155974613579858, 28.80949327971016, 31.050481437029678, 29.815791988006527], 'center': [30.603228025304766, 29.312642633858346, 8]}\n" - ] - }, - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": url_template.format(id=sceneid[0]),\n", " \"expression\": \"(B08-B04)/(B08+B04)\", # NDVI (nir-red)/(nir+red), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n", - " # We need to tell rio-tiler that each asset is a Band \n", + " # We need to tell rio-tiler that each asset is a Band\n", " # (so it will select the first band within each asset automatically)\n", " \"asset_as_band\": True,\n", " \"rescale\": \"-1,1\",\n", @@ -1034,7 +485,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1044,9 +495,9 @@ " stac_item = f\"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{sceneid}\"\n", "\n", " xmin, ymin, xmax, ymax = bbox\n", - " \n", + "\n", " # TiTiler required URL + asset or expression parameters\n", - " params = ((\"url\", stac_item), )\n", + " params = ((\"url\", stac_item), (\"max_size\", 1024))\n", " if assets:\n", " for asset in assets:\n", " params += ((\"assets\", asset), )\n", @@ -1058,10 +509,10 @@ " params += tuple(kwargs.items())\n", "\n", " # TITILER ENDPOINT\n", - " url = f\"{titiler_endpoint}/stac/crop/{xmin},{ymin},{xmax},{ymax}.npy\"\n", + " url = f\"{titiler_endpoint}/stac/bbox/{xmin},{ymin},{xmax},{ymax}.npy\"\n", " r = httpx.get(url, params=params)\n", " data = numpy.load(BytesIO(r.content))\n", - " \n", + "\n", " return sceneid, data[0:-1], data[-1]\n", "\n", "def _filter_futures(tasks):\n", @@ -1079,40 +530,11 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1, 128, 128)\n", - "(128, 128)\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Fetch one data\n", "_, data, mask = fetch_bbox_array(sceneid[0], bounds, assets=[\"B02\"], width=128, height=128)\n", @@ -1125,43 +547,11 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "900812f792d44a349a3b2e1579b17338", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/85 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Let's fetch the data over our AOI for all our Items\n", "# Here we use `futures.ThreadPoolExecutor` to run the requests in parallel\n", @@ -1181,7 +571,7 @@ " executor.submit(bbox_worker, scene) for scene in sceneid\n", " ]\n", "\n", - " for f in tqdm(futures.as_completed(future_work), total=len(future_work)): \n", + " for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n", " pass\n", "\n", "results_rgb = list(_filter_futures(future_work))\n", @@ -1198,36 +588,11 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "93982f5e7e9a4a01838b940b72b43b8c", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/85 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "## Fetch NDVI\n", "\n", @@ -1244,7 +609,7 @@ " executor.submit(bbox_worker, scene) for scene in sceneid\n", " ]\n", "\n", - " for f in tqdm(futures.as_completed(future_work), total=len(future_work)): \n", + " for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n", " pass\n", "\n", "results_ndvi = list(_filter_futures(future_work))\n", @@ -1259,30 +624,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3AAAAKACAYAAADdD5p6AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAABcSAAAXEgFnn9JSAAEAAElEQVR4nOzdd3gUVRfA4d+mhxRqAiENQou00HvviHREVFDEggoIooDYFRT1UyyoqCiiIijSu5ESCCV0CSCEHiAJJZRU0vf747KNtE2y6ed9njzszM7O3IWQ7Jl77jkarVarRQghhBBCCCFEiWdV3AMQQgghhBBCCGEeCeCEEEIIIYQQopSQAE4IIYQQQgghSgkJ4IQQQgghhBCilJAATgghhBBCCCFKCQnghBBCCCGEEKKUkABOCCGEEEIIIUoJCeCEEEIIIYQQopSQAE4IIYQQQgghSgkJ4IQQQgghhBCilJAATgghhBBCCCFKCQnghBBCCCGEEKKUsCnuAYjCV6NGDRISEvDx8SnuoQghhBBCCFHuXbp0CScnJ65evZrn18oMXDmQkJBAampqcQ9DCCGEEEIIAaSmppKQkJCv18oMXDmgm3k7ceJEMY9ECCGEEEII0ahRo3y/VmbghBBCCCGEEKKUkABOCCGEEEIIIUoJCeCEEEIIIYQQopQo1wHcoUOH+Oijjxg2bBheXl5oNBo0Gk2+z3f79m0mT56Mr68v9vb2+Pr6MmXKFO7cuZPta9LT0/n8889p0qQJjo6OuLm5MXLkSE6ePJnvcQghhBBCCCHKJo1Wq9UW9yCKy5AhQ1izZk2m/fn5K4mOjqZ9+/acPXsWPz8/WrVqxYkTJzhx4gT169dn7969VKlSxeQ1GRkZjBgxglWrVlGpUiV69uxJdHQ0O3fuxNHRke3bt9OmTZt8vz8d3SJJKWIihBBCCCFE8SvI5/NyPQPXvn173nrrLdauXUtUVBT29vb5PteUKVM4e/Ysw4YNIywsjD///JPjx48zadIkTp8+zdSpUzO9ZuHChaxatYp69epx6tQpli9fTlBQEH/99ReJiYk8/vjjpKWlFeQtCiGEEEIIIcqQcj0Ddz8HBweSk5PzPAMXFRWFl5cXNjY2XLp0ierVq+ufS05Oxtvbm1u3bhEZGYm7u7v+uYYNG3Ly5ElWrVrFkCFDTM45ePBg1q5dy/Llyxk+fHiB3pfMwAkhhBBCCFFyyAxcMdu8eTMZGRl07tzZJHgDsLe3Z+DAgaSnp7Nx40b9/gsXLnDy5EkcHR0ZMGBApnOOGDECgHXr1hXu4IUQQgghhBClhgRwFnD06FEAWrRokeXzuv2hoaGZXtO4cWNsbW3Neo0QQgghhBCifLMp7gGUBZcuXQLAy8sry+d1+8PDwwv0mtxk19H93Llz1KlTx+zzCCGEEEIIIUommYGzgPj4eAAqVKiQ5fNOTk4AxMXFFeg1QgghhBBCiPJNZuDKkOwWQWY3MyeEEEIIIYQoXWQGzgKcnZ0BSExMzPL5hIQEAFxcXAr0GiGEEEIIIUT5JgGcBfj4+ABw5cqVLJ/X7ff19S3Qa4QQQgghhBDlmwRwFhAQEADA4cOHs3xet79p06aZXnP8+HFSU1PNeo0QQgghhBCifJMAzgL69euHlZUVwcHBXL9+3eS55ORk1q1bh7W1NQ8++KB+f+3atXnggQe4e/cuGzZsyHTO5cuXAzBw4MDCHbwQQgghhBCi1JAALg++/vpr/P39mTlzpsl+Dw8PHn30UVJSUnjxxRdJS0vTPzd9+nRu3LjB6NGjcXd3N3nd1KlT9ccYB34rV65k7dq11K1bl8GDBxfiOxJCCCGEEEKUJuW6CuWGDRuYNWuWfjslJQWAdu3a6fe99dZbDBgwAIDo6GjCwsKIiorKdK4vvviCkJAQVqxYgb+/P61ateLEiRMcP36cevXqMXfu3EyvGTduHBs3bmTVqlX4+/vTs2dPoqOj2bFjB46OjixevBgbm3L9TySEEEIIIYQwUq5n4G7cuMG+ffv0X1qtFsBk340bN8w6V7Vq1di/fz+TJk0iJSWFVatWERMTw0svvcT+/fupUqVKptdYWVnx119/8dlnn1GzZk3Wr1/PsWPHGD58OAcPHqRt27YWfb9CCCGEEEKI0k2j1UUtoszS9YHLrk9ciaDVQsQhqFAFqvgV92iEEEIIIURJkpEO+76D039Dy7HQeFhxj6hACvL5XPLzRPFLvQurX4ATq8DaHkavgNqdi3tUQgghhBCiJIi7BiufgQs71faFHWBtCw+Uz2J/5TqFUpQACdHwyyAVvAGkJ8PqFyEptnjHJYQQQgghit/5HfBdJ0PwprNyPFzLR3bZ7q8g6GPLjK2YSAAnis+N0/BjT7iy33R/zCX4+/XiGZMQQgghRFFKT4OLuyH0L4g6CmkpxT2ikiEjHbbPgV8HQ4JRmy6NtfozNQGWjoKEm+adT6uFwLfgn7cg6EPYv8DyYy4ikkIpiseFYPjzcUiKMexzbwTX791JOfKbmhav37d4xieEEEIIUVhS78K5bXBqA4Rtgru3DM9Z24H7A+ARcO+rGVRvBLaOxTbcIhd3DVY8DReDDfus7aDfHKjoDUseAbRw5xL89SSMWaVSKrOTngbrJsO/iw37Qr6F5qNL5d+rBHCi6P27FNZOgoxUw77es6DdC/BTb4g8ovatnQQvhqjCJgWVngaHfoZT68H/IWjzbMHPKYQQQghhrsRbqgDHqfUqeEtNzPq49BQ1Exd11LBPYw1u/tB5KjQZUTTjLS7ng2DFs6azblX84OFFKqAF6PUubHlHPb4YDJtfgwGfZX2+1CRYPg7CNhj2VW+iai6UwuANJIATRUmrhaA5sMMo79jGAYb9AA3vNSwf8h1830WthYu/BhtfhRELC3bdc9tg8+tw46TaPr8D6vaCKrULdl4hhBBClG53b8OhX9RMTsfJUNnX8teIvwFrJ8KZf0CbnvUxVrZQtQ7cPGd6g1tHm66ylFaNB+82UMnH8uMsCYI/g62zAKMi+Y2GwsCvwMHVsK/jZLX+7dgytX3gRzVL2Wqc6fmSYmDpYxC+y7DPpwM8uhQcKxXWuyh0EsCJopGWDGsmwLG/DPuc3ODRP8CrlWGfuz/0fBsC31Dbx1eoGbP8lIq9eQ4C34Swjfc9oYWLuySAE0IIIcqr+BsQ8g3s/xFS4tS+qKPwzBbQaCx7raA5cHpz5v12LlCvN/gPgHp9VICSlqJuOOtm4KJC4eoxSLurXpORBru+gIfmWnaM5kiIVjfFa3cFl+qWP//lA7D1fcO2tb1KmWw1LvO/iUYDg76Cm2cMmVsbp0G1BlCro9qOvw6Lh6m/P536/eHhn0vtzJuOBHCi8CXegj8eh0t7DPuqNYDHl0HlWpmPb/eCygnXHb/hFfDtaP4Pi6QY2Pk/CPku67tYAJdCoMWYPL0NIYQQQuRBSgJsm32vMEeyyq5JT733OEV9paVAhcowYC7U7Vn4Y4q5oqoQHv4F0pJMn4s4qG7wWrKVUUoChC4zbDu5g/+D4D9QXcfG3vR4GzvD2jed9DTV/0x3c/vIb9D5Fajoablx5ub6Kfi5v1qrV70xjN8JVtaWvcbZfwyPK/rAqMWmfw/3s3WEUUvgh24qaysjDZaNgeeCQJsBvw2FW+cNxwc8BoPmgXXpD3+kCqUofBGH4NJew3btLvD031kHb6B+IAz5Bmyd1PbdW2rhaW495zPSVRrEvJawZ55p8ObTHjpOMWwbj0cIIYQQlrd/gSoUEb5bBUdXj8GNU3D7AsRGQMINSI6B2xfVzdrcfs8XxM1zsGYifNkM9n+fOXjT2f2lZa97YpVhhs/eFSb/CwO/hHq9Mgdv2bG2UWv3Xe8FbOkplh9nTm6Hq2BIV2jl2nGIOGz561w5aHjc4omcgzcd15rwyO+qwAlA4k34fST81Nc0eGs/EQZ/UyaCN5AZOFEU6vWGvh+o1gDNRsNDn6s7TDmp4gd9ZsGGqWr79Cb4dwk0fzzzsdf+U6mWx5erXwLGKnpD7/dV/vSdS7D7C7X/1jk1te7sXtB3J4QQQoisXN6f+zE6ty+oAM+jad6vsfk19cHdylZVIrSyuffnvW1thrpxq80wfa2Tm/pgX8VPzdyAmgW6ehxqNM7bOLJz6BfD4yYPg51T/s5jYw+dXla1AQAOLVIFTVxqFHiIOYq7qsr4x0Wa7j/7D3i3ttx1tFp1w1/Hq6X5r/VurYLi1S+obV3NA51e70GnKQUeYkkiAZwoGu1eVGmTdXuan1veapyhUhOoH9C1u0Alb3VX5fgKOL4Srv+X+bW2FdQPug6TDHnOlXzApabhh9ClEGg4qODvTQghhBCZRYcZHnecDJ6tVCBibavWN9nYq5m3qH/VMSfX5i2A02rVrJrxdczh6qXG02KM+oyg1UKNpnA1VD2/5ytVYK2grp807XXb8smCna/5GNj5KcRfVemou7+Cfh8W7Jw5SbwFvw1TwfX9zgRCdwv27L15DpLu3NvQgGceAjiAZo+poiZ7vzbs01ip4idlcMmMpFCKoqHRqHSBvCwM1mhg0NdgX1FtJ8eq3nELesBXzVVefabgTQNNRsLEg9B1uukiVY0GfNoZti/vy/fbEUIIIUQO0pLhltEH/2aPq5um9ftCnR6q0IRXK2g83HDMf2vzdo2oo3kL3qrUUWl0Lx2Bts8ZPiNoNCqg0zm2XGXtFJTx7Nv969ryw9bBdJwHF6piLIUhOR6WjDT05wUVQOpEHlGZTJYSYZQ+Wa0+OFTM+zl6vaeKwYC6QTDytzIZvIHMwImSrqInPPiJKpsLpj1RjNVsDo1HqFTJnBb1+rSHEyvVY1kHJ4QQQhSOm+cMJfOtbFSaYlYaDoJ/3lKPo8PgRhi4NTDvGsaVrT0CoMfbav17eooqlpKRpv5MT1FZOHV6ZF94o+EQ2PqeCty06RAyX1VAzK/UJAj9w7DdooCzbzotx8KuuWr9YNpd2DtPLRWxpLRk+HM0XDlg2NfpZej5jsqKio1Q+85uhWaPWuaaxtcyrk6eF9Y2MGopnNuqvoeyq7VQBkgAJ0q+po/AyXUqndKYm78K2hoPU71TzGE8Axd1VFWHym8+uhBCCCGyZjwzVqWOSpvMSuVapumLJ9eC27Tcz5+RrpZS6DQfozJ98svaBtpPgk33rn3oF+gyDSpUyd/5Tq5TPeZALeto8nD+x2bMroJaHvLP22p7/4/QYTI4VbXM+dPTYMXTcH67YV+rcSp402hUXYNDi9T+M4EWDOCMZuDymj5pzNpGzfKWcZJCKUo+jUaVfa3fX/2Q7zQVXtgDL4ZA12nmB2+gmjzauajHGWmmC2aFEEIIYRk3Thseu9XP+dgHjNajm5tGGb4b4qLUY421ysApqOaPg+O9gC01AQ7+lP9zHTZKn2w0zLQJdUG1etp0nCHfWOa8GRmq6vfJdYZ9jYfDg58alsDU7W147tw2FfAVVOpdVdlSJ78zcOWIBHCidKhQBR77A54Phl7vqEAsP402razBu41h+1KI5cYohBBCCMV4Bq5aLimRxgXFroaarp3LjnH6ZJ0e4FQtb+PLip0TtHnOsB3ynQou8urmObgYbNguaPGS+9k7Q4eJhu19P6iCI/mRlqKKfxxbDqueg38XG56r1xeGfm+adurXVVX3BFV0xHjtWn5Fhaqb6gA2juDeqODnLOMkgBPlj097w2NZByeEEEJY3g2jAM7NP+dj3RqYBnnGM0BZSUuG/9YYti2VnggqgLO5V9wkMVq1MMqrw78aHrs9AF4WLLev0/pZcKikHqfEwb7vc39NzBX19xb0ESx7Er5uAx96wPwOKm3SOCj27Qgjf8mc+mrvAr4dDNtnAgv8VkyCwJrNy0yvtsIkAZwof0wqUe63zPS/EEIIIZSMdIg+Y9jOLYUSTGfhTuaSRnl2CyTFqMc2juD/YN7HmB2nqqaVC/fMU+/HXOmppkFfiyfylzGUGwdX1aJJJ2S+4e/kftFnYPk4+LwxLHsCgubAf6vVLGlGFp+BPALg0T9MK3kbq2eURnnmn3y/BT2TAiYFWP9WjlgsgPv444/NOu7mzZsMGzbMUpcVIu88W6qKWAAp8aYlcoUQQghRMHfCVZ8yADRQtV7urzFeB3flAMREZH+s8UyR/4NqVsiS2k9QPcRA9UDLbUbQWNgmSLhXXt/aDgJGWXZsxtqOB/t7a+uSY1QqpbFb52HV8/BNm3sFX7RZn8fKFtwbqrV6fT+EsRtzXrOnK9UPKuU1NqpAb4MrRvUIPGX9mzksFsDNnDmTHj16cOXKlWyPCQwMpGnTpqxZsybbY4QodHYVwKOZYVvWwQkhhBCWY1zApJK3+r2bmxpNTMu+3195WicpVgVJOpZMn9SpXMu0KMruL1Wzb3MYFy95YFD+q1iaw7EStH3esB3yDSTHqVYIaybCvFZwdCloMwzHVPSGBwZC1xkw4md4cR+8EQUv7oWHf1bBq71zztetVl+1ZdA5uyX/7yH+OsQY9dyTAiZmsVgA17t3b4KCgggICOCPP/4weS4lJYXJkyfTv39/rl27xmuvvWapywqRP8ZplLIOTgghhLCcG6cMj3Nb/6aj0ZjOwmU363VqA6QlqccOlaBOz3wNMVcdXjI8jjxsWpQkO3cuqd5oOpYuXpKVdi+A3b2A6+5t+HUwfNUCjvxm6MMHULUuDP8JJh+FRxZD99dVGyZ3/+xbPGRHozGtRnm2AGmUxu0DXDzANYdevkLPYgHc33//zdy5c0lMTOTxxx9nzJgxxMbGEhoaSsuWLZk3bx6+vr7s2LGDDz74wFKXFSJ/TAK4EPPvrAkhhBAiZ9FGM3DVzFj/pmMcwIXvhoTozMcYp082GgI2dnkenllqNgO/bobt3V/m/poji9GnKVbxg1qdC2Fg96lQBdo8a9iOOKSametUrgVD5quZtiYjsm9knlfGaZTntqu1f/kRcV//t8JYL1gGWbSIyZQpU9i/fz+NGjViyZIlNGzYkLZt23LixAmeeOIJjh49SseOHS15SSHyx9sogIuLUvn6QgghhCg4kwqUubQQMObZElxqqsfajMxplPHX4XyQYbvJyHwP0Swdpxgen90CV49neygZ6fcCuHsKq3hJVtpPVM3Cjbl6wcAvYeJBaPaY5Ss71u6s1vgBJMeqonD5YTwDJ+mTZrN4FcomTZqwevVqXFxciIyMJCUlhTFjxrBo0SJcXCy8yFSI/HJ2U+kEOrIOTgghhCg4rfa+Gbg8BHBWVmp9ls79Tb1PrDKkBbp6mrYFKgx+3aBGU8P23zMh8t+ss3bOboXYe4VXrGwg4LHCHZsxp2rQ8x1VeMWlpmq8/dJhaDk27+mR5rJzglqdDNv5aSeQkQ4Rhw3bUsDEbBYP4FauXEnbtm2JjY2lZcuW2NnZsXjxYkaPHk1sbKylLydE/sk6OCGEEMKy4qLUjIyOOS0EjBm3E7iwQ63r0jFOn2w8XAV8hUmjgY6TjcazE37oqqo67vifacNx4+Il9fuBS/XCHdv92j0PM8Lh5RMqpdLGvvCvaZxGmZ92AtGnVQ87UMFnzeaWGVc5YLHv/ISEBJ5++mkefvhh4uLi+OSTT9i/fz8HDhygcePGLFmyhICAAIKDzVgEKkRRMGnova/4xiGEEEKUFcbpk87VwbFy3l7v0x6c3NTjjDQI26we37pg2i+saSGnT+o0HALVG5vuiz4N22fDV83gx96w+yvTypgtxxbN2O7n4Fr4Qa0x40Im10/k3PohK8bpk+4Nc69+KfQs9q/crFkzfv75Z/z9/QkJCeHVV19Fo9HQuHFjDhw4wJQpU7h06RI9evRg5syZlrqsEPlnHMDdOAmJt4pvLEIIIURZkN8CJjpW1uA/wLCta+p9fLlhn5t/5qCqsFjbwLjNKi3Ru23m56/sh3/eMqR2VvSGOj2KZmzFrWodqFzbsJ3XapT3FzARZrNYAHfu3DkmTpzIoUOHaNasmclzdnZ2zJ07l8DAQKpXr84nn3xiqcsKkX9V/Ax3+SD/C3CFEEKIsmjP1/BVc/WnufJbwMSYcTXKs1shOR5CjdInm4wo2mqF9i4qLfHpQFWGv8eb2a/taz7acpUeSzqNpmBplMYNvKWASZ5YLIDbsGEDX331FQ4ODtke07NnT44dO8bDDxdC00Uh8kqjyfs6uNQkSEksvDElx8O+7+HUxsK7hhA66akQugzWTTbtXSSEECfXQ+AbcOu8mmGKu2re64wDuLwUMDFWqzM4VFSP05Mh+DOINjpv4xH5O68lVK4FXabBhH0wfid0mGSonFm5FrR6uvjGVhzqGaVRng+CtBTzXpccr9IudaSASZ5YrKZo//79zTqucuXKmRp9C1FsfNobmoXmVoky6ij8PhLir4FfV2j6iKqWZW+h6qqJt+C3Ieo6AGNWlZ80DFG0UhJVk9c9X0PMJbXv8K8wLhC8W1v+eulpkHAdXGta/txCCMuLv6Fu7OhoM+D8Dgh4JPfXRltgBs7GDho8CEeXqu3dXxie82oDVWpn+bIipdGAR4D66vWeakfk5Ga5zwSlRa1OYOOgmqunxKub4X5dc39d1L/q+wpUI/L8fq+UUxZf6ZiWlsaaNWt44403GD9+PAsXLtQ/FxkZSWhoKGlpaZa+rBD5YzwDF3lYzbBlJfEW/DEa4q8CWnWXafUL8L96sPxpOB2oPqTmV+It+HWQIXgD9YFaCEtKvAU7PoEvGsOm6YbgDdQv0lXjISXBstc8HaiuN/cBWPoYJNy07PmFEJal1cL6KZB4XxPtCztyf23iLUi4YdguyIdy4zRK3Qd9gCYlMIvLylotyyhvwRuAraNpw3Jz2wkYFzDxbFF+0k4txKIB3K5du6hbty7Dhg1jzpw5/Pjjj+zatUv//N69e2nevDlr167N4SxCFKEaTQ3NL9NTIPJI5mMy0mHFM6YfdnXS7qqF1Usehrn+sGkGRBzKukdMdhJuwi+D4Oox0/2n/7b8h2lRPsVEwObX4fPGsP0DSDQKoqxsVPlmgFvn4J93LHPN5DhY+5L6vxEXpfaFbYDvOqpS3EJkJy1FrUlOji/ukZRPoX9mbqANagYut99txgVM7CuqKpT5VaeHmpkxprGGRkPyf05ROIzXwZ3dYt5rTAqYSPpkXlksgPvvv//o168fUVFRTJo0iWXLlqG97z/6wIEDqVChAitWrLDUZYUoGGtb04WzWa2DC/oIzhmtD2r3IrR8ChwqmR6XcAP2fQcLeqjZtGsnyFVCNPwyEK4ZB2/3FmanJsLpzea+k7Il8gj8ORoC3yzYzGZ5l3AT1k2BLwMg5BtINbohYFtBfS+/9K9pn6MDCwq+Hi58D8zvaNoXSScuSt2w2DZb/m1FZmnJsOhB+Kk3/PKQ+etphGXEXIGN0wzbxh+sY6+o9XA5MSlgUr9ghUZsHUwDA1CNtZ3d839OUTiM18HdOAV3srjhfT8pYFIgFgvgZs2aRVJSEuvWreOLL75gxIjMC0zt7Oxo0aIFR45kMcshRHEx6Qd33zq4sM2w06hqqv9D0PdDGPgFvHoaHlms1sFZ25m+7sJO+K4TbHgl+/YE8ddh0UOmi3g7ToaARw3bx1fm6y2Vaod+gZ/6qLWJe+bB/u+Le0SlT0Y6HPgJ5rWAQz9DRqrhOccq0G2mavbabw5U8lbbxiW510w0bZ5rrrRkCHwLfn5QrQfR8QiArq+Ble29HVrY+T/1Qd2cX/Si/Nj+gaHXV+QR+Pf34h1PeZKRAWsmGJpw27vCyF9Ufy6d80E5n8MSFSiNGTf1hqLr/SbypkptqFrXsJ1bNcqYCIiLNGzLDFyeWSyA2759O23atKFPnz45Hufp6UlkZGSOxwhRpIzXwV0OUb/EAG6eg5XPGZ6rWheGzDfcUbSxV8HbI4vhlTB46Au1uFpHmwEHflQlmEO+UxX/dOKuqeDtxknDvk5T1ULoxsMM+878A0mxFnurJVrqXfXhYd1LKp1VZ9cXhVv5s6yJOAQ/9oQNUyHpjmG/qxf0+xhePg7dXoMKVQzP2djD0O8NAVZcJGycnrfrRoXCD91gz1fAvewLjTV0nQHPbIXuM+GZLVCljuE1l/fB/E5wYnXe32d+ZGTkfowoPpf2qYbIxoI/k1m4onLwJ9MArf/HUNELahsVpMhtHVy0BSpQGqvb29AI3KGiaX84UbLkpZ2AcfpkRW9wKUCqbTllsQDuzp07eHt753pcQkICqampuR4nRJHxam1YA5QUo6b/UxJh2ROQHKP22zqpQM3BNetzVKgCrZ5SPWIe/dP0Q2rSHdg8Q6WUnd2qSjH/8pDpL7ou06Dn2yo49Otm+IWVnlw+0ihvh8PCvnBkcebnEq7DwYWZ9wtTibdU1bgFPU3Xctq5QN85MPlfaPc82Dll/foajaHHG4btY8vgxKrcr5uWAjs/VanD1/8z7K9aF57+B7q/rlKVAWo2g/E7IOAxw3HJMfDXk2rshRWop6fB0kdhVjX4xA++66y2N05TNwiOLYfwver7MCFa/RxIvatmMkXRSEmA1c+jD/51Yi7LLFxRiD6rZs91GgwwZIMYVxS8sDPnGyE3jNbAWWIGzt4ZHvsL2oyHx5eXzyIhpYVxGuWFHdkXhQPTAiaSPpkvFmsj4O7uztmzZ3M97uTJk2YFekIUGXsXlT52NVRtX9qjShZfO244ZvA8cH8g93NpNNCgn1p8vf97VfFPl44SHQaLh6mF3brAEFRqWbfXDDN71rZqZk9XhfL4yrKdNnJmC6x8xjRlz6s1VK0HR5eo7d1fqAA5u+CjLNNq1WxudhW6MjLgyK+w5d3MaY9NRkKfWeBSw7xrdXgJwjapmTGA9VNVinF2rz+7BTbPNC1cAND2eej5DthVyPwaexcYOh/qdIf1L6uy0wCHFqlZvKc2qbUvlhS2UX2BKuCSeNPw/z03GiuVIm1tp/5vutZUxY9qNFFf1RuDYyXLjrc8+ucdw/oqjRX4dIDwe0XQgj+DZo+r0vLC8tLTVPCcdldtV6gGA780/E7y7ahm07Xp6mfM1VB1M+Z+KQmmxb6q1bfM+LxbF057E2FZvh3V2urURPW1YSoM+hqsspgrijBa/ybpk/lisQCuR48e/Pbbb2zfvp3u3btnecyqVas4e/YsEyZMsNRlhbAMn/aGD3Q7/nevXcA97SZA4+F5O5+NnWru2XQUbJt1Lxi7d2fZOHjr9jp0m5H59Y2GGgK4s1vg7p2y9yExIwOCP4XtH2Jy173Nc9DnAzVzeWKV+lCRcEOt6er4UnGNtnic2QIrn4W7twCNCiCsbFSqo/W9PzPSMpf7dnsABnyq+vPkhZU1DP1OpTWmJqjrrn0JHvvTtBjBrfPw9xuGoEjH1ROGfKtmkXPTdCR4toQVTxtmDCMPw9b31No8SzK3rHVWtBmqv1HavbvJiTczV4yt5KOCuuqNwbcD1O5SsOIN5c257ap4jk7HydBqHHzVQq3f1M3CtXqq+MZYlu350rDuENQab2c3w7aDq/q/emW/2r6wI+sAzvhGjo2D+n8hyg8be2gywvDZ5d/f1c2YgV+ZBnHpaaZZIjIDly8WS6F87bXXsLOzY8iQIcyfP5+rVw0fgG/fvs3ChQt5+umncXJyYurUqZa6rBCWYbwOzjh48+kAvd/L/3md3WDQVyptzLej6XM93so6eAOo1UXdBQX1AebUhvyPoSS6exuWjlIFC3TBm40jDP0BHvyfCoCd3aHNM4bX7P6yfLVVSE2CtZPuBW8AWrU2MDVR3QRIvKm+V42DNztnFfw+H5z34E2nih/0nW3YPvO34RdycjxsfR++aXtf8KZRlVlf2GNe8KZTtY5qHt7sccO+kG8LXgXTmFZrWta620wY/hP0fl/dLGgwQBVZ0f1/y487l1TZ9R0fqQq0B34s+LjLi6QYVTRHx72R+jeq5APNRxv2y1o4RatV6b1x1yD6jJq1Ti/AspSrx2C70Q2TpqNUBsj9jNMoz2ezDs44fbJaPenrVR71+8i0J9yR32DDy6ZptzdOqt9joG5IegQU7RjLCI32/lr/BbB69WrGjBlDYmLW6xgcHBxYunQpgwYNyvJ5UTgaNWoEwIkTZpS1L69iI1WjYWPONWD8TsstrtVq4b81Khir31fdqcrJ+pcNa7/q9oLRZaT9hlYLiwZA+G7Dvsq11RrDGo1Nj42/AV82Nfyw7/2+acn7sizkO7V20lyNR0Cf2eDqUfBra7Xw+whD4GPnrNZo7vrCtHIYqNnr/h8X7JdwahIs6G5YQ+dcHV7YC05V839OnavHVEVYADQw7Vz2501PVUFyekrWj1OT4OYZdc6rx9SsfVJM5vPYV4SXDoNTAYLC8mL1i4Y1blY28Ox28Giqtu9cMszCgSoUVZ5m4U5tgP0L1E2a5DhV0Co5zrSqLKiZ7xf3qiIfeZGeCt93NVRCdvVUN2Gyyva4EKzWboNKk5txUc24GNv6vgq0Qf08GvFT3sYjyobkePj9YbUcRafV0zDgM5WZcHCh+nwD6vfG+PLbF7Qgn88t2sh7yJAhHD9+nEmTJuHv74+DgwN2dnb4+fkxfvx4QkNDS1zwdvfuXd5++23q16+Pg4MDNWvWZNy4cURERJh9jkWLFqHRaHL9+vXXX01eN3bs2ByP/+677yz9dkV2XGtCJV/DtpUNPLzIspWRNBrVgHT4gtyDN4BGRtUozwdl346gtLmw0zR4q98fngvKHLyBmsFsfd8sXHlo7puSaPggBNDiSXjpCEw4oD5gjd8Jz2yDcX/D2A2ql9uInywTvIH6Xh30taHXYUo8bJpuGry51FQzWU9tKvgdVFsHGLbA0I4j/pqafbTE/UXjamieLXMOCq1t1TpLx8pqBriil5qRdGug1rt5t4Zmj6kUz7HrYUY4TDkGo5aqdGj7ex+gk2MgyMJpoGXRqY2mBUq6vmYI3qB8z8JFHlGFtM5vVzcLbl9Us/H3B28AsRFwMovG27k5v8O0jc3gr7NP1fduo7IkQN1QM0651LF0CwFROtk7w+PLwNsos+ngT+p3iFZ7X/83WduYXxZbA6fj6+vLF198YenTFoqkpCR69OhBSEgIHh4eDB48mIsXL/Lzzz+zfv16QkJC8PPzy/U8devW5cknn8zyuZiYGFavXg1Ap05ZpzT17duXGjUyFwlo0EB+ABapRkNUgAAqDc23fY6HFzrfDmomIv6aWud0ci20HFu8Y7KEvd8YHtfuCqOWZL3IWafjZLX+LTVBpQ0eWACdXi78cRanAz+q6pug1pJ0f938QiSW4uoBD82F5eNM91vbqWInnadatqhMjcaqjcbfM9V22AbVCLyg3/PGAdz9TYELSqNRQUYlH/B/UP19BN6r5HnwZ2j9LLj7W/aaZUXCTVV5VKdmi6z/X3eeqqrTlqe1cCkJsOIZ9XPfXMYtacxlXMinTg/1lR0be7XU4Px2tX1+R+Y0beMAzlIFTETpZO8Cj/+lCrfpgv39P6hiOMYtBKSASb5ZPIArTWbPnk1ISAjt27cnMDAQZ2dnAObOncsrr7zCuHHjCAoKyvU8nTp1yjY4mz9/PqtXr6Zjx47ZBoOvvfYa3bp1y+/bEJbSbaa62+5SE+pb+INeflhZQ8MhhkbWJ1aV/gDuxmm1pkqn4+ScgzdQaWhtnlWVKEH1iWr9TNktJ50cZ3ivoN5rUQdvOo2HqwDo6FK13WCAWh9XJfcbW/nS9nlVcET3IXHzTPDtBNXq5vy67Ny9Y6ioCVCvV4GHmKM2z6k7zbfOq4p9gW+UndRnS9JqVYU645sUQ79XhXnup5uFO/Sz2i4PFSk3vwY3dVW9NaogUZU6qpiIve7LRX0g3vKOOuz6qbxf54bRa2o0yf14v66G/5sXdgBGbUfSUgxVRAHc5MZFuefgqn7+/TbUUHVy33zTY6SASb5ZNIWyNElJSeHrr78G4JtvvtEHbwBTp06ladOm7Nixg0OHDmV3CrMsXqz6Wo0ZM6ZA5xFFwNZRBUglIXjTMW7qfWGnWhNW3FKTYOss2PBK5rL1uQkxmn1zb5jzHV9jHV5S67BApRHtX5Dz8aXZvu/VTCOo/oMdpxTrcBj8rept+NwOeHRJ4QVvoIL5IfPB8V6T8dRE1WIiv0Uazm9XgRSoIiUezS0zzuzY2Kl1iDpnt+Te0LY8Or4C/ltt2O75NrjlMGPTeaqhyXxZ7wv331pD0SBQs5Ktn1FtNzxbquIgLtVViw7j1jY38hHAXTeatXMzo02OcYGiiEPqZpOO7qYFqFmWwvw5IUoPh4oweiV4NMv6OeOeuSJP8h3AWVtb5/vLxqb4J/52795NTEwMderUoXnzzL/UR4xQa5TWrVuX72tcuHCBPXv2YGdnx8iRZbiPlyg8Xm3UjCCocuYn1xTveEDdOQ/+VKX5rXzO/NclRMPRPwzb7SeYX2rdqaqa3dDZ85Xph4f8iDoK66aYVigsbkkxsGeeYbvtc6blvIuDlZXqbZhV2fDC4OoBg4z+DiKP5H89mUn6ZO/cZ3stocGDplXY/n6jYFUCs2K52mNF7+oxdfNHx7cTtH0h59dU8oHmRpVKy+pauNhIWGfUKqVmC5U+nR3jWa6Yy3n7mZiRblr235w+pzWaGtbFZqRBuFGRimij9MkqfmV7hlTkjWMlGLNKff8Y82xZND+Ty6h8/815e3vj4+Nj8uXt7Y1Wq9V/VaxYkYoVK5rs8/b2LhGNvI8ePQpAixYtsnxetz801Mxmr1nQzb4NGDCAypUrZ3vcypUrmTRpEi+++CL/+9//OHUqH3fSRNlkZaV6wukcX1V8YwE48rvp3e8zgarxszkOLjT00nJyhyYP5+3aHSYZzcLdVjNV+ZUcr9I6Dv0Mv480vROdVxkZKvC6c0l9OL24+15xhqWqkuS+780vQLP3W9X/DsDORc08lkcPPKQKt+gEz1V/r3mRkWEawNUt5PRJHY0G+n4I3Ls5ER2mmpQXREaG6pW27EmYXUNV7Iy7mvvrSppL+1QFWt33uK0TDPnGvA9xnV9RxaWgbM7CZWTAqvGGrAZbJxj+oyqsk52K3oafiWC6Bi03ty8afh6jMW/NmpU11Da6OWHcTkAKmIicVKgCT6yB6kapun5Z94wW5sn3VNjFixdNtlNTUxkxYgRpaWm89dZbPProo7i6ugIQFxfH0qVLmTVrFgEBAaxYUfxrAi5dugSAl5dXls/r9oeHh+f7GuamT86bN89ke8aMGbzwwgt8+eWXeZqt1JUjvd+5c+eoU0emqUutxsMMqYfhuyE2ynLVBvPi+knTO+c6m2aoH8S2Dtm/NjVJrdfQafNs5hLUualQBdqON1Rn3DNPzco5uObtPKB60+jSFLXpqvz1o0vzdo7YSPjj8XsNSXOZEQmZr+5AVqmd/TGJt1QPNJ32L6r3XF71mwMXd8Gtc4BWfbh9fpf5De2vhhrWWGmszE/XtQSPpmrd1pHf1Pb2D1XlWcfsb+RlKe6aClQO/6I+cOtEHoE/HoOxG3P+f1eSnN0Kf442tATRWKuqh5Vrmfd6/Vq4RWq7rK2F2ztPpcnr9P9Y9UnMiZWVCpZ064uunzR/TZGuZQeon0t2Fcx7Xe2ucPJeZtL5IMN+CeBEbipUgSfXwq656v9/2/HFPaJSzWJzlx988AFbt25l586djB8/Xh+8Abi4uPDcc8+xY8cOtmzZwvvvv2+py+ZbfLwqRV6hQtY/tJycVHW1uLj8pWnt37+f06dPU6VKFQYMGJDlMc2bN+e7777j9OnTJCYmcv78eb755hsqVarEt99+y7Rp0/J1bVHGeLZUH14AuNdLrqglx6u7/2l31bZDJfUDGOBOuEppzMmxvyDh3vo9GwdoNS7n47PTfqKamQJ1F39/Pmbh0tPUTJexsI1wKSRv51n/MkQeJtfgDeD2BVjYF64ez/6YPfMgOVY9dqgI7V7M23jKGjsn1XLDeNZl46vmv9549s2rTdEHwz3eMl23ufNT816XkaHSev8cDZ83hK3vmQZvOhGHVLpdaUinPLEaljxiCN6s7WDkr6ZrfM1RVmfhIv9V64p1Gg42bZ+QE+O1a3nJJDAuemLO+jcd43Vw108Y1mUbp1BWkwBOZKNCFbVOuPd7eb+JK0xYLIBbvHgxPXr0yLHsvp+fHz179uT338vID90c6GbfRo4ciZ1d1ncIJ0+ezPjx46lXrx6Ojo7Url2bF198keDgYOzs7Pj666+5fPmy2dc8ceJEll8y+1bKaTSmaZQnVhbt9bVaNfNm/At62AJVMVAn+DO4nc1stVZr2jog4NH8NziuUAXaGV13z9dZN1LOyX+rIeZS5v1b3jX/w/CpjXB6c+b9ds6qGa57Q9XgunZXw3Px1+DnByF8b+bXJUSbpoR2mGT+TFNZ5tlSVYfVOfYXnA4077VnjI4r7OqTWXGpropv6Oz7Hm6ey/745Hj1/fxVACwermY5jMvIa6ygfj/14V4n9E9D65OS6vBvsPwpQ/8yWydVXvyBh/J+rvv7wu38tGDpzyWBvmXAvb8fV0/VsNzc9cHGbSry0krA+Ni8tLqoWtewLhtUNcqMdIg+Y9iXU0EaIYRFWCyAi4iIwN4+92ja3t6eyMjIXI8rbLqqk4mJiVk+n5CQAKjZw7xKS0vjzz//BPJXfbJRo0YMGjSItLQ0tm7dmufXizLIuKn35X0Qc6Xorn3kNwg1Kj7ScYqq1NlthlrLBmotxd/ZLLY/t830w0JBZ5bavajKaIOahTMODnOj1ZrOFhrn41/am3VQdr+URJU2quPbCaZfgLduwusRMPU/eHEvjNus0kUGf6M+fINq8PzbEDj9t+k5d32u+tyBqsBoHByXd51eVsGwzvbZuQfaibdMew1Zuv+budpNgIr3Zs8zUiHwrczH3L0NQR/DF41V24E7991ccPVUQeyUY/DYn6p5unGRlC3vQpgZ37fFYe83sHaiKsAEaub+ybWmszh5ZTwLF3sFvm0HSx8zbQ5cmvz9OtzUBT8aGPpd3maLTWbg8rB+3jjwdW9o/us0GtN/vws71Pesfj0d0gNOiCJgsQDOw8OD7du3c+fOnWyPuX37Ntu2bcuyaXVR8/FRv1SvXMn6g7Buv6+vb57PHRgYyPXr1/Hz86NDhw75Gl+9evUAiIqKytfrRRnjEWBalvnE6qK57tXjsNEoldenvUoNA5Xm18co7efU+qwrOhoHWPX6FvzubIX7ApzdX2adYpaVCztV9UmdofOhbm/D9pb31N3knOyaa5jBs7KBAZ+pMWXVwwrUjMEji8H63g2utCRY+qihImfcVVXRU6fTlLLb4y4/rKzVejidqKPqey0n57YZggbnGpmrnxUVWwfo/a5hO2yDYZ1T/HX45234vDEEfWjakkNjpapZPrZMBW7dXoOK99ZrW9vCw79AJd3vJq2awSlJM1FaLWz7wPSmjnN1eGpjwfs+VfJR/SONhW2AH3vAL4NUYY3sAnytFu5cVj01/35D/b1teRcO/aL+Xe5cVimsReXketMCNx0nQ+0ueTuH8exZXKTqfZib9NT7Zszy2LPNzyiz4PwO02qWFb1V+rMQolBZLIB79NFHuXXrFr1792bnzp2Zng8ODqZv377cuXOHxx9/PIszFK2AgAAADh8+nOXzuv1Nm+b9F78ufXL0aDNz2LNw+7b6Za5biyfKOUulUWq1EBOh0vhy6ymXHAd/PWm4s1qhKoxYaBqoNH0EvNsZtjfNgLRkw/a1/+Cc0Sxy+wn5G/f9Or6kPpiDGt/mmTkfr2Ncot+vu2pe2+sd9BUDb5xUaWnZuXnONGWt3YvmpR/5D4AxKw3r97TpqijH3m9VhUXj6pytnzXvvZQnNZuDv1HK3fY5OX/QNk6frNvL/HS0wtBoGHi3NWxvngkbXoUvmqjvpZR4w3O2Tip9dspxVVSnfl8VwN7PqaqajdOtsUuJg6WjzK92WpgyMtTPgZ2fGPZV8lUz0tWzLrSVZz3eUjOR988cXdgBvw6CH3vBqQ3qZ9jFXWqG+4/H4TN/NdP511jY+7VKyd31uVpL+MtA9dwH1eHr1qo67ebXCy8wDt+rZid1PJpB9zeyPTxbrp6GjAQwrxLlrfOGlE2NteotlxfGqeF3wk3/v0kBEyGKhEartcwK6KSkJPr06cOuXbvQaDS4u7vrZ6/Cw8O5fv06Wq2Wjh078s8//+DgULyVs1JSUnB3dycmJoYjR47QrFkzk+cDAgIIDQ3l4MGDtGzZ0uzzxsfHU716dRITEzl9+rR+Ji0vkpOTqVevHpcvXyY4OJhOnTrl+RzGdNUpT5w4UaDziGJ29Th819GwPXYDVK6tqjDaOWf+kJqWrJq7Xj0O146rEvfXjhvd6deAdxto0B/q91e/eHXn0Gph5bPqA47O6BVZl2KPCoUfuhpmPHq9q9LeANZMgCPqhgY1msD4YMt9mA79SzV51nlsmfrAm51r/8F8o1S8MasMlQlXPAvHlqnHrl4w6VDm6n5aLSwepmZ3QH1wmrAf7J0xW9RRtb4pwSh41lgZ/u76fQTtcumJVV7d//0/4uesi2BkZMCndQ1VRh/+BRoNKZIhZuvKITVDlB2HSmpWue34vKXPhW1Ss7m6Qjq1Oqvv65xKzxe27R/Cjo8N227+akyuNbN/TX5lZMCZv9Ua3CsHLH9+UDOHk4+CraNlzqfVwr7vIPBNwxpH2wowfmfeAymdH3sZ3v/AL6Hl2JyPP7FKBbGg0h0n5uPv7uvWhpk3WydDCni7CdDvw7yfT4hyqCCfzy02A+fg4MDWrVuZM2cOXl5eXLt2jf3797N//36uXbuGl5cXH374Idu2bSv24A3Azs6OiRPV3a8JEybo17wBzJ07l9DQULp27WoSvH399df4+/szc2b2d/tXrlxJYmIi7dq1yzF4O3XqFL/99hvJyckm+2/cuMGoUaO4fPkyAQEBdOzYMZsziHKneiPTtQWLBqgqdXO84P0qMMdHpWN92wG+aQsf1oTvu8CaF1V5+ovBpmlaaNV6ui3vwrdt4avm6o7zhZ0qpc84eOv8SvZ9tDyaQqunDds7/qdm+eKvQ+gyw/72Ey07E9JkhFp/prNpumpXkB3j2bcaTUx70PR4A6zufeiNvWKa0qhzcq0heAPV6ysvwRuoVNhxfxtVFcUQvLnUhJZP5e185UmNxtBwiGE76KOs010jjxiCN4011CkBvYa8WqrZ6vs5uUPv9+Hl49B9Zt4rZTboDz3fNmxfDDZdn1nUTgeaBm81m6tWB4URvMG9JvP94el/4Mn15veVquQLjUdAl2mqqJJ3OxWoZSX+mmnD6oJISVA3xja/ZgjerGzVOtn8Bm9gmgJpzjo4kwqUeUyf1DGehUs1fH6SAiZCFI1894HLiq2tLTNmzGDGjBlcvnxZX6zEw8NDv+asJHnzzTfZsmULe/bsoV69enTu3Jnw8HD27duHm5sbCxcuNDk+OjqasLCwHNelmdv77erVqzzxxBNMnjyZVq1a4ebmRmRkJIcOHSIuLg4vLy+WLVuGpjhTf0TJotGoDx1BWdzd1GaoAhnJZlZktHM2Td0CVeo+5BtDzzkd307QLZsCJTrdX1dpnYk31S/zf95S1crSU9TzLh6mhVgsQaOBB/8H33VSKYm3L6oCJV2nZz42NtI0IO3wkmkwWbkWtH5a3RkHCP4UWoxR6/xAVQg0TtP0625aDTAvqtaBcYFqNs+4F1OXV0pPT6/i0m3mvTYaWlUV9fgKaDrS9JizRu0DfNoZ/g2LW893VDPy2CuqsEnHl9T6yILO7HR6WaX56WaQD/4E1RtC62dyfp2l3Q5XwYlOlTqqcW9R/P1rNKrBdO3Oqr1C8FzDOkk7F/BsodbeebUGz1bg7Jb1eZLj1c+R2xfUOSLvLbE4vx3q9izYGG+egz/HqNL7Oi41VTsF79YFO7e7USETcypRGv/cyUsBE2N+3eDAgsz7pYWAEEXCogGcMW9vb7y9vQvr9Bbh4ODA9u3bmTNnDkuWLGH16tVUqVKFsWPHMmvWrGybfGcnKiqKbdu2YWtryyOPZHG31Uj9+vWZMmUKISEhHDt2jJs3b2Jvb0/9+vUZOHAgkydPpnLlPDZ9FWVf+wmqQfHl/apnWFoOM06g+i25NVDVFms0huqN1eyTY2V1nrBNqgeacWEPYxWqwfAfsy/QoT+uivqAuu4ltX18hUqr0WnzXOE03K3eUKWe6YLO4M/UB/r7mwOHzDcq0+1lup5Qp8s0OPK7Wk9097Zan6Sb3dj5CcRGqMfWdvDgpwWbTXT1UAUd/hgN4btUufzmT+T/fOWFu7+aedUF40EfqRsDxt+fJu0DelNiVPSE54PV95Gbv+XSHDUaGPQV3DxrCDg2Tlez9XktiJFfqUmw7AlVFRZUSuAji4snePZsCaN+VxkASTGq+FNW6wizYu+sfk7WaKxu+uj+Ps9tL9iYwjbByvGmN9hqdVZpwNkFk3mR1xm4G0bH5KWFgLFanUzTv/VjkQBOiKJgsTVwouSSNXBlWFqKCuSSYu79Gav+zEhTH+Cq1Tfvg2LMFVVCP2yzKgSQnqIClceWmZ+ClpEBP/Y0fOjRsa0AL58ovEbKSbHwdSuV6gTQYAA8usT0+c8bGZpk9/kAOkzMfB5Q5dx1M5w2jjD5X/V3O7+DIeWp86vQM4ty8Pmh1aoP3pV8pKmpuaLPwjetDR8cB38Lze8VxkqIhv/VRb8m7IU9liucUdLFRsGC7hB3L0PEsQo8FwSV815JOc/WTTatpjhsQeaZ0dLmxmn1fabzymnV2y8vMtIhaA7s/J/p/g6ToOe7ud8YM1dsFMw1CsSmX8j+521aMnzgobIWAF7cl/8g7ofupj/vndxg2tn8nUuIcqggn88tOgN3/fp1vv32W3bu3ElUVFSm9V06Go2Gc+dyaGgqhDCPjR3YVMt/Y2ydil4q5ar1M6pyW8QhVQ66ah6awFtZwYBPYUFP9B+gAZo9XnjBG6giLn1mG9K3wjbAmX8Msy+HfzEEb/YVoeWT2Z+r/QSVFpRwA9Luqhmem2cNwVtFH7Ue0FI0moKtfSmPqtWFpqPg6L0gfcfHKliwtoWzW9F/77nUzH96WGnk6gGjlsDCfpCeDHdvwZ+Pq3RduwqFd90jv5sGb62fKf3BG6j/l65eKuUV4HwQBOScWWMi9S78Odq0tYqds1rvZumiOi411Gxn0r0ZvhunwDebFkbRZwzBm5Vt3n7G38+vq2kAJ+mTQhQZixUxOXnyJI0bN2bWrFkEBQURFhbGxYsXs/y6cOGCpS4rhLA0exe1viE/v9g9W0IL41RATdFUVWzyMPgaFfzZOE2ldaWnqvRJnVZP5dxjzd4ZuhoVgTj0syoModP/48L9MCzM03W6KlACqoz5v7+rx/enT5a3NcSeLVQ6pc7VY/caaRdSok1UKGyYanT9Vqq4T1mg0ZhmH5zPYxrlnnmmwVvVevDstsKpiKrR3NfQO4d1cMbpk9XqFSyV17iQCUgBEyGKkMUCuGnTphEdHc2wYcM4ePAgsbGxZGRkZPslhCijer6jPqyAKotekDu85tIVNNF9qL99QX2AOr7CsHbNyta0AXh2WjyZeQ0dQP1+4P+gxYYsCqBKbUPaJKjKpymJpj0HS9L6t6IUMEqVctc5vkIV97G0u3dg2RjDOlzHKjDyl7KVCmwcwJ3bbn4grNWa9pOs318Fb4W5Psw4DfJGDuvgjIO7/Fag1PFpB9ZG/94FPZ8QwmwWC+CCg4Np0KABy5Yto0WLFjg757G8thCibHCqCs9th4mHoO+cortu9UaqWIpO8GemJc2bjlRpZrmxsVONgk32OajZN1FydJlm2vph3WRDmwwr28yzA+VJ7/dNC5hsedd0NqigMjJg1fOqYiMAGhjxk0rFLktqdwPuzeLGXzW/qfe14yr1WufBT1Sqd2EydwbO+LmCphjbOpr23qxVsJ61QgjzWSyA02q1NGvWTMreCyFUmmK1umpdXFHqPlP11gK1hu3WecNzHSaZf55Gw6BGU8N2p6lZz8qJ4lPJxzRdV1dGH8C3feF/YC7JrG1gxCJDv0FtBiwfp0rZW8Luz+H0JsN2jzegTg6Nyksrp6qqd6OOcR/InJxYZXjs1dq072NhMXcGzrjNQH6LlxgbMBe6vwGjlpafgkFClAAW+3TVqlUrwsPDLXU6IYTIO4eK0GdW5v31+pj2SsqNlZUqg/7AIOg4GTpPzf01ouh1fsU0hUunbjlNnzTmVFUVNbG9t2YzKQb+eFwVKSqICzth22zDdr2+0MmChX1KGuPA1Jx1cFqtaQCXVcuSwmA8A5dwAxJuZj4mJRFuGdUgsESRH2c3tSZV0suFKFIWC+DeffddDhw4wLp16yx1SiGEyLumj4BPe9N9eZl906nsC4/8ptLRLNWzS1hWRU9VmOZ+9foU/VhKohpNVNVDnRsnVepjQdahB75paOFQyReGfV/0M+1FyXgd3MXdqjhSTq6Gms78NxxcOOO6n7O76u+pk1VD7+jT6Ku02jhIVoEQpZhF2whMnjyZYcOG8dhjj9G7d2+8vLywyuYHe5cuRdRgVAhRvmg0qtH2jz1VgQXfjqppriibOk2FQ7+olFlQrR6kmbBB42EqqNj1udo+tR6CP1WzJnl19RhEHTVsj/jZNGgoi7zbqlnM1ET1PXZ5nyqfnx3j2TfvtkW3LlBXifLSHrV9/WTmNWn3V6A0t8G5EKLEsVgA161bNzQaDVqtlt9++43FixfneHx6erqlLi2EEKZqNIan/4Er+6HxiPJXTr48cakObZ5RVUcB/AfIv/f9erwFV4/D2X/U9vYP1Oxcg/55O8+/SwyPPVuBV0vLjbGksrFXN4F0f3fntmUfwBVX+qSOu79pAHe/6/8ZHVuOeiQKUQZZLIB74oknpICJEKLk8GiqvkTZ1/1NSE+DlPj8zSyVdVbWMPxHWNADbt0rZLJxuloraG3mx4C0FNPS+M0es/w4S6o6PQwB3PntwHtZHxf1r1FlTooufVLHeB1cVoVMrhvtk5L/QpRqFgvgFi1aZKlTCSGEEOazdYD+HxX3KEo2x0qqqMl3HSEjDWIuqXRKcxtLn/kbEu8VxrBxgMbDC2ukJY/xOrioUEiIBqdqmY8znn3zaQ+uNQt/bMbc72sloNWazkabVKDMQ1EnIUSJU4ZXHgshhBBCz90fGg4xbIfMN/+1R343PPZ/SAWE5YWbP7joekhq4XxQ5mOKO30STIOyu7dUNUqd5Hi4cynrY4UQpY4EcEIIIUR50e5Fw+PLIRBxKPfXxF+HM4GG7fKUPglqFsvPaBYuq3YCkYeNAiSNakFS1JyqQQWjmUHjdXA3wgyPbSuoYj9CiFIr3ymU77//fr4vqtFoeOutt/L9eiGEEELkg1dLVR3x8j61HfIdDF+Q82tC/wTtvcJjrp7g161Qh1gi1ekBR+8VcTm3PXN6ovHsm28HcPWgWLg/ABeD1eMbpwwFV4zTJ938y3brByHKgXwHcO+++66+6qS5dMdLACeEEEIUk3YvGAK4Eyuh93vZr9fSak3TJwMeLZ/l542D1tgIiD4DbvXVtlYLJ1Ybni+O9EkdN39DAGc8A3dd1r8JUZbkO4B75513LDkOIYQQQhQF/4Hg6gWxV1RBkwM/Qs+3sz428rDp7E15S5/UcXZTrReuHlPb57YZAriIQxBz+d6BxZQ+qeNuVF3SuBLl9ftm4IQQpZoEcEIIIUR5Ym0DbZ+Df+4FbQd/hs6vgl2FzMca937zaQ9V6xTNGEsiv+6GAO78dmj3vHpsnD5Zq5PqTVhc3LKpRGkyAyc94IQo7SQJWgghhChvWjyhilmAqlho3ONNJzUJjv1l2G72eNGMraSq08Pw+EKw6o2XkXFf+uSQoh6VKeP0yKQ7EHcV7t6BuEijY2QGTojSTgI4IYQQorxxrGwakIXMV7M1xsI2QFKMemxbofiDk+Lm0171wANITYArByDioEpFBdBYFW/6JECFKuDkbti+cdK0AqW9qypEI4Qo1SSAE0IIIcqjts8bHkeHqXVdxoyLlzQcDPYuRTOuksrWQVWY1Dm3LXP6pLN75tcVNeMZtuun4Pp/hm03f9PqmUKIUkkCOCGEEKI8qlYX6vU1bBs39o6NNO13Vt7TJ3WM+8Gd21pyqk8aM14Hd+OkaTETSZ8UokyQAE4IIYQor9q9YHh89h9Dut3RpaDNUI8r+YJvx6IfW0lkvA4u8ohhbVlJSJ/UMV4Hd/2UFDARogySAE4IIYQor/y6mX6o3/dd5t5vzR6Txs861RuZrjHTqd0FnKoV/XiyYhzA3TglLQSEKIPkJ7IQQghRXmk0prNw/y6F03/DrXOGfQGPFv24SiqNBup0z7y/pKRPgmmQlhwLCdcN29LEW4gyQQI4IYQQojxr8jBUqKoep92FVeMNz9XuApV9i2dcJZXffQGcxlo1Ry8pHCuBi0cW+yuDczH2qBNCWEy+G3nnJC0tjZs3b5KcnJztMT4+PoVxaSGEEELkha0jtBoHO/+ntpPuGJ5rNrpYhlSi3T8D59cVnKoWz1iy4+YPcVH37XtAKlAWM61Wi0b+DQpk26VtbL20lSF1h9C6RuviHk6xsWgAt2XLFmbPnk1ISAipqanZHqfRaEhLS7PkpYUQQgiRX62fgV1fQIbR7247F3igBM0slRQuNaB6Y7h2XG2XpPRJHfcHTKuIglSgLEZnbp/hhS0vEJsSS9sabens1ZkuXl2o4VSjuIdWatxNu8vH+z9mxZkVAPx98W+WDFhC/cr1i3lkxcNiAdz69esZOnQo6enpVK5cmdq1a+PiUs57xgghhBClgUsNaDwcQv8w7Gs8FOwqFN+YSrK+H8C6KVCjMTQdVdyjySyrYiVSgbJYaLVaZofM5lriNQCCrgQRdCUIgHqV69HFswtdvLrQ1K0pNlaFkhhX6p2/c55XdrzC2Ttn9fuS05N5dcer/DHgDyrYlr+fUxb7TnnvvffIyMjg888/Z+LEiVhbW1vq1EIIIYQobO2eNw3gJH0ye37dYPK/xT2K7GVVrEQqUBaLvVF7OXz9cJbPnbl9hjO3z/DT8Z9wtXOlo2dHRtYfScvqLSXV8p41Z9fwwb4PuJt2N9NzF2Iu8OG+D5ndaXYxjKx4WayIyYkTJ2jfvj2TJ0+W4E0IIYQobWo2h5ZPqceNhoF3m+Idj8g/twaZ90kFyiwlpSUxfcd0Bq4ayP6o/RY9t1ar5Zt/v9FvB7gFMPqB0fi4ZK4DEZsSy6YLm3jq76d4bMNj/H3xb9Iyyu9yo8TURF4Pfp03d79pErw92fBJnmz4pH57zbk1rD23Nk/n1mq13Lx702JjLQ4Wm4FzdnaWwiRCCCFEaTbwC5UeaOdU3CMRBeFQEVw9ITZCbTu5lZw+dSXMLyd+YdPFTQC8vut1Ng/fbLFUxuCIYEJvhOq3X231Ks3cmzGjzQwuxlwkOCKYnVd2cvDaQZNg7fjN47y641U8nT0Z03AMQ+sOLVdpgmG3wnh1x6tcjL2o31fRviIfdPyArt5dSc1I5eiNo/x7418AZofMpkm1JtSuWDvXc99JusNbu98iPC68VKdfWmwGrlevXhw8eNBSpxNCCCFEcZDgrWwwTpmU9Mks3bx7k4XHF+q3ryVeY8eVHRY59/2zb508O9HMvZl+u1bFWoxpOIYFfRawa9Qu5nabS1uPtibniIiP4KP9H9F7eW++OvwV0XejLTK2kmzlmZU8tuExk+CthXsLlg9cTlfvrgDYWtnySZdPcLVzBVSBk1d3vEpSWlKO5z587TAj1o0g6EoQF2Iu8MG+DwrtfRQ2iwVwH3/8MbGxscyYMUMqTAohhBBCFKeazY0eNyu2YZRk34d+T2Jaosm+P0/9aZFzB10O4r+b/+m3JzSbkO2xTrZO9PbtzY99fuTPh/7kwdoPYq0xLEeKTYllwbEF9Fneh5+O/WSR8ZVEB64e4J0975CSkQKABg3PNnmWn/r+lKlip4ezB7M6ztJvn759mk8PfprledMz0vkh9AfG/T1OX0wG4HbSbVLSUwrhnRQ+i6VQ/vzzz/Tv359PP/2UFStW0K1bN7y8vLCyyhwjajQa3nrrLUtdWgghhBBCGGv3AkQeBm0GtJ9U3KMpccJjw/kr7K9M+/dG7eVizEVqVayV73NnaDNMZt+6eXWjcbXGZr22YdWGfNzlYya3mMzik4tZcXqFPshMzUjli8Nf4F7BnYF1irbFh1ar5ULsBa7EXaG5e3Nc7Cxfad44OK3iUIU5nebQwbNDtsf38OnB6AdGs/jkYgD+DPuT1jVa07dWX/0x0XejmRk8k5CoEP0+G40NU1pOYUzDMVhpLDaXVaQ0Wq1Wa4kTWVlZodFoMOd0Go2G9PR0S1xWmKFRo0aAKjQjhBBCCFHevRL0CoHhgQB4OnviYO3AuZhzAIxpOIbprafn+9xbwrfwctDL+u1lDy3jgar5KyITmxLL8tPL+e2/3/QplPbW9vzW/7d8n9NcSWlJHLx2kJ1XdhJ8JZgr8VcA1f5g6YCl2FvbW+xaYbfCGLFuhH77h94/0L5m+1xfl5KewphNY/Sznc62ziwbuAxvF2/2Ru5lZvBMbiYZCpZ4OnvySZdPaOrW1GJjz6+CfD636AycEEIIIYQQJVnojVB98AYwqfkk4lLi9GuiVp9dzaTmk3C0cczzue+ffevl06tAgZarnSvjGo+jX61+PLL+Ee4k3yE5PZmXg17mjwF/UMmhUr7PnZXI+EiCrwQTHBHMvqh9JKVnXld25vYZFh5fyAsBL1jsur/+96v+sX8Vf9p5tDPrdXbWdnza5VMeXv8wCakJxKfGM23HNDrU7MCPx35Ei2Fiqbdvb97t8K5+7VxpZrEA7sknn8z9ICGEEEIIIYqJVqtl7qG5+u0HqjxA/9r9SUxN5PNDn5OYlkhcShybLmxiWL1heT5/4MVAk4bTLzSzTJBT07kmn3T5hOe3PE+GNoOI+AhmBM/g257fYm1V8PZdp2+f5oOQD7LtWXe/n479xEN+D+Ht4l3ga19NuMrG8xv122Mbjc1THzxvV2/ebf8u03ZOA+DEzROcuGmY1bKzsmN66+mMbDCyzPTXK52Jn0IIIYQQQuTRjis7OHTtkH57aqupWGmscLZzNllX9sepP8xaFmQsPSOdb49+q9/uW6sv9SvXL/ig72lfsz2TW0zWb++J3GMy25cfKekpfH3kax5Z90iWwZujjSM9vHvwbvt3WTNkDW6ObgAkpyfzyf5PCnRtnd9P/k6aVhVArOFUgz61+uT5HP1q92NE/RGZ9tdyrcWSAUt4xP+RMhO8gQVn4Izt3buX4OBgIiJU/xFPT086d+5M+/a557IKIYQQQghhaWkZaXx+6HP9dkfPjiapeo80eIQ/w1QVypO3TnIs+lie1kpturiJCzEXAFVB0ZIphjpPNXqK49HH+Sf8HwAWHFtAo6qN6OnbM8/n+vf6v7yz5x3Ox5w32V/LtRadPDvRxasLLau3xM7aTv/cq61eZUbwDACCrgQRdDmIbt7d8v1+4lLi+Ou0oZjMmAfGYGtlm69zzWg9g6M3jnLm9hkABvoN5M12b5baXm85sWgAd/r0acaMGaPvB6e7c6GLeFu1asXixYupV6+eJS8rhBBCCCFEjtacXaMPVjRoeLnFyybP16tcj5bVW+pn6P4M+9PsAC4tI43vjn6n337Q70HqVKpjoZEbaDQaZnWcxbk75/Tv5fVdr7O00lL8KvqZdY7E1ES+PPwlS08tNVkj5uHkwVvt3qKzV+dsX9u/dn+Wn1nOgasHAPho/0e082iHg41Dvt7PitMrSEhNAMDF1oXh9Yfn6zwADjYOqhVD2J88UOWBAgWWJZ3FUiijoqLo2rUrBw4cwMPDg5deeonPP/+cL774gsmTJ1OzZk0OHDhAt27diIqKstRlC+zu3bu8/fbb1K9fHwcHB2rWrMm4ceP0s4fmqlWrFhqNJtuvU6dOZfm69PR0Pv/8c5o0aYKjoyNubm6MHDmSkydPWuLtCSGEEEKUe4mpiSbphgPrDKRBlQaZjhvVYJT+8eYLm7mddNus8284v4Hw2HAArDRWPN/0+QKOOHtOtk580f0LnG2dAUhMS2TK9inEp8Tn+tpdEbsYsmYIS04t0QdvGjQ85v8YqwavyjF4AxVAvt7mdWw0ag4oIj6Cn47nrzddanoqv538Tb89ssFInGyd8nUunSoOVXgh4IUyHbyBBWfgZs+ezbVr13j55ZeZM2cOdnZ2Js9//PHHzJw5k7lz5/Lhhx8yb948S10635KSkujRowchISF4eHgwePBgLl68yM8//8z69esJCQnBz8+8uxk62RVzqVixYqZ9GRkZPPzww6xatYpKlSoxYMAAoqOjWb58ORs2bGD79u20adMmX+9NCCGEEEIoi08u5sbdG4AqajGx2cQsj+vp05OqDlW5mXSTlIwUVp9dzVONn8rx3KkZqSazbw/5PVSgPnLmqF2xNh90+oDJ29WauAsxF3hz95vM7TZX39ssKS2JK3FXCI8L53LsZf698S9bL201OY9fRT/e6/AezdybmX3tupXrMrrhaBadWATAwmMLGeg3EB9Xnzy9h00XN3E98ToANlY2PPbAY3l6fXlmsT5wtWvXxsHBIceZI61WS8OGDUlKSuLChQuWuGyBvPnmm3zwwQe0b9+ewMBAnJ3VnYy5c+fyyiuv0LVrV4KCgsw6V61atQgPD8/Tgtcff/yRZ599lnr16hEcHEz16tUBWLFiBSNGjKBu3bqcPHkSG5uCxdnSB04IIYQQ5dWtpFs8uPJBfareU42fYmrLqdkeP+/IPH4I/QFQfcM2DtuYY8PnpaeW8uG+DwGw1lizbsg6vF0LXp3RHMZjBbWuLzU9lfDYcK4lXsv2dTYaG55u8jTPNX3OZI2buRJSExi0ahDX76oArJNnJ77t+a3ZhUK0Wi3D1w3Xr1cbUncIszrOyvM4SrOCfD63aAplixYtcjxGo9HQokWLEpFCmZKSwtdffw3AN998ow/eAKZOnUrTpk3ZsWMHhw4dyu4UBTZ3ripj+8knn+iDN4Dhw4czaNAgzp49y5o1awrt+iVJVHwU7+55l/n/zic1I7W4hyOEEEIICwiPDed49HHSMtKKbQzfH/1eH7y52rnydOOnczz+4foPY61Rpfkj4iPYHbE7y+O0Wi0Ljy9kzr45+n2D6w4usuAN4MWAF+no2VG/vTtiN/uv7s8xeGtctTF/PPQHE5tPzFfwBiqNc1qbafrtXRG72H55u9mv3xO5Rx+8gWodIMxnsRRKV1dXLl++nOtxly9fxtW1+Bvo7d69m5iYGOrUqUPz5s0zPT9ixAhCQ0NZt24dLVu2tPj1L1y4wMmTJ3F0dGTAgAFZXn/t2rWsW7eO4cPzv6CzNIiMj2Ts5rFEJajA/k7yHWa2nVnMoxJCCCFEQeyJ3MMLW14gQ5uBq50rXby60N27Ox09OxZ4rZNOXEocG89vJDIhkrtpd7mbdpektCSTP/+7+Z/++OeaPkdF+8zLWozVcKpBN+9u+nTDP8L+yLQ2LCktiXf3vsuG8xv0+1xsXQp17VtWrK2s+bjzx4xaP4or8VcyPW+jscHLxQtvF298XX1pUq0JfWv1tUjvuL6+fVnusZx9UfsA+Hj/x7Sv2d6sBug/n/hZ/7iLV5dCKfhSllksgGvfvj3r169nw4YNWQYkABs3bmT37t0MHDgwy+eL0tGjRwGynTXU7Q8NDc3Tef/3v/9x7tw57O3tadSoEUOHDsXNzS3b6zdu3Bhb28zlUvN7/dLmasJVxv09Th+8ASw5tYTG1Rqb9GMRQgghROny8/GfydBmABCbEsv68+tZf349tla2tPFoQ3ev7nTz7kZ1p+q5nClr5++cZ+K2iVyOy30CAVQ65KP+j5p17CMNHtEHcMFXgrkSdwUvFy8AriVcY8r2KRy/edzk3F/1+AoPZ488vouCq2hfkZ/6/sSiE4uw1ljj4+qDr4sv3q7eeDh5YGNVKF3DVEGTtq8zfO1w0jLSiEyIZEHoAl5q8VKOr/vv5n/6oA9k9i0/LPYv+tprr7Fx40aGDh3KI488wmOPPUatWrUACA8PZ+nSpfzxxx9YWVnx2muvWeqy+Xbp0iUAvLy8snxetz88PDxP550+fbrJ9ssvv8y8efMYN25coV9fl0t7v3PnzlGnTsm7s3Et4RpP//00EfGZK36+v/d96leun2WFKCGEEEKUbLeTbutLzd8vNSOV3RG72R2xm9n7ZtOoaiOeD3g+T5UDd0XsYtqOacSn5l55EVSlxemtp5udMtjWoy21XGtxMfYiWrT8dfovXm75MsduHGPy9sn6gigAraq3Ym63uVR2qGz2+C2tpnNNXm/7epFf16+iH080fIKFxxcCsOjEIgbVGZRjEZdfTvyif9yoaiNaVW9V2MMscyw6A/fzzz8zfvx4fv/9d5YsWWLyvFarxdHRke+//5527dplc5aiEx+v/sNXqJB1cz8nJzW1HxcXZ9b5Bg0aRPfu3WnZsiVubm6cP3+ehQsX8uWXX/LMM89QtWpVBg8eXGjXL21uJN7gmcBnuBR3Sb/v6cZPs/zMcmKSY0hKT2LK9in88dAfuaY6CCGEEKJk2XZpG+nadADcHN14rc1rBF0OYmfETmKSY0yOPXHzBJO2TaKXTy9ea/NajjNyWq2WxScX8+nBT/Wze6D6k1V1qIqjjSMVbCvgYO2Ao40jjjaOONg44FfRL0+VIa00VoxsMJJPDnwCwKozq6jlWovZIbNJyUjRH/dIg0eY0WZGvptPlwXjm45nw/kNXEu8RmpGKm/veZsJzSbQ3L15poA5Mj6Svy/+rd8e23is2YVPhIFF51RHjx5Nt27dWLBgAbt27SIyMhKAmjVr0rlzZ55++mm8vYtuYWdR+uqrr0y2GzVqxGeffYa/vz/PPfccM2bMMAngCkN2VWyym5krLtF3o3km8Bkuxl7U73u55cuMazyONjXa8PyW59Gi5Ur8FV7f9TrzeszLsfqTEEIIIUqWwPBA/eNevr3oU6sPfWr1IS0jjSPXjxB0OYjtl7ebpD9uubSFvVF7mdR8EqMajMq0Tis1PZUP9n3AijMr9PscbRyZ03kOPX16Wvw9DKoziK8Of0VSehK3k2/z9p639c/ZaGx4rc1rPOL/iMWvW9pUsK3A9NbTeWXHKwAcuX6EZwKfwcHagRbVW9Deoz3tarajfuX6/Pbfb/rA3tPZk14+vYpz6KWWxZNivby8eO+99yx9WovTVZ1MTEzM8vmEhHtd4V1cCnSdp59+mjfffJOwsDAuXryoTystquuXNLeTbvNs4LOcjzmv3zep+STGNVYpph08OzCx+UTmHVF9Ande2cn3od/zQsALxTJeIYQQQuTNnaQ7Jmuc+vj20T+2sbKhdY3WtK7RmldbvcqpW6f4+MDHHLqmqn4npCbw0f6PWHduHe+0f4cHqj4AqM8PLwe9rD8OVLGRr3t8XWjLLSraV2SA3wCTgBGgkn0l5nabS+sarQvluqVRb9/edPPuRtDlIP2+pPQk9kTuYU/kHjikmmzrqoECjGk4ptDW55V15XZaw8dHNRu8ciVzxR7j/b6+vgW6jpWVlX79mXH7hKK6fkkSkxzDs4HPcvbOWf2+FwJe4Lmmz5kc90yTZ0zy4Of/O5/gK8FFNUwhhBBCFMD2y9v1syzVHKvR3D1ztW9QRTAeqPoAC/su5P0O7+NqZ6hSfuLmCUZtGMX/DvyP0BuhPLrhUZPgLcAtgKUDlhb6WvmRDUaabNetVJelA5ZK8HYfjUbD590+Z2abmXTy7JRlJcpbSbdITk8GVHA8tO7Qoh5mmVFuA7iAgAAADh8+nOXzuv1NmzYt8LVu374NGNa1GV//+PHjpKZm7ntmyeuXBLrgLex2mH7fs02ezXJmzUpjxQedPsDHRQW5WrTMCJ5hdpUpIYQQQhSfv8MNa5x6+vTMtWS9lcaKofWGsnbIWgb6GSpQZ2gz+PW/X3l84+MmBc8G1RnEwr4LqeZYzfKDv0/Dqg15/IHHsbOyY4DfABY/uFhfjVKYsrGy4bEHHmN+r/nsGrWLhX0X8myTZ2larWmmpTCP+z9OBdus60CI3Gm0Wq02Py+0srLCysqK//77j/r162NtbX4/CY1GQ1pa8TV0BNXI293dnZiYGI4cOUKzZs1Mng8ICCA0NJSDBw8WqA/ciRMnaNKkCY6Ojty+fRs7O8NizoYNG3Ly5ElWrVrFkCFDTF43ePBg1q5dy/LlywvcB64gnd4tIS4ljucCnzMpt/tUo6d4ueXLOS5cPXP7DI9vfJy7aXcB8K/iz6/9fzWrv4gQQgghil5Mcgzd/uxGmlZ9zlvYd2GeZ6v2Ru5ldshsk0JnoCpJTmk5hacaPVXkhS/SM9It0jutvIpJjuHg1YMcun6Iqg5VGdtobLn/+yzI5/N8z8D5+Pjg7e2t72Hm7e2Nj4+PWV8loZCJnZ0dEydOBGDChAn6NWcAc+fOJTQ0lK5du5oEb19//TX+/v7MnGnaZHrjxo1s27Yt0zVCQ0N5+OGH0Wq1PPPMMybBG8DUqVMB1Xrg+vXr+v0rV65k7dq11K1bt9ALnxSFA1cPcOKm4Ztz9AOjcw3eAOpVrsd7HQzrKU/dOsXskNnk856DEEIIIQrZ9svb9cFbFYcqtHDPut9uTtrXbM/KwSsZ33S8fo1UBZsKfNn9S8Y1HlcsVQvLe7BRUBXtK9LTtyfTW0/n6SZPy99nAeV75eDFixdz3C4N3nzzTbZs2cKePXuoV68enTt3Jjw8nH379uHm5sbChQtNjo+OjiYsLMxkLRvA/v37ee+99/D19SUgIIAKFSpw/vx5Dh8+TFpaGt26deOjjz7KdP1x48axceNGVq1ahb+/Pz179iQ6OpodO3bg6OjI4sWLsbEp/Ys7e/j0YHan2by1+y0eafAI01tPN/uHb//a/Qm9Ecrik4sBWHtuLfbW9rza6lWZehdCCCFKmMCLRtUnfXrl+4O6vbU9E5tPZGCdgeyL2kcnz07UdK5pqWEKUaqV2zVwAA4ODmzfvp233nqLChUqsHr1asLDwxk7diyHDx/Gz8/PrPP07duXcePG4erqyu7du1m+fDlnz56lU6dOLFiwgC1btuDomDntz8rKir/++ovPPvuMmjVrsn79eo4dO8bw4cM5ePAgbdu2tfRbLjaD6gzit/6/MbPNzDzfOZvaaiotqxtmQv86/RfD1g4zqXAlhBBCiOIVmxLL3qi9+u0+tfrkcLR5fF19GdlgpARvQhjJ9xq4+40bN45OnToxbty4HI9btGgRO3fuzDS7JQpPca+Bs4Tou9G8sOUFTt06ZbJ/ZP2RTG01FSdbp2xeKYQQQoiisPbcWt7Y9Qag0ie3PrxVysQLkY1iWQN3v0WLFrFr165cj9u9eze//PKLpS4ryolqjtVY8uASng94HhuN4ZfBstPLGLZmGCFRIcU4OiGEEEIYp0/28OkhwZsQhaTIUyhTUlLyVLFSCB1ba1smNJvAkgFLaFDZ0PclMiGSZwOf5f297xOfEl+MIxRCCCHKp7iUONWw+R7j5t1CCMsq0gBOq9Vy+PBh3NzcivKyoox5oOoDLB2wlBcDXjSZjdOtjdt4fiNpGcXbpkIIIYQoT4IuB5GaofraVrKvJI2uhShEBZrb7tGjh8n25s2bM+3TSUtL49y5c1y9epUxY8YU5LJCYGttywvNXqC7T3fe3PWmvkF4VEIUM4Jn8MXhLxjTcAzD6g2T9XFCCCFEIQsMN6RP9vTpKemTQhSiAhUxsbIyTOBpNJpc+3PZ2trSr18/fvrpJ6pVq5bfy4o8KgtFTHKSmp7Kj8d+5IfQH/S9Z3RcbF14uMHDPOb/GNWdqhfTCIUQQoiyKz4lnq5/diUlIwWA73t9TwfPDsU8KiFKtoJ8Pi/Q7ZELFy4AKjXSz8+PESNG8L///S/LY+3s7KhWrZq+8bcQlqKbjetTqw8Ljy9UKZT3Arm41DgWHl/Ir//9yoO1H+TJRk9Sv3L9Yh6xEEIIUfJtvriZdefW0d27O8PrDc+2DdCOKzv0wVtF+4q09pD0SSEKU4ECOF9fX/3jd955h+bNm5vsE6Io1alUhw86fcCk5pNYcnIJf53+i/hUVdQkLSONtefWsvbcWh5p8AhvtH0jz/3ohBBCiPLiz1N/MnvfbAB2XtnJnsg9zOo4K8tlCSbVJ717YGslN+uFKEwW6wMnSq6ynkKZnfiUeFacWcHik4u5mnDV5LlPu35K31p9i2lkQgghRMm15uwa3tz9Zqb9tSvW5otuX+BXyU+/LyE1gS5/dNHPwM3vNZ9Onp2KbKxClFYlog/c/e7cucPly5e5dOlSll9CFDZnO2eebPQkG4dt5KPOH1G7Ym39c18d/kpfLUsIIYQQyuYLm3l7z9v6beNqzxdiLjBqwyg2X9ys37fzyk598OZq50pbj7ZFN1ghyimLBnBXr17lmWeewd3dnapVq1KrVi1q166d6cvPzy/3kwlhIbZWtgzwG8AX3b/AWqN6EF6Ku8Ty08uLeWRCCCFEybH90nZmBs8kQ5sBQFWHqqwYvILnmj6nP+Zu2l2m7ZjGx/s/JjUj1SR9srt3d0mfFKIIWCyAi4qKolWrVixcuBB7e3vc3NzQarW0a9cOd3d3fYXK9u3b07lzZ0tdVgiz+VX0Y2i9ofrt745+R0JqQjGOSAghhCgZ9kTs4ZUdr+iLgFWyr8SCPgvwq+jHpOaTmNdjHi62LvrjF59czNN/P82uiF36fX1qSfNuIYqCxQK42bNnExkZyfvvv8/ly5fp378/Go2G3bt3ExUVRVBQEP7+/mg0GjZt2mSpywqRJy8GvIijjSMAt5JusejEouIdkBBCCFHMDlw9wOTtk/VLC1xsXfi+9/fUq1xPf0w37278+dCfNKjcQL/vyPUjJKUn6V/T3qN90Q5ciHLKYgHc5s2bqV27Nm++mXnRK0CXLl0IDAzkyJEjzJo1y1KXFSJP3Cq4MaahoZH8Lyd+IfpudDGOSAghhCg+R28cZeLWifpAzNHGkW97fUvDqg0zHevt6s3iBxczqM6gTM919+mOrbWkTwpRFCwWwEVERNCsWTP9trW1WmuUnJys3+fp6Un37t1ZtmyZpS4rRJ491egpKttXBlQu//x/5xfziAy0Wi3BV4I5detUcQ9FCCFEGXfy5kle+OcFEtMSAbC3tuebnt/QzL1Ztq9xsHFgdsfZvNXuLZP1bg/WfrCwhyuEuMdiAZyrq6vJdqVKlQAV2BlzcHDItE+IouRs58z4gPH67RVnVnAh5kIxjkjRarW8sesNXtz6Io+uf5QTN8tX2wchhBBF53bSbZ7f8jxxqXGAKvj1ZfcvaV0j9ybcGo2GkQ1G8tuDv/GQ30NMazWNjp4dC3vIQoh7LBbA+fj4mLQHaNy4MQAbN27U70tMTGT37t14eHhY6rJC5MvI+iPxcvYCIF2bzleHvyrmEcFv//3GuvPrAEjTprHqzKpiHpEQQoiyas3ZNdxKugWAtcaaT7t+mucgrFHVRszpPIcnGj1RGEMUQmTDYgFcjx49CA0N5caNGwAMGjQIJycnpk2bxmuvvca8efPo3r07165do3///pa6rBD5Ymtty0stXtJvb7m0hX+v/1ts49kftZ+5h+aa7NtxZYe+eqsQQghhSbobhgBPNX6KHj49inE0Qoi8sFgA9/jjjzNs2DD+++8/AKpUqcL333+PVqvlk08+YcqUKRw4cICGDRvywQcfWOqyQuRb31p9TRZpf37o82IJmKLio3h1x6uka9NN9l9NuMrp26eLfDxCCCHKtrBbYSa/XwbXGVyMoxFC5JWNpU4UEBDA0qVLTfY9+uijdOzYkY0bN3L79m3q16/PoEGDsLWVKkWi+FlprJjacirPBD4DwOHrh9lxZQfdvLsV2RiS0pKYEjSF28m3AbCzssOtghsR8Wqd6I4rO2hQpUFOpxDl1PmY82wJ30LwlWCsNFY8WPtBBtUdpG+TYa7U9FQi4iNwr+BOBdsKhTRaIURJsuH8Bv3jJtWaUKtireIbjBAizywWwGXHx8eH559/vrAvI0S+tPVoS0fPjuyO2A3AF4e+oJNnJ2ysCv2/Blqtllkhs/jv5n/6fW+3f5uohCi++fcbAHZc3sFzTZ8r9LGIkk+r1XL69mm2XNrClvAtnL1z1uT5w9cPM+/feYysP5JH/R/FrYJbjuc6Hn2ctefWsvniZu4k38HWypaW1VvSxasLnT07F/gDXVJaEgeuHmB35G52R+zm5t2btKjegn61+9HduztOtk4FOr8QIn/SM9JNAriH/B4qxtEIIfJDo7VQzliPHj3o168f06dPz/G4Tz/9lI0bN7Jt2zZLXFaYoVGjRgCcOCFVDbMSdiuMh9c9jBb1X+G9Du8xrN4wAFLSU4hNiSU2OVb9mRJLWkYaLnYuuNq54mrnioudCxVsK2ClyVtG8tJTS/lw34f67Uf9H+X1tq9z6tYpHl73MAAaNGwbuY1qjtUs9G5FaXPy5kk2X9zMlvAtXIq7lPsLABsrGx6s/SBPNHzCZAY3Mj6S9efXs+7cOi7GXszxHD4uPnT26kwXzy60rNESe2v7HI/XarWEx4azK2IXuyJ2cfDaQZLTk7M81sHagS5eXehfuz+dPDvhYONg1vsSQhTc3si9PPePujFoo7Fh68itVHGoUsyjEqL8Kcjnc4tNMwQFBVGrVq1cjwsLC2PHjh2WuqwQBdagSgMG1hnI2nNrAfho/0d8c+QbYlNi9Y1Nc2OlscLZ1hkXOxeqOlSlfc329KnVh3qV6qHRaDIdf/jaYT7Z/4l+u4V7C6a1nqbGU7kB1StU51riNbSovnBD6w21wDsVpYlWq+Wzg5/xy3+/ZHtMg8oN6OXbizvJd1h5ZiV30+4CkJaRxtpza1l7bi3tPNrR2bMz2y9v5+C1g2Zf/1LcJX4/+Tu/n/wdRxtHvF28sbGyUV8a9ae1xlr9aWXNmdtn9Km/uUlKTyIwPJDA8ECcbJ3o4d2DfrX70aFmhyKZ/RaiPFt/fr3+cUfPjhK8CVEKFflvyqSkJGxs5Be0KFkmNpvI5gubSclI4W7aXf0HYXNlaDP0M3QR8RGERofyfej31K5Ymz6+fehbqy91K9VFo9FwLeEaU4OmkqZNA8Dd0Z3Pun2mb4iq0Wjo6tWVZadVw/udV3ZKAFfOZGgzmLNvDn+E/ZHpuSbVmtDLtxe9fXrj7eqt3/9isxdZfno5v5/8neuJ1/X7Q6JCCIkKyfI6Lau3ZFCdQfTw7sG5mHMEXwlmZ8ROztw+Y3Lc3bS7+Sqo42TrRDuPdnTy7ET1CtXZemkr/4T/Q2xKrP6YhNQE1p1fx7rz62hYtSHzeszDvYJ7nq8lhMhdYmoiW8K36LcfqiPpk0KURhZLobSysmLs2LEsXLgw22NiY2Np3rw56enpXLx40RKXFWaQFErzzD86n2///TbL52ytbFXKpL0r1hpr4lLiiEuJIzEt0ezz165Ym761+rInYg+h0aGASnVb1G8RAW4BJsfuvLKTCVsnAFDBpgLBo4Kxs7bL5zsTpUmGNoNZIbNYfnq5fp9fRT8erv8wvXx7UcOpRo6vT01P5e/wv/n1xK+cvHUy0/O1XGvxkN9DPFTnITydPbM8x9WEq+y8spPgiGD2Re3L0w2N+pXr08mzE508O9HMrRm21qZFq1LTU9kTuYdNFzex7dK2TOeu6VST+b3n41fRz+xrCiHMs+H8Bl4Lfg0AZ1tnto/cLinMQhSTgnw+L1AA5+dn+AV78eJFnJ2dqVYt67U6aWlpXLt2jbS0NCZOnMiXX36Z38uKPJIAzjxarZbD1w8TkxyjD9Zc7VypaF8RB2uHLFMhUzNSSUhJIDYllriUOGJTYgm7FUZgeCDHoo/les2327/Nw/UfzrQ/KS2Jzn901qdwft/rezp4dij4mxQlWnpGOu/ufZfVZ1fr9zWt1pT5vefjaueap3NptVoOXjvIkpNLiIiPIMAtgIF1BtKkWpMsv5ezk5yeTOiNUP36z/SMdNK0aaRlGL7Stem42rnSzqMd1Z2qm33uu2l3Cb4SzLrz6wi6HKTfX9G+Il/3+Jpm7s3Mf8NCiFw9v+V5fdGuoXWH8n7H94t5REKUX8UWwFlZGYo2aDSaHHto2draUrNmTQYNGsScOXOoUEHKVRcVCeCKR0R8BP9c/Ie/L/7N8ZvHMz0/vN5w3u3wbravn7Rtkv5Dra7AiShZktKSuJF4g+T0ZJIzkklNTyU5PZmU9BRS0lNITk/GztqONh5tcg3A0jPSeWv3WybNdZu5NWN+r/k42zkX9lspdov/W8wnBz7RFxOyt7bn4y4f09OnZzGPTIiyIfpuND3/6kmGNgOAhX0X0rpG62IelRDlV7EVMcnIyNA/NieFUojyxNPZk7GNxzK28ViuxF3hn/B/CLwYyPGbx+nm3S3XgKyrV1d9ALfzyk5mtpmZp5kTkT2tVssvJ37hr9N/Ua9yPQbWGUgXzy6Z0v2ye+3RG0dZfno5geGBZqUX2lvb08e3D8PqDaNl9ZaZ/h3TMtJ4Pfh1Nl3cpN/XsnpLvun5Tbkptz+64WiqVajG68Gvk5qhAuGpQVN5vc3rPOL/SIHPv+3SNo5FH6OtR1taV2+NtZW1BUYtROFIy0hjQegCNlzYgAYNVRyqUNWxqv7Pqg7qq4pjFRpUbmBWD8dNFzbpg7caTjVoWb1lYb8NIUQhsdgauF9++YW6devSsWNHS5xOWJDMwJUsuv9yuQVj1xOv0/Mvw+zDykErqVe5XqGOrTxIzUjl/b3vm6QpAlS2r0z/2v0ZVGcQDas2zPTvcyfpDuvOr2PlmZWZerDlha+rL8PqDWNQnUFUc6xGakYqM3bO4J/wf/THtK3Rlq96fFUuG2sfuHqAydsmE5cap9/3bJNnmdR8Ur5uYKRlpPG/A/9jyakl+n1ujm70r92fAX4DeKDKA3JjRJQoMckxTNsxjb1Re806vqpDVRb1W5Rr78aR60bq18U+3fhpprScUsCRCiEKothSKEXpIAFc6fXI+kf0jb4nt5jMM02eKeYRFR3dneK89tfLSXxKPFODpub6wahOxToMqjuIAbUHcCnuEstPL2dL+BZSMlIyHatBg4ONA7ZWtthb22NnbYedtZ16bGXHpbhL3Em+k+l1Nhobunp3JTk9mV0Ru/T723u058seX+Jo41jg91tanbl9hhe2vMC1xGv6fYPqDOLdDu/qq7WaIzYllmk7prEnck+2x9SuWJsBtQfwoN+DeLt4Z3ucEEXh/J3zTNo2yeyejzo+Lj78/uDvVHKolOXz5+6cY8iaIfrt1YNXU6dSnQKMVAhRUCUqgAsMDGT+/Pns37+f6OhoRo8ezU8//QTA33//zd9//82rr75KzZo1LXlZkQMJ4Eqv+f/O59ujqjJmM7dm/Pbgb8U8osIVfTeaHZd3EHQ5iJCoEJLSk7CzssPBxgEHawf1p9Hjms41GdVgFI2qNcr13FcTrjJh6wSTcvjN3ZtTo0INtl3elm3T6ey0rdGW4fWH09OnZ44VQlPSU9h+eTsrz6xkb+Re/RqvrHTy7MQX3b/ItWl2eXA14SovbHnBZLazbY22zGgzw6yZ6PDYcCZunWjSsLx6hercvHtT38LjfgFuATRza4a3izdeLl54u3jj4eyRp6BRiPzaeWUn03dOJyE1Qb+vj28fBtcdzM27N7mZdJNbSbe4effen0k3TVp+tK7Rmu97fZ9lKviXh7/kx2M/AvBAlQdYNnBZ4b8hIUSOSkwAN3nyZL7++mu0Wi3Ozs7Ex8ebrIsLDQ2lWbNmfPbZZ7z88suWuqzIhQRwpdeJmycYtX4UoGZ6djyyg8oOlYt5VJaj1Wo5d+cc2y9vJ+hykL69Ql519OzI+Kbjae7ePMvnw26F8eLWF036o/Wr1Y/ZnWZjb21PbEosgRcDWXtuLUeuH8n2OlUdqjK47mCG1xuOj6tPnscZER/B6rOrWXVmlcnsEkA3r2581u0zaRdhJDYllpe2vcSha4dM9vf27c34puNpUKVBlq8LiQrhlaBXTPrN9fHtw+xOs0lKSyLwYiAbLmzI8d9ax0pjhYeTB14uXng5e+Hr6ouvqy+1XGvh5eIl/16iwLRaLQuPL+TLw1+a3OCZ2GwizzV9LscU3wWhC/jqyFf67aF1h/Jeh/dMXpOhzaDfin5EJUQBMK3VNJ5o9EQhvBMhRF6UiADu119/ZezYsbRq1YoffviBZs2aZVnYxNfXlzp16rBt2zZLXFaYQQK40kur1dLrr15cv6sCjw86fcCgOoOKeVQFdznuMktOLiHochBX4q9Y7LxtarRhfNPxtK7RWv8BZm/kXqYGTSU+NV5/3FONn2JKiylZpmdejr3M2vNrWXduHRHxEWjQ0MGzAyPqjaCrd1eLzMakZ6SzJ3IPK8+s5PD1w/Tw6cHrbV43q4hKeZOcnswbu97g74t/Z3qup09PxjcdzwNVH9Dv++PUH3y0/yPSten6fS8EvMDzAc9n+ve+EneFTRc2seH8Bs7FnMvz2HTBXS3XWvrArlG1RjSt1lTW1QmzJKUl8c6ed9h4YaN+XwWbCnzY+UOzKrBqtVre2PWGSfXaqS2n8lTjp/TbB64eYNzf4wD1Pbv14a1Uc8y65ZMQouiUiACuffv2hIWFERYWhpubG5B1ZcqBAwdy7NgxaeRdhCSAK93e2/uevqlzH98+fNbts3yf69StU6w5u4bQG6G4V3CnjUcb2tRog19FvyL5wKnVall1dhUf7f8o2+qNHk4edPfuTjfvbvi4+pCclkxSehJJafe+7j2OS4lj9dnVWbZoaO7enOeaPkf03Wje2/OePmXOSmPFzDYzGeU/KtexZmgzuBR7CWc7Z/mwU8y0Wi2B4YF8d/S7LAvIdPPuxrNNnmXtubX8Gfanfr+DtQOzOs2iX61+uZ4/7HYYuyJ2cTnuMpfjLnMl7gpXE67mmPKanXYe7Xi11avZzhAKASpNePL2yfp1zqCqF8/rMS9PBatS0lN4JvAZ/YyyBg2fd/9cHwC+vfttVp1dBahshe96fWfBdyGEyK8SEcC5uLjQtWtX1q9fr9+XVQA3evRoVqxYwd27uZfeFpYhAVzpFnQ5iEnbJgHgZOtE8CPBeZqpuXn3JhvOb2DtubWE3Q7L8piqDlVpXaO1PqDzcfGxeEB3J+kO7+19jy2XtmR6rlHVRnTz7kZ37+7Ur1zf7GtrtVr2Ru7l+9DvOXz9cI7HOto48kmXT+jm3S0/wxclQIY2g62XtvLd0e9M1jJmxd3Rna96fkWjqrmvj8xOSnoKkfGR+qDuctxlLsVdIjw2nCtxV0xm+e5npbFiaN2hTGw+UW4AmOlG4g0OXTvEwWsHiYyPpEm1JozyH1Wm0saT0pIIjghm04VNBF8JJik9Sf9cmxpt+KzrZ9kWIsnJraRbPL7hcX1Gg6ONI4v6LcKvoh/dl3XXZyDM6TyHh/wessh7EUIUTIkJ4Lp168a6dYZp/KwCuB49evDvv/9y69YtS1xWmEECuNLtbtpdOv/RWV9kY0GfBbTzaJfja1LSU9h5ZSdrzq4hOCI4xw+aWXGv4I6Xsxdp2jTSM9JJ1977uvc4LSONqg5V6Ve7Hw/5PZTrB6yQqBDeCH5DnwoKUNG+Ii8GvEhPn55Ud6qep/HdT6vVcvDaQb4P/Z59UfsyPV/FoQrf9PyGxtUaF+g6omTI0Gaw/fJ2vj/6vb4surHGVRvzZY8vca/gXmhjSM1IJTI+kvDYcC7GXCQ8Npz/bv6XaUa4gk0FnmnyDGMajsHBxqHQxlMaRcZH6gO2Q9cOER4bnukYB2sHhtQdwhONnii1VUJT01PZE7mHTRc3sf3SdhLTEjMd86j/o0xrPa1AKdrn75xn9MbR+hYc7o7ujG08lk8OfAKooC5oZFC5bE8iRElUIgK4Fi1acO3aNS5evIitrfoBdH8AFxcXh6+vL40aNSI4ONgSlxVmkACu9JuwdQI7r+wEYPQDo5nRZkaWx6Wmp/J96Pf8EfYHMckxWR7TtFpT+tTqw7XEaxy4eoCwW2H5ShPTsbWypadPT4bVG0Zbj7Ym64xS0lOYd2Qei04sMnlNO492zO44u8CBW1b+vf4vP4T+QHCE+hlTy7UW83vNx8vFy+LXEsVLq9USdDmI70K/06eh9a/Vn/c7vl8swZJWqyU4IphPD37KhZgLJs95OHkwpcUU+tfuX27Xx91Nu0tIZAhBV4IIiQwhMiHS7Ndaaazo49uHsY3HFmhWtSgdunaINWfXsOXSFuJS4rI8xtXOlVdavcKwesMscs09EXt4ceuLWd60G1RnEB90+sAi1xFCFFyJCODmzJnDG2+8wcsvv8xnn6k1OvcHcBMnTmT+/Pl89dVXTJgwwRKXFWaQAK70Wxa2jFkhswDwcvZi47CNmT4EXoy5yPSd07OckXCv4M5Av4EMqjsIv4p+Js/FJMdw8OpB9l3dx4GrBwrUpNrT2ZNh9YYxuM5gElITmBE8g1O3Tumft7WyZXKLyYxpOMai/d2yEnYrjAsxF+ji1UXuOJdxWq2WozeOkpqRSqvqrYo9QErNSGX56eV8+++3mXoANqnWhO7e3WlQpQENKjfAvYJ7sY+3MN28e5OdV3ay7fI2QiJDTFIGs+Lh5EGr6q1wq+DG6rOruZWUOVunrUdbxjUaR4B7ALeTbquvZPXnneQ73Eq6xZ3kO6RlpOFg7YCjjSOOto7qT6MvGysbktKSuJt2N8svK40VD9Z+kNY1WufpPWu1WuYdmceCYwuyfL6CTQV6+PSgX61+dKjZweLFi/489Sez983OtP+H3j/QvmZ7i15LCJF/JSKAu3v3Lu3ateP48eO0adOGwYMH8/rrr9O5c2eGDBnCqlWr2LVrFy1atGDPnj3Y2Unp5aIiAVzpdzXhKr2X99Zvrxm8Br9KKhDTarWsPruaOfvnmBQGsbe2p6dPTwbXHUzbGm2xtrI261o3797k3+v/kpiWiLXGGisrK2w0NlhrrLG2ssZaY40GDfuv7mf12dXcTLqZ6RxWGiusNdakZqTq9/lV9OPjLh/jX8U/v38NQpQqMckxLAhdwO+nfictI+vec5XsK9GgcgPqV6mv/qxcn8oOlXG0ccTBxgE7K7tSFeBptVrOx5wn6HIQQZeDOHrjaI4z/D4uPrSq0YqW1VvSqnorajobesQmpSWx9txafjnxS54bW1uKBg2TW0xmXONxZv07pGek88G+D/jr9F8m+x2sHeji1YX+tfvTybNToc8Qf7T/I34/+bt+293RncARgWb/HhBCFL4SEcAB3Lhxg7Fjx7Jp0yY0Gg33n7p3794sXrxYX6VSFA0J4MqGketG6mfXdGWiY1NieX/v+5lKrD9c/2FebvkyLnYuhTqm1IxUdl7ZycozK9kVsYsMbUaWx41qMIqprabiaONYqOMRoiS6HHuZuYfmZlnAJzfWGmscbBxMZo5qOtWkQZUG+Ffxp0HlBni6eBb6jHZ2tFotV+KvsD9qv34WP/pudLbHO9k60cmzE129utLWo61Z6xTTM9LZdnkbC48tzLLqbFEYVGcQ77R/J8e+f6npqczcNdPk53GdinV4rulzdPPuVqSZAGkZaUzaNoldEbsAeLbJs7zU4qUiu74QInclJoDTOXr0KIGBgVy8eJGMjAy8vLzo3bs3bdq0sfSlhBkkgCsbvvn3G747qso/t3BvwZSWU3ht52sm60hc7Vx5r8N79PLtVeTju5pwlTVn17Dq7Coi4iMAVTxkVsdZdPHqUuTjEaKkOXrjKDsu7yDsdhhht8IyNXPPLydbJ+pXVjN4/lX88XH1wdXOFRc7F1zsXHCydbJogHc98Tr7ovax/+p+9kftz3UtW/UK1enm3Y0e3j1oVaNVvpuf64oV/Xz8Z/0aV1AFkSrbV6ayQ2XDnw6VsbWyzZQWmZiWyN1U9ThNm5YprbKCTQX9480XN3M57rL+Oi3cW/BF9y+yLNqUmJrI1KCp7I7crd8X4BbANz2/oaJ9xXy934JKTE1k4fGFZGgzeD7geWk6L0QJU+ICuNLk7t27zJkzhz/++INLly5RpUoV+vXrx6xZs/D09DTrHHfu3GHjxo2sW7eOkJAQIiIisLe3p2HDhjz22GO8+OKL+sIuxsaOHcsvv/yS7Xnnz5/P888/n+/3piMBXNlwPPo4j254FFBpPRqNxmTGq1X1VszpPIcaTjWKa4iAqhC4/+p+LsVeopdvL6o4VCnW8QhRUt1JusPp26f1Ad3p26c5H3NeX3HWUjRocLZ1xtnOGRc7F9wc3RhYZyD9avXLU0rdpdhLfP3v12y+sDnXwkcPVHlA3xrEv4q/xdNAY5JjSMtIo6J9RWysbCx6bp07SXeYEjSFQ9cO6fd5OnvyTc9vqFOpjslYJmydwNEbR/X7OtbsyNxuc2X9rRAiWxLA5VNSUhLdu3cnJCQEDw8POnfuzMWLF9m/fz9ubm6EhITg5+eX63nefPNNPvjgAzQaDc2aNaN+/frcuHGD3bt3k5ycTKdOnfj777+pUMH0B7kugOvbty81amT+0P3kk0/SvXv3Ar9PCeDKhgxtBj3/6pkpPclaY82EZhMY13icrG8QogxIy0jLurBG6l3iU+O5EHNBH/QVZG2Yr6svzzR5hgF+A3IsX38j8Qbfh37PitMrSNNmvZbPr6IfbWq0oa1HW1pVb5WvXmYlUWp6Ku+HvM/qs6v1+5xtnfm066d09OzIjcQbjN8ynjO3z+if71erHx92+tDixUmEEGVLiQjgfv31V7OOs7Ozo2rVqgQEBODuXng9esyhC7zat29PYGAgzs7OAMydO5dXXnmFrl27EhQUlOt55syZw507d5gwYQI+Pj76/WfOnKFXr15cunSJmTNn8uGHH5q8ThfAbd++nW7dulnyrZmQAK7seGfPO6w8s1K/7ensycddPibALaAYRyWEKC4JqQlqFu9WGKdunSLsVhjXE68TlxpnUtQoJ57Onjzd5GmG1BliEnTEpsSy6PgiFp9cnOlcHk4etK/ZnjY12tCmRhvcKpTdte1arZZFJxbx+aHP9TOPVhorng94nrVn1+qbZ4Naf/xG2zfkZpoQIlclIoCzsrLKU4qERqOhV69ezJs3j3r16lliCHmSkpKCu7s7MTExHD58mObNm5s8HxAQQGhoKAcPHqRly5b5vs7SpUt57LHHqFWrFhcumPYFkgBO5NV/N//j8Q2Pk6ZN48HaD/JmuzcLvVCJEKJ0Ss1IJT4lnviUeOJS44hLUV9bLm1h04VNmYoOVa9QnXGNxzHAbwArz6zkx2M/EpsSa3KMp7MnE5pN4MHaD5a7IGXbpW28FvxatoHxs02eZVLzSaWqaqgQovgU5PO5xRLH3377bS5evMivv/6Ks7Mzffr00c9GXb58mcDAQOLi4hgzZgz29vbs2bOHwMBAOnfuzKFDh8xeb2Ypu3fvJiYmhjp16mQK3gBGjBhBaGgo69atK1AAFxCgZkYiI81vWCpEdhpWbcjm4ZtJSE3QtxEQQois2FrZ6gt6GOvl24sXAl7gx2M/sv7cen1a5LXEa8zZP4eP9n+UaY1bFYcqjG86nofrP1xuUwN7+PTg1/6/MnHrxEwFaF5t9SpPNnqymEYmhChvLBbAjRkzhjZt2jBu3Dg+++wzKlY0rboUGxvL1KlTWbVqFfv27cPPz49p06bx+eef89FHHzFv3jxLDcUsR4+qxcYtWrTI8nnd/tDQ0AJd5/z58wBZrnHTWblyJStWrCA9PZ3atWszcOBA/P2lV5bIWnWn6sU9BCFEKefr6susjrN4PuB5fjr2E6vOrtL3qjMO3pxsnXiq0VOMaThGCnIA/lX8WTpgKS9te4njN49jpbHi3fbvMrTe0OIemhCiHLFYADdz5kwqV67MDz/8gJVV5nLFrq6u/PDDDwQFBfH666+zbNkyffXHzZs3W2oYZrt0SS389vLyyvJ53f7w8PACXefLL78EYPDgwdkec3/wOmPGDF544QW+/PJLbGzM/yfSTcXe79y5c9SpUyfL54QQQpRfns6evN3+bZ5r+hw/H/+ZFWdWkJyejJ2VHaP8R/FMk2eyLJtfnrlVcOPX/r+yN2ovtV1r4+3qXdxDEkKUMxYL4LZv306fPn2yDN50rKysaNOmDYGBgYAqaBIQEGBWoRBLi4+PB8hUGVLHyckJgLi4uHxf47vvvmPLli1UqlSJ1157LdPzzZs3p3379vTo0QMvLy+uXr3Kpk2bePPNN/n222+xs7Pj888/z/f1hRBCCHPUcKrBzLYzebbpsxy6dohmbs1ktj8Htta20t9SCFFsLBbAJSYmcvXq1VyPu3btGklJSfptV1fXPM0ylRbBwcFMnjwZjUbDwoULqVmzZqZjJk+ebLJdu3ZtXnzxRbp27UqLFi34+uuvmTp1Kt7e5t3dy24RZHYzc0IIIYSxao7V6Furb3EPQwghRA6yny7LoyZNmrBz50527tyZ7THBwcHs2LGDJk2a6PddvnwZN7eiLz+saxmQmJiY5fMJCQkAuLjkvcLf8ePHGTx4MCkpKXz55ZcMHZq33PhGjRoxaNAg0tLS2Lp1a56vL4QQQgghhCibLBbATZ8+nfT0dPr27cv48eP5559/OHXqFKdOneKff/7h+eefp2/fvmi1WqZPnw5ATEwMhw4dol27dpYahtl0FTKvXLmS5fO6/b6+vnk674ULF+jTpw+3b9/m3XffZdKkSfkan661QlRUVL5eL4QQQgghhCh7LJa7OGzYMD7//HNmzJjBggUL+PHHH02e12q1+jVduhmpmzdv8t5779GzZ09LDcNsuvL+hw8fzvJ53f6mTZuafc6oqCh69+5NVFQUkydP5p133sn3+G7fvg0Y1uIJIYQQQgghhMUaeetcuHCBn376iT179uhnjzw8POjYsSNPPfUUfn4lo3eVcSPvI0eO0KxZM5Pn89rI+/bt23Tt2pVjx47x1FNP8dNPP+W7mWdycjL16tXj8uXLBAcH06lTp3ydR0caeQshhBBCCFFyFOTzucVSKHVq167N7Nmz2bZtGydPnuTkyZNs27aNWbNmlZjgDVQFzIkTJwIwYcIE/Zo3gLlz5xIaGkrXrl1Ngrevv/4af39/Zs6caXKuxMREBgwYwLFjxxg5ciQLFizINXg7deoUv/32G8nJySb7b9y4wahRo7h8+TIBAQF07NixoG9VCCGEEEIIUUaUvfKPefDmm2+yZcsW9uzZQ7169ejcuTPh4eHs27cPNzc3Fi5caHJ8dHQ0YWFhmdalvfHGG+zduxdra2tsbGx4+umns7zeokWL9I+vXr3KE088weTJk2nVqhVubm5ERkZy6NAh4uLi8PLyYtmyZfmexRNCCCGEEEKUPRYP4P777z8WLFjA/v37iY6OZvDgwXzyyScA7Nmzh4MHDzJ69GiqVKli6UvnmYODA9u3b2fOnDksWbKE1atXU6VKFcaOHcusWbOybfJ9P916tfT0dJYsWZLtccYBXP369ZkyZQohISEcO3aMmzdvYm9vT/369Rk4cCCTJ0+mcmVpniqEEEIIIYQwsOgauLlz5/Laa6+RlpamTq7R8OSTT+pnsvbs2UPnzp359ttvGT9+vKUuK3Iha+CEEEIIIYQoOUrEGrgNGzbw6quv4u3tzcqVK7l+/Tr3x4YdOnTAzc2NNWvWWOqyQgghhBBCCFFuWCyFcu7cuTg5OfHPP//kWKykWbNmhIWFWeqyQgghhBBCCFFuWGwGTteQO7dKk9WqVePq1auWuqwQQgghhBBClBsWC+BSUlJwcXHJ9bjr169jY1Oui18KIYQQQgghRL5YLICrXbs2R48ezfGYlJQUQkNDqV+/vqUuK4QQQgghhBDlhsUCuEGDBnHx4kXmzp2b7TGffPIJN27cYNiwYZa6rBBCCCGEEEKUGxbLZZw+fTq///4706ZNY9++fQwdOhSAa9eusWrVKlatWsXvv/9O7dq1mThxoqUuK4QQQgghhBDlhkX7wJ0+fZoRI0Zw/PhxNBoNWq0WjUYDgFarpWHDhqxevZq6deta6pLCDNIHTgghhBBCiJKjIJ/P8z0D98033zBq1CiqVq2q31e/fn3+/fdf1q1bR2BgIBcvXiQjIwMvLy969+7N8OHDsba2zu8lhRBCCCGEEKJcy/cMnJWVFba2tvTv35/Ro0czcOBA7O3tLT0+YQEyAyeEEEIIIUTJUZDP5/kuYtKvXz+0Wi1r167lkUceoUaNGjz33HPs3Lkzv6cUQgghhBBCCJGDfAdwGzduJCIigi+++IKWLVsSExPDjz/+SPfu3alVqxZvvvkmp06dsuRYhRBCCCGEEKJcs1gRk7Nnz/Lbb7+xZMkSzp07py9e0rJlS0aPHs2oUaNwd3e3xKVEHkkKpRBCCCGEECVHQT6fW7QKpU5ISAiLFy9m2bJlREdHo9FosLa2pnfv3owZM4YhQ4bg4OBg6cuKbEgAJ4QQQgghRMlR4gI4nfT0dDZt2sTixYtZt24dSUlJALi4uHDnzp3Cuqy4jwRwQgghhBBClBzFUsTEHNbW1jz00EMsWbKEn3/+mapVq6LVaomLiyvMywohhBBCCCFEmZTvPnDmOHDgAIsXL+bPP//kxo0baLVarK2t6du3b2FeVgghhBBCCCHKJIsHcOfPn2fx4sX8/vvvnD17Fl2GZosWLRgzZgyPPvqoFDMRQgghhBBCiHywSAB38+ZN/vzzTxYvXsy+ffsA0Gq1+Pj48PjjjzNmzBj8/f0tcSkhhBBCCCGEKLfyHcAlJSWxZs0aFi9eTGBgIGlpaWi1WipWrMiIESMYPXo0Xbt2teRYhRBCCCGEEKJcy3cAV716deLj49Fqtdja2jJgwADGjBnDoEGDsLe3t+QYhRBCCCGEEEJQgAAuLi6ONm3aMGbMGEaNGkXVqlUtOS4hhBBCCCGEEPfJdwAXFhZGvXr1LDkWIYQQQgghhBA5yHcfOAnehBBCCCGEEKJoFWojbyGEEEIIIYQQliMBnBBCCCGEEEKUEhLACSGEEEIIIUQpIQGcEEIIIYQQQpQSEsAJIYQQQgghRCkhAZwQQgghhBBClBL57gOXnZs3b7J48WL2799PdHQ0PXv2ZPr06QCcOHGCc+fO0atXLypUqGDpSwshhBBCCCFEmWbRAO6vv/7imWeeIT4+Hq1Wi0ajwdPTU/98REQEQ4cO5ZdffmH06NGWvLQQQgghhBBClHkWS6Hcu3cvjz32GDY2Nnz22Wfs378frVZrckzPnj2pWLEiK1eutNRlhRBCCCGEEKLcsNgM3IcffoiVlRX//PMPLVq0yPIYa2trWrRowfHjxy11WSGEEEIIIYQoNyw2A7dnzx7at2+fbfCmU6NGDaKioix1WSGEEEIIIYQoNywWwCUmJuLm5pbrcbdv37bUJYUQQgghhBCiXLFYAOfp6cmJEydyPEar1XL8+HFq165tqcsKIYQQQgghRLlhsQCuX79+hIWF8ccff2R7zI8//sjly5cZMGCApS4rhBBCCCGEEOWGxYqYvPbaayxZsoQnnniCI0eOMHToUAASEhI4cuQIq1at4pNPPsHNzY2XX37ZUpcVQgghhBBCiHJDo72/1n8B7N27l+HDh3P16lU0Go3Jc1qtFnd3d9asWUPbtm0tdUlhhkaNGgHkmuIqhBBCCCGEKHwF+XxusRRKgPbt2xMWFsbcuXPp168f/v7+1K9f///snXd4VGXaxu8zM5mZ9JAeShJCC70ICCKIYAUUUWxgYbHsuroqWNaya3d1da2f665rXRYLSrErvYkgHQRDKAkQIL1OMsn0749hzrzv1DMzZ2ZSnt91cZGZOXPOySSZOc97P899Y8qUKXjxxRdRXFzc7oq31tZWPPHEE+jfvz+0Wi26d++O+fPn4/Tp0wHvq76+Hvfddx/y8vKg0WiQl5eH+++/Hw0NDV6fY7FY8Nprr2Ho0KGIjY1FRkYGrrvuOhQVFYXwXREEQRAEQRAE0RmRVYHraLS1teHCCy/Etm3bkJOTg4kTJ+L48ePYvn07MjIysG3bNhQUFEjaV01NDcaPH4+jR4+ioKAAo0ePxsGDB3Hw4EH0798fW7duRWpqKvccq9WK2bNnY8WKFUhJScHUqVNRU1ODTZs2ITY2FuvXr8fYsWND/j5JgSMIgiAIgiCI9kO7UeA6Gs899xy2bduG8ePH4/Dhw1iyZAl++eUXvPLKK6iursb8+fMl7+v+++/H0aNHcfXVV6O4uBhLlizBgQMH8Kc//QmHDx/GwoUL3Z7zwQcfYMWKFejXrx8OHTqEpUuXYsOGDfjiiy+g1+sxd+5cmM1mOb9lgiAIgiAIgiA6MLIVcOvWrcPVV1+NzZs3e91m06ZNuPrqq7Fp0ya5Dhs0RqMRb731FgDgn//8JxISEsTHFi5ciGHDhmHjxo3YtWuX332Vl5fj008/hVqtxttvvw2VyukN8/LLLyMjIwOLFy9GVVUV97xXX30VAPDSSy8hKytLvP+aa67BlVdeiaNHj+Krr74K6fskCIIgCIIgCKLzIFsB984772D16tUYMWKE121GjBiBVatW4d///rdchw2aLVu2oLGxEX369MHIkSPdHp89ezYA4JtvvvG7rx9//BFWqxUTJ07kCjEA0Gg0uOKKK2CxWPD999+L95eWlqKoqAixsbEeYxUCOT5BEARBEARBEF0D2Qq47du3Y+TIkUhMTPS6TVJSEkaNGoVffvlFrsMGzb59+wAAo0aN8vi44/79+/eHZV+O5wwZMgQxMTEhHZ8gCIIgCIIgiK6BbDlwFRUVOO+88/xu16tXL+zcuVOuwwbNyZMnAQA9e/b0+Ljj/hMnToRlX3Ie34FjGNKVY8eOoU+fPpL3QxAEQRAEQRBE+0Q2BS4+Ph6VlZV+t6uqqoJWq5XrsEHT3NwMAIiLi/P4eHx8PABAp9OFZV9yHp8gCIIgCIIgiK6BbArcyJEj8dNPP+HkyZPIzc31uM3JkyexefNmjB8/Xq7DEgzebEi9KXMEQRAEQRAEQXQsZFPg5s+fD4PBgBkzZnhskdy5cyeuuOIKmEymgOz5w4XDdVKv13t8vKWlBQB8zvSFsi85j08QBEEQBEEQRNdANgXuxhtvxIoVK7B06VKce+65GD58uDh3dezYMezbtw82mw2zZs3CzTffLNdhg8ahEp46dcrj44778/LywrIvOY9PEARBEARBEETXQLYCDgA+++wz/O1vf8Orr76KvXv3Yu/eveJjKSkpWLBgAR577DE5Dxk0w4cPBwDs3r3b4+OO+4cNGxaWfTmec+DAAZhMJjcnykCOTxAE0ZH5bn85/vvzccwc2R1zz6VFK4IgCILwhWCz2Wxy79RkMmHnzp0oKysDYHeePOecc6BWq+U+VNAYjUZkZmaisbERe/bsccuvGz58OPbv34+dO3finHPO8bmv8vJy9OzZEyqVCmVlZcjMzBQfMxgM6NWrF+rq6nDmzBnusUGDBqGoqAgrVqzAVVddxe1z5syZ+Prrr7F06VJcc801IX2vjhk4bzNyBEEQ0eLgmUbMfGsLzFYbVAoBvzw2FWkJmmifFkEQBEGElVCuz2WbgWOJiYnB+PHjcd111+G6667D+PHj21XxBgBqtRr33HMPAODuu+8WZ84A4NVXX8X+/ftxwQUXcMXbW2+9hcLCQjz66KPcvnJycnDjjTfCaDTij3/8I8xms/jYww8/jOrqatx0001c8QYACxcuFLepqqoS71++fDm+/vpr9O3bFzNnzpTvmyYIgmhHGM1WPPjFfpit9nVEs9WG4kpy3iUIgiAIX8jaQtnR+Mtf/oI1a9bg559/Rr9+/TBx4kScOHECv/zyCzIyMvDBBx9w29fU1KC4uBjl5eVu+3r99dexbds2LFu2DIWFhRg9ejQOHjyIAwcOoF+/fnj11VfdnjN//nx8//33WLFiBQoLCzF16lTU1NRg48aNiI2NxeLFi6FSdekfEUEQnZi3NxxFUXkTd9+x6hac1yc9SmdEEARBEO0fWasDg8GATz/9FJs2bUJ5eTkMBoPH7QRBwNq1a+U8dFBotVqsX78eL7zwAj755BN8+eWXSE1Nxbx58/Dss896Ddn2RHp6OrZv346nnnoKX375JVasWIGsrCzce++9ePrpp5GSkuL2HIVCgS+++AJvvPEGPvjgA3z77beIj4/HNddcg6effhqDBg2S8bslCIJoPxw804i31h11u7+kujkKZ0MQgWEwW/DtvnL0zUzA8F4p0T4dgiC6GLLNwJ0+fRpTp07FkSNH4G+XgiDAYrHIcVhCAjQDRxBEe8JkseLKt7a4qW8AMKl/BhbNHxuFsyII6dz98W5892s5FAKweuEF6JOREO1TIgiigxHK9blsCtxDDz2Ew4cP47zzzsPChQvRv39/yjAjCIIg3Pjner518rbze+P9n0oBAMeqSIEj2jdbjtbgu1/toxRWG7DpcDUVcARBRBTZCriVK1ciNzcXa9asgVarlWu3BEEQRCfitzNNXOvknHNzccv4PLGAO9PYilajBbFqZbROkSC8YrZY8ey3v3H3HSon4x2CICKLbC6UBoMB5557LhVvBEEQhEdMFise/GKf6DrZIyUWj00biJ7d4qBW2j+ObDagtKbF124IImos2VmGQxV8wXaowr0VmCAIIpzIVsANHToUNTU1cu2OIAiC6GS8vf4YfmNaJ/9+zTAkaFRQKgTkp8eJ95fUUBsl0f5oajPhlVWH3e4/XNkMi1X2SF2CIAivyFbA/fnPf8amTZuwfft2uXZJEARBdBJ+O9OE/1t3RLx949hcnN/PGRdQkO6cITpWRQoc0f54a91R1LUYAQBxaiUEwX5/q8mCk3X6KJ4ZQRBdDdlm4EaNGoWFCxdi6tSpWLhwIS6++GL07NkTCoXnGjE3N1euQxMEQRDtGM+tk4XcNn0y44GzRlykwBHtjeM1LfhwS6l4+4+T+2DZ7tNiu++h8ib0To+P1ukRBNHFkK2Ay8/PhyAIsNlseO655/Dcc8953VYQBJjNZrkOTRAEQbRjXFsnX7xmKBK1Mdw2nAJHWXBEO+P574tgsjgXIG6fWICDZ5rEAq6oQofLh+ZE8xQJguhCyFbATZo0CYKjn4AgCIIgAJxuaMVb69nWyV6Y2C/Dbbs+mc4CrqS6BTabrVN9ppgsVjzw+T7sPF6HJ64YhMuGRO9iv9VogUalgEIh/fXVtZlQ2WRAZVMbapoNGNw9CX0zu0ZU0JajNVj9W6V4+5HLC6GNUaIwOwk/HKgAYFfgCIIgIoVsBdyGDRvk2hVBEATRSfjh13JRuchO0uKxaQM9bleQ4Ww/0xstqGhqQ05ybETOUQrri6uwYMle9M1IwPvzxiA5Nsb/kxhW7DmNr/edAQC88MOhqBRwVqsNv/toBzYeroZCABK1MUiOjUFSrArJsTHivzi1CnUtRlQ0tqFS14bKxja0GC3cvtRKBVYumNTp2wYtVhsXG3BOXjfMGGb/2RXmOAtYV2dKgiCIcCJbAUcQBEEQrqwtqhK/vnpUD7fWSQdJ2hhkJGpQrTMAsKtw7aWAazVa8NAX+9GgN2HniXr8Z9MxPHRpof8nnsVms+GDn5zzUydq9ahtNiAtQROO0/XK1pJabDxcDcAeQN3YakJjqymofRktVvxSUtvpC7glO/jYgCdmDBKV4YHZSeL9J+v0aDaYkaChyyqCIMKPbC6Urhw5cgRbt27F4cPulrsEQRBE56dRb8L243Xi7akDs3xuX8AUA+1pDu7T7SdR02wQb/9v6wno2qQXPluO1ropNL+ebpTt/KRyqj40p0SVQoBa5bxsqGhqC/WU2jX22IBi8fbVo3pgeK8U8XbPbrGIZwLni0mFIwgiQsi6VGQwGPD000/jP//5D+rr6wEAt956Kz744AMAwOLFi/Hqq6/igw8+wIgRI+Q8NEEQBNHO2HC4SszHSk9QYwRz8euJPpkJ+KXUXvCVVLePKIE2kwXvbDrG3dfUZsan20/izkl9JO3jvZ9K3O7bf6oRkwdkynKOUqlqchahFw7IwIKL+4sqXGOrCU2tZjS2mtBiMKNbXAyykrXITtIi6+y/tHg1XvzxEP6zyf79VHbyAu6f646i9mxsQGyMEn++jFddFQoBA7ITsftkAwB7oPc5ed0ifZoEQXRBZCvgWltbMWXKFGzfvh1ZWVmYNm0avvvuO26bKVOm4NZbb8Xnn39OBRxBEEQnZw3TPnnhgEwo/Zhm9Mlof06UX+wsQyVT+Dh4b3Mpbj0vHxqV0sOznByt0mFDcbXb/ftPRV6Bq9Q5C66CjAQM65kS8D6ykrTi1+WNnbeAO17Tgg9cYgPY791BYU6SWMCRAkcQRKSQrYXypZdewi+//IL58+ejpKQE33zzjds23bt3x6BBg7BmzRq5DksQRBfFarXBZrNF+zQIL5gsVmwodhZwFw3y3T4J8EYm7UGBM5qt+NcGp/o2bWi22EJYpTPgyz2n/e7j/Z+Oi19rmPbD/acaZDtPqbCFaFZScPN32UwRU9GJC7gXfzgkmu90T9bijkkFHrcrzGaMTMrbXwFX0diGqk6ulBJEV0S2Am7JkiXIzc3Fv/71L2i17qtUDgYMGICysjK5DksQRBfkdEMrJv9jA4Y9tQqPLt+Pg2cir2YQvtlRWgddmz3vU61SYGK/dL/P6csocKcbWqE3RjcvdNnuUzhztkiJUQp4fPogzD6np/j4OxtLxBZRT9Q2G7B89ynx9v0X9Re/rtIZIl4AsRfyntQkKWQnOwu/ztpCqTeaseq3CvH2I9MGQhvjWWktZIxMiiqa2tWi0s7jdZj40jqc+8JabDlaE+3TIQhCRmQr4EpLSzF69GioVL67MtVqtTgfRxAEEQxf7jmNk3V66AxmfLq9DNPf/AnX/OtnfLX3NAxmi/8dEGFndZEzN2tCnzTEqf137HdPieVMMqKpwpksVvxz/VHx9uxzeqFHSizunFgARydoSU0LVh2s8LIH4ONfTsJgtgIA0hM0mH9+PronOwunSKtwVTqnApeRGJwCxxZ+9XoT2kyd7+/tTEMrHHV5jFLAdB8B3QMYBU7XZhYL/vbAt/vtER42G/Dvjcf8P4EgiA6DbAVcbGyspMKstLQU3brRkC9BEMFTVufuprfrRD3u+2wvJry4Di+vPITTDa1RODMCsNvmr2EKOCntkwCgVAicE2VJjbQCTtdmwt0f78bvPtyOn47IozR8uec0TtXbf4dUCgF/nGw3LMlPj8flzAX9vzce86i6GMwWLNp6Qrx9y/g8aFRKbu4sknNwVquNK+CCVeAyE/nnVXmYD+zonG7glUpfs5vJsTHokeKMu5Ar0Lux1YRHlu3H6OdW4/U1wbl51501YAGArcdq0agPLjKCIIj2h2wF3IgRI7Bz505UV7sPazsoLS3Fnj17MGbMGLkOSxBEF4Q1T0iNV3OP1TQb8c/1xzDx7+twx6KdOC6xCCDk40hVM8rqnAX01EJpBRzAz8Edq5JmZPLhluP47tdyrC+uxk3v/4LffbgdRyqDn0cyu6hvs0b2QK/UOPH2XRc43Sf3nWrE1mO1bvv4eu8ZMXpAo1Jg7rm5AIBhvZKZ5zYEfY6BUtti5No9gy3g1CoF0hOcf3OdMUqgnFn86Z7iP4uQm4OTwcjkpyM1uOz1TfhsRxlqmo34v3VHg8rrq9c7Cziz1cap4gRBdGxkK+DuuOMO6HQ63HjjjaipcV8BbWhowPz582EymXDnnXfKdViCILog5Y3OC6y/zRqKb+45H9ee05MzibDagNW/VeK+JXujcIZdG1Z9G9ojGdnJ0osF1olSqgLnCKd2sL64Gpe9sRl/+fJXLr9NKt/sP4PjtXaVVyEAd1/Yl3t8SI9kbqbvXy7taTabDe8zwd1Xj+ohhnYP65Ei3v/r6caIzUyx82rxamVIgdPsz7MzFnBn2AJOwu9uYY6zgCsKQYFrNVrw5FcHcNP7v3CLVBarTQy4DwS2gAOAH34tD/rcCIJoX8hWwN1444244YYbsG7dOhQUFOCyyy4DAGzZsgUzZ85Efn4+Nm7ciJtvvhkzZsyQ67AE0aUoKm/Cws/34o8f7+IuMroa7MVNTrIWQ3sm4+Vrh2Pbo1Px+LSByGXUkn1lDUGtXhPBs+Y3ZwE3dWBgWWeBKnDNBjP2lTW43W+x2rB420lMfnkD3t5wVPKslsVqw1vrnOrbVSN6IJ9p63TAqnCbj9TgABPM/fMxPrh7/oTe4tdDezgVuAa9iVMqw0kVEyGQGaT65oB3oux870PsHJs0Bc5pZBKsArfnZD2mv7kZ/2XabllcizEp1Lfw73ubj9QEFEBPEET7RbYCDgA+/vhj/P3vf4dWq8WqVasAAEeOHME333wDQRDw/PPP48MPP5TzkATRJSir02PBkr2Y9uZmLN99Gt//WtFlh9KbDWbR3RAAclKcF5Pd4tW4Y1IB1j84GSlxMeL9ni7wifBQ02zAHub1vmig9PZJgFfgSmtaYPXh8ggA20trYT67TXJsDP41dxTy05wFfLPBjJd+LMbUVzbiq72n/e7v+1/LceyseYogAH90Ud8cjO+ThuE9ncUYq8K9t9kZ3D15QAb6ZTkVmuS4GO789p9u8Hk+csHOqmUGaWDiIIsr4DrfDBy7OJYjoYAbyChwJdXNARm7GM1WvLKqGNf862dOcS7MTuSiHth5Nqm4Fn1GixXrDlV52ZogiI6ErAWcIAh46KGHUF5ejl9++QVLlizBp59+is2bN6OyshKPPvooBMF3kCtBEE5qmw14+puDmPrKRqzYcxpst9VRifNBnQ12xV+lEJAe734xqlQIGNErRby952zQLhF+1h2qEn9Pc5K1GNw9yfcTXOjNqF2tJgvK/bTo/XTEOX82viANlw/NwaoFF+CvMwYhOdZZxJ9uaMV9n+3F5W9sxtf7zni0/7dabfi/dUfE2zOGdUffzAS37QD7590fGBXuh1/LcbymBUerdFjPBHffdn5vt+dGw8iEz4CTT4HrjFECrMLfI8X/a5WfFi+6p1pt0t+bD1fqMOvtLfi/dUdF10tBAP5wQR98dc8E7m+hPsACrs1kgd7oXkj+8Kt311SCIDoOwTfBu5CamoqhQ4di48aNUCqVGDNmDJmVEESQtBjMeG9zKd7dXIJmg+csrKogZiI6A+zFVVaSFgovDnGjcrthw9kL6d0nKbokUqwt4tsnA120S9TGICtJIxYcJdXNnMufKz8fc85cTzg7l6ZWKXDb+b1xzage+L91R7Fo63ExlLm4Uod7P92D11Yfxl2T+2DWyB6IUdovvlf9VoHDlc6L7z9N8ay+ObhkcDYK0uNRUtMCqw34D6O8AcCArESc39c9/25Yz2R8ve8MgMipw5U69u8mRAWunc/AWa02LN11CiarFdee04uLpvCHzWbjFbhk/wqcSqlA/6wEHDhtn387VKHDEKZV1hMVjW247p2taGCcIXNT4/DKdcMxJj8VAG/QVBdgC2WDF8fJDYeroDeaJcV6EATRfpFNgTObzejZs6f/DQmC8IrFasP/th7HBS+vx2trDnPFW/dkLe6cVCDermqHF06RoLyBnU/xvjo+MjdF/HpvWYPf1jkidNpMFmw67CyopgbYPumgIN2pevmag6tpNnAzRxP6pHGPp8Sp8dcZg7B6wQWYNjSbe6y0pgUPL92PyS9vwP+2HkebyYI31jpn3y4fko3+TOujJ5QKAb+/wPk3uXTnKSzb5Qzuvm1ib48FLKvAHTjdGJHfTfb9wjUKIFD4Gbj29z60bPcpPLxsPx5fcQCf7TgZ0HPrWoxidh8gbQYOAAZkMXNwEoxMluwo44qsOefm4of7JorFGwB0i3MWcIEqcGzLpTZGgcSzpjVtJis2Fnt3CycIomMgWwE3ePBgnD59Wq7dEUSX5LXVh/HXrw6iptn54ZsSF4PHpw3Eugcn43cT8sX7m9rMnTJE1x+sApftY3V8eK8UOK6dG1tNkh0NieDZWlKL1rO/k3FqJcYXpPl5hmf6ZErLgvuZse/PSdZyLWcs+enxeHvuOVh5/yTMHNEdrGh7uqEVf/3qIMY+v4ZzELzHj/rm4KqRPURFy2ixMsHdalw5vLvH5wzuniSeQ4vRgpKa8LdDs4p9ZogKHOtCWaVra3eLI2uLnHNe20rcIx58cYZZIIpXK5GklaZUsXNw/oxMbDYbVuxxFvr3Tu2Hv80aingXZ1BOgWsJzHykgVHs0uI1nJnQDweojZIgOjqyFXB/+tOf8NNPP+Gnn36Sa5cE0aWwWm34YleZeFsbo8DdF/bBxocuxB2TCqCNUSI9QQN2Qb8zhuj6g40Q8GXxnaSNQT9mfmkPtVGGHdZ9clK/DGhjlEHthzUyOVbtvbj5+ahT7TuvT7rfds0B2Yl444aRWPfAZFw/uhdUTCXXxBjjXDwoC4O7+26Bc6BRKT3Oud08Lt/r9x+vUXGzdZGYg2Nn1UKegWP+7kwWW8DtfeFmP5Ovd7JOH9BzzzTyGXBSW4B5J0rfCtzesgYxpgIArh/Ty+N2nAIX4GvM/kxS49W4bIgzfH5tUWWXXPwjiM6EbAXc+eefj9tvvx2XXnop7rvvPqxZswaHDx/GyZMnPf4jCIJn/+lGce5HIQCrF1yAhy4t5IwYYpQKpDGrsuxcS1eBV+B8X4iO7NVN/Ho3GZmEFZvNxikfgcYHsBSwWXDV3hW4Lez8W1/pal9+ejz+PnsYNj58IW4dn+c2I3XvlH4BnC1w49hcTqlRqxS4aVyuz+dE0sjENUcs1AIuUaNCnNpZnLanNspqnYGLAThZG2ABF6ADpQM2C66m2egzt+3LPc5upXN7p3qd8eQVuMAKuHqmPTMlLgaTB2SIP7MWowU/HXHP6yUIouMgWwGXn5+Pd999F62trXjrrbdw6aWXYuDAgejdu7fbv4KCAv87JIguxqqDzraW0fmp6MVkmbFkMPMrXV2B82cwMCovRfyaFLjwcvBMk2hoIQjAlMLgC7g+TBZceWMbWjwY+Zys1XMZahM8mIX4o0dKLJ6eOQQ//flC/H5SAQblJOHxaQMxtKc09c1BojYG887LF2/PPqenGNztDTaCYB+jGIWD2mYD2C7HUGMEBEFot3Nw+11ey6Y2Mxq9GHp4gl0gkhLi7SA9QYN05mfuTYUzWaz4Zr8zUPvqUT287rNbfPAKHDszlxqvhjZGiQsHUBtlsBw43Yi//3iIy3skiGgimw3RpEmTKCKAIEJgFdN+dskg7+YPWUkaFJ39/K/q4gpcjj8FLtepwB2u1KHZYEaChtzXwsEaxn1yVG43vwWML7onx0Ibo0CbyT5PVlrT4ubqx6pvfTLiQ1KVMhO1eHTaQDwa9B7sc0wQBLSZLFh4cX+/2w9lFLjfzjTBZLGKbphyw86/JWhUbrNWwZCVpBXnE9uTE+U+D2rmyTo9hsZJK8pPN/AtlIEwMCcRm4/YX+tD5TpM7Jfhts2mw9WimqZWKbjWRldS40JR4JzbO1oxLxuSje9+tX94rP6tAkbz0IAcOrsqZosVdyzaifLGNny15zQ2Pnxh2P5WCUIqsl3JbNiwQa5dEUSX41h1M5cddMmgbK/bsqvnlV1MgXML8fZTwPXNSECiRgWdwQyrDdhf1oDzglBqCP+wBVyg4d2uKBQCeqcniKYix6qb3Qs4Zv7Nk1V/pFEpFZIKNwcDcxIRoxRgsthgMFtxuFLnd+5O12bC/7adQEF6Ai4b4v09whV2/i1UAxMHbPtye8qC+9WDmllWr5esqpZzEQKBLQoUZidi89nWxCIvCtwKpn3yooGZXIu8K93inY/p2swBFfmsAuco4C4szIRGpYDBbEVTmxlbS2pxQX/3IpPgqdMbxYXDM41tqGhs89ohQxCRgpYQCKIdsJpR3wqzE5Gb5v3DgVUaupoC5xbi7UflUSgEjGDiBPZEKHOrq1He2CpmYAH2C9NQKWDaKF2jBKxWG7YyDpQdsSjXqJQYkO2cm5IyB/fA5/vw0o/F+MPiXfg1gLk5LsQ7xAgBcT/tsIXSZrN5fB0DMTJhXSh95Q96gjMyKXd3omxqM3Hv9bNG+o5eYmfggMDaKOuYtlFHIZigUWESU7D9eKDc7XmEO60ugejl7eT3nejahK2AO3LkCLZu3YrDhw+H6xAE0Wlg598uGex7ZZ1V4LraDJzUEG8Wto1y9wmagwsHrHlJXloc57AYLJwTpUuUQHGlDrVnFQaFAIwLMq4g2gRiZFJU3sS1WW8/Xif5OLwDpUwKHLOf9tJCebqhVfy9YJFawJksVm5RLBATE4A3Mjla1Qyzxco9/uOBCjFmIiUuxq/6FRujhIZpcawPIEqgwUMLJWDPN3Sw8mCl2zkS7ujdCrhWL1sS/iiu0GHKPzbgolc34kQtRfuEgqwFnMFgwGOPPYb09HQUFhbi/PPPx4svvig+vnjxYowaNQp79+6V87AE0aGp0rVxypCv+TfAxcSkiylwUkO8WUa6KHA2W/vKrOoMrGXaJ6cWZskyD93HhwLHtk8O7ZHssw2tPcMambiab7jyzsZj3O2TAVz88Blw8ihwbAZje2mh9FYEl0ks4Cqb2jizl0BbKPtmJkB5dlHJaLGi1GXhgXWfnDEsx+/8mSAIQTtR1rmYmDiYOjALMUpB3CaQhYCuit7Imyi1F8W5o2GyWHHfZ3tQUtOCo1XNWLbrlP8nEV6RrYBrbW3F5MmT8fe//x1qtRrTpk1zu1CaMmUK9u3bh88//1yuwxJEh2dtURUcfyo9UmIxuHuSz+3ZFfQqH1bVnRGpId4sI3uliF/XtRhxIkBbccI3eqMZW5h2xosGhd4+CfAKXGlNCxcWzRZwHbF90sHQHini18UVOq/ZXGV1es65EABOBNAWWMXOwIXoQOmAnYGT+4K2UW/Ct/vPBLxAxRZwbFEvVYFj31/Szjo3BoJGpUQBEyZfxAR6lze2YisTKu6vfdJBsFlwDS4xAg6SY2M4x9YfyY3SL+4KHBVwwfD+T6VcyH17y4/saMhWwL300kv45ZdfMH/+fJSUlOCbb75x26Z79+4YNGgQ1qxZI9dhCaLDw7ZPXjzIv3rBrqA36E1dKpC1oilwg4GUODU3T7WnjNooQ8Fms+FkrR6f7yzDA5/vw8WvboLxbFtYolaFMfmpshynN3MhbDBbRXdAk8WK7aVO1WBCn45bwPXPShBb5MxWm2ja4sr7P5XCYuUXRAPJN2PzIkPNgHPAxgg0tZndVIpgsdlsuPmDX3DPJ3tww3+2ib9bUmBVzEsHOzsZTte3SmoVPBOCA6WDwhx2Ds758/xq7xlxoS43NQ6jmM4AXwSjwBnNVjQz0Ruus3RsG+WPByq4xRHCHdcCjhS4wCmr0+P1NfxIld7Qda5dwoFsBdySJUuQm5uLf/3rX9BqvX9ADBgwAGVlZXIdliA6NM0GM7Ycda7KXjLYv3tfhotxh6/A2M4GazAQSHsTF+h9okHOU+oSlNXp8en2k7j/sz0478V1mPTyejy8dD+W7T7F2a5PKcyUzV47XqPifsYOy/p9ZQ1oOXtBpVYpMDq/m8fndwRUSgWnuHtqAaxrMeKzHSfd7i+r17sVdd7gTExkKuDSE9RgR1DluqitaTaKr0NJdQt2Smzxs1ptnLHLtKFOe36z1SZJNQn2/YWlkDGmYdUGtn3yqpE9JLcZc1lwEgu4Bhdlg1XxAODiQdliq2eVzoDdlJHpEzcTk3bSMtxRsNlsePzLA2IsjAPXwpgIDNkKuNLSUowePRoqle9kArVajfp6erMgCADYWFwN49mV4eTYGIyVoF6oVQpuRbUrzcFVBJABx8IFepMCFxBf7CzDBS+vx6PLf8WXe894vRAuzE7EfVP7yXpsT06U7ILH6LxuAbe5tTf8GZn89+fj4oVPN6YVzmSxSTJTMFusqGlmZuBkaqFUKRXISJTfyORUPa8sbjhcLel5pbUt0J1VnQQBGJ2fyrWbS5mDY1/PYBW4gYyRiUOB++1ME1fMzRrpPbzblVTmZy615YzdLjZG6fY3khqvxrgC52cNhXr7psVtBo5MTALh2/3l2OTh79j1dSUCQ7YCLjY2VlJhVlpaim7dOu6KKUHIyarfnB+cUwdmQiVRveiqTpRnGtkWSukXWKwCV1Suc1tRJbyzaOsJeBJ6CjLicePYXLxxwwhsfXQKfrx/EgoyQnefZGHn4EpqzhZwTID3hA48/+ZgeC/vRiZ6oxn/3XpcvP27Cb25v30pbZS1LUaw4+hy5cABfBulXEYmp+r5i+MNxVVetuRhX7s+GQlI0KiQy2R1SZmD41sog1XgnIrqmcY2NOpN+HKvU30b0SuFaw/2RzAKXB2XAefZ4IcNEP/xQEXQ5k66NhP+s+mYxwv0zoLr50WVzgATuXdKolFvwtPf/CbejmUWE+hzODRkK+BGjBiBnTt3orra+x9xaWkp9uzZgzFjxsh1WILosBjNVqw75Lw48RXe7UomlwXXNQq4QEO8WQZkJyJObf/gsFhtfh3/CDtmixXFlU7l4JpRPfHWnJHY/vhUrHtgMl64eihmjugRUDEdCFyUQFUL9EYz9jDtXuf16ZjxASyskcnR6mZudmnJjjLRjCJOrcQt4/OQx2RESjEyYQurRK0KcWrfXTKBwGfByfM+5FrAHa5s5gorb7Dq5bCz7p69Ai7gWJfb4H6nc5K1SNI6X+OD5Y34iingAlHfAJcZOL20GIEGLgNO7XGbSwdnwdHFebqhVVIOoSf+9Oke/O37Q7jlg+04Vt3s/wkdENdWP5ut63zuhsrfVx4SOwA0KgXuv8jZpUEtlKEhWwF3xx13QKfT4cYbb0RNTY3b4w0NDZg/fz5MJhPuvPNOuQ5LEB2WX0prxYJEo1JgUn/pagK7Ct9eLLzDjWuId5qfEG8WpULAcKZVjQK9pXG8tkU0kVAIwPOzhmDGsO7IlCkM2h9sC2VJTTO2l9bBZLErBYkaFYb2SPb21A5DQXo8EjT2C36bDTh42n4hbbJY8d7mUnG7G8fmIiVOjdxU52sixVE1HPNvDthFFPkUOPfvaUOxf3WHK+DO/l4ErMAFqfCzCILAGZl8uOW4+DNQKQTMGJbj7ake4Vwog1LgPBdwmYlajMkLrY1y14k67mdz4HRwRWB7x1OrH7VR+mfXiTp88otzfve+i/pxfxtyGR91VWQr4G688UbccMMNWLduHQoKCnDZZZcBALZs2YKZM2ciPz8fGzduxM0334wZM2bIddiQ2bJlC6ZNm4bU1FQkJCRg7NixWLRoUcD72bVrF5566imcd955SElJgVqtRq9evXDTTTdh//79Hp9z/PhxCILg9V92tnRFhuh4rDrozM6a2C89oJXxrhgl4BrirZQQ4s3CzsFRoLc0fit3qm8FGQkRnzdjFbjKJgMXZH1uQZrkluP2jEIhYEgPdyOTb/adEQ1iVAoBt53fGwA4BU7KXFdlGCIEHGSFIUrAVYED/LdRmi1WHDzDFHBno0PYAs7fa6U3mjnlKtgWSgAYyBiZrGZ+Zy/onxHQwhMQnAslF+LtRYEDgMsYN8qlu8o49VcKb649yt0O9PkdBU+tfhQl4Buj2YpHl/8q3h6QlYg7JhaInTAAKXChIl8vBYCPP/4YI0eOxMsvv4xVq1YBAI4cOYIjR44gOTkZzz//PB555BE5DxkSy5Ytw/XXXw+r1YpJkyYhPT0da9euxa233or9+/fjH//4h6T9mM1mjB49GgCQmpqK8847D/Hx8dizZw8+/vhjfPHFF/j4448xe/Zsj8/PysoSC16W5OSOv7pMeMZqtXEf7IG0TwLgFJCuWMAF4xDHzsE5Ar3lCJzuzLA26ANzfOcThoPsJC1iY5RoPRuVwTr5Tejb8dsnHQzvmYJtJXa3xf2nG2Gz2fDOxhLx8ZkjeogtfXwLpf8wb/b9QW4Fjp2BC5eJCWDP/TOarV6Drw9XNotGLyqFgEFnf1cDUeDY9kmlQghJZS708rdyVYDtk0BwOXB1Lc5CNNXLDBwAXDG8O15eWYxWkwU1zUa8u6kECy7uL+kYe8sasNFl7q2lkxZwngoNihLwzbubS3C40tlS+7erhyJGqeBm4KiACw1ZCzhBEPDQQw9h4cKF2L17N44fPw6r1YqePXtizJgxUKu9rwRFmrq6OsyfPx8WiwXLli3D1VdfDQCorKzE+eefj1deeQUzZszA5MmTJe1vzJgxePzxxzFjxgwolfZfUKvViieeeALPP/885s+fj8mTJyM93b1NrrCwEB999JFc3xrRAfj1dKN4waMQ7AYmgcCbmHSND5Jy1uI7iPmUkUzuUrXOgFP1rdyMDOEO65zH2qNHCoVCQEFGPA6esReS7Af++Z3AwMQB70TZgPXFVdzs4R8uKBC/Zn9nT9Tq/S5EcCHeMhqYAC4FnAwXtDabzaMC12K0YOfxOq+h7b+ebhC/HpCdKCrFbAFXrzehqc2EJK3ngoZ1oMwOQuFnGeDhbyVBo8LFg/zHxLjCKnB6owVtJotfJZxV4FK8tFACQEaiBred3xtvrbcrae9uLsHccbmSite31h1xu6+5k+Z6kQIXGCdqW/DmWufvx9xzc3FOnn0BNV7jLDv0RjMtpIZA0P0nBQUF+POf/yzeXrRoEX7++WcAgFKpxJgxY3Dttdfi+uuvx4QJE9pV8QYA7733HpqamjBz5kyxeAPsathLL70EAHjllVck7UulUmH79u2YOXOmWLwBgEKhwLPPPosBAwZAp9Phu+++k/ebIDosrPvk6LzUgNtquqKJSTAh3ixpCRpOvaA5OP/wClzkCzgAHp0tMxM16Jspr+NlNHGYbgD2ouwfK52BtxcNzES/LOdrn8cUJbo2vu3PE2wLZZbMs4tsC2V1s0FyLp03apqNMDDB3aPznKq5rziBfR4MTAB7gaJhVDtfbZRyOFA6GJDl/rdy+ZDsoFqQU1wUNCkqHBsj4Bri7crvLygQt9EbLdyFtzcOnmnEmiL3ttbmts6pwHmegaMCzhM2mw1/+fKA+HeckajBw5cVio+zLZRWG7i/dyIwgi7gjh8/zjlOzps3D++9954sJxUJHMWUp7bG6dOnQ6vVYs2aNWhrC+2PVBAEDBs2DABw5syZkPZFdB7Y+Tcp4d2usApcXYtRNJrozMgRsjsqlw30pjk4XzTojTjDXKSw9uiRpE+Gu+X6eX3SOtWqbc9usZzd+29M4fyHC/pw26bGq0XTE8C/E2U4TUxYBc5itXF5c8HAtk8maVW4Ynh38bavOTjWVZZVMwVBkDwHx7+/hOaqGq9RcYtFQODukw60MUrEMxe9UubgWLMT1wLQlURtDP40pa94+9PtZSjx4yb51rqjHu8PVwvll3tO49p//4wVe06FZf/+8NTqJyWDsSvy9b4z2HzEaWT45BWDkBzr/B1kCziA2ihDIegCLjY2Fg0NDTKeSmTZt28fAGDUqFFuj6nVagwZMgRtbW04fPiw2+OBUlJin2XwZkpSWVmJJ598EnfeeSceeughLF26FEajtF53ouNRUt2MI1XOD8hg2moyXMwIqkO8cOoIBBvizcK2UZIC5xu2fTI5Nibo1zxUPClw3lrpOiqCIGAoU3g4GJ3XDaPzU922zeXaKH3PwbEKvdwtlPEaFRIZy/xQVQm2fbJntzhMHpAh3vYWJ9BmsuAQY7bDKnCA9Dk4XoELPRaDbTnOSdZiXEHwM5t8Fpz/KIF6RpX1p8ABwJxzc9Er1f49W6w2/GNVsddtiyt0nGPlcOb1bg6Dq2CbyYK/fHkAO47X469fHkSbKfIX/J5aKEmB88yirSfErycPyMD0obzrqqtZGzlRBk/QM3CFhYVYvXo1/vvf/6J3b7s7VkVFBTZt2iTp+ZMmTQr20CHT1NSExkZ7y0XPnj09btOzZ0/s3LkTJ06cEBW0YPjpp5+wa9cuqNVqj0YlAHDo0CE888wz3H25ubn44osvMHbs2KCPTbRPWPOSwuxE5KVJD3V1oI1RIiUuRmyfqmpqQw8ZLjraM9yMSpAr5KyRyW9nGiXNk3RV2PbJwuzEqClenhS4zhDg7crwnsluYch3Te7jcdu8tDhRpfMV5m2yWFHbwihwYYh/yE7SQtdmX5CqaGrD8BD2xRdwschLi0fv9HiU1tiL1A3F1Zhzbi73nKLyJpjPtm5qVAr0d2lflJoFx840hdpCCQDjCtKw8mynxbXn9IQihJm61Hi1+NrUSWihrJcQI8CiUSnx4CUDcN9newEA3/9agT0n6zGS6Vhw4JiXA+ytorNG9hBbWMOhwDW2mkR3y2aDGY2tpoi/Z3sqMip19pbhUGYlOyNsYXv7+QVunxtKhQC1SiF2DZECFzxBF3APPvggbr75ZsyfP1+8b+XKlVi5cqWk51ss0fuhNTc71Y+4OM8mBvHx9osGnU7n8XEpNDU1ia/PggULkJPDr0RoNBrcdddduP766zFw4EDExsbi4MGDePbZZ/H999/j0ksvxd69e5GXlyfpeIMHD/Z4/7Fjx9Cnj+cLASLyrOLcJwNX3xxkJmrEAo5tk+qMtBjMaGLmK7oHqQYV5iRCG6NAm8kKk8WGg2cacU5eqv8ndkGKGFUjGg6UDnqn8wVcflpcp1ysGOaiwPXPSsCFAzybG+VKDPOuaTbAxoylya3AAUB2slbsKAg1C45toezZzf49XtA/Qyzg1hdXuRVwvzLZY4O7JyHGJVqCb6H03vbGKXAyBNPPOTcXujYzLFYb/nhhaJ+/gWTBmSxW6JhCyleMAMsVw7rjvc2l4uv5wg+HsOTOcdwF+LHqZny73zkKcs+Uvlz7fjhm4FyjCZoNZgT/qRkcnooMR8uw3G3JHZ2mNqf66619N16tpAJOBoIu4G688Ub069cP33zzDcrKyvDRRx+hT58+mDBhgpzn55VZs2ahqKgooOcsWrQoYoqWxWLB3LlzceTIEYwdO9ZNYQOAnJwcvP3229x948aNw3fffYe5c+fik08+wd/+9je88847ETlnIvxU6wzYfdI5e3XJ4OCz/rKStKJNb7VO3naOo1XNyEjQINnP/ESkYFfHAw3xZolRKjCsRwq2H7dbtu852UAFnBcOVUTfwASwt9z0SIkVM9E6o/oGuLf+/X5SH6+qTR4T5u1LgWMXdpJjY8KiXGTJ6ETpqsABwIWFmfjo5+MAgJ89xAnsK2MNTFLc9illBs5ms/Eh3jIocBqVEvdO7RfyfoDAsuBcTU5SJShwgN3x9ZHLCzH3vV8AANtL67C+uApTCp3l0j/XHxUXBAoy4jFtaA7XURKOHDi9i7Ol6+1I4K3IKG9sowKOwWazcb8D7KwuS5xaJbb5Ugtl8IQUIzB69Ggx/+yjjz7C+eefjw8++ECWE/NHaWkpiou992l7Qq+3v3knJCRw9yUlua8ut7TYV/wSE4O7cLnrrrvw7bffYsCAAfjuu+8CduF87LHH8Mknn0hWNAHg4MGDHu/3pswRkWdtUaX4Adg9WYvB3YNXNtg5OLkUOJvNhue+K8L7P5VCG6PAhgcvRHaUZp9Y2PbJYEK8WUbmOQs4tpgmnFisNs7GPloGJg5G5qaIBdxFAyO9/h4ZspK0uHBABtYXV2NYz2RcOaK7122lZsGFM8TbgZxRArwCZy/gzu2dKqrmnuIEeAMT9+xUVq08Vd/qse2tXm8Sc+QAtDuFN5AsONaVVKNSIFYtvWif0Dcdk/pniK28f/+hGBf0z4RSIeBEbQu+2suobxf2hVIhcBfpntwaQ8V1n+E4hj/YGTi1UgGjxf67UtHYCpwNjSfscR+s4s/Ox7JwYd6dNHoiEgRtYvLMM8/g66+/Fm9/+OGHuP3222U5KSns3bsXNpstoH+OTLekpCQxJPvUKc+uRo77pbYvsjzyyCN499130atXL6xevdpj9ps/+vWzr9yVl5cH/Fyi/fLjQefw9yWDs0OaK+LDvOVR4P65/ije/6kUANBmsuLHA+3j9y/UEG8WLtD7ZENI++qsnKhtES9oBQFuc0WR5tFpA3Hj2Fz8ZfpAztiis/HerWPwzT3nY8md491aAVlYVamyyeDV2CGcId7ifpm/x1DCvF0z4BwtlNoYJcYzBiBsnECzwYyjjGOiJwXOUQgCgNFi9djmybZPxsYoOde89kBqvPN8/Clw7ONSDExc+fNlA+D4WCqu1GH5bvu10Nvrj4kxEXlpcbjyrENoAnORHo4WSleFJtKKjcliFQs2AMhPd/7tURYcj+vPP0FKARcFU5rOQtAF3FNPPYUvv/xSvD1//ny8//77cpxTRBg+3D5qvXv3brfHTCYTDhw4AK1Wi/79+we035deegl///vfkZmZidWrV6NXr15BnV99vV0ZcMziER2fb/efwYZi58VHKPNvAJDFzLPIkQW3ZMdJ/GMV77pa09w+3FDZEO9QFcFRjBNleWMb2UF7gJ1/650WH9AqfjjokRKLF64eitsnug/FdyaUCgFDeyb7fb27p8QiRul8Hby1BoYzxNsBp8CFUMC5ZsD1YAqvycwsIBsncPB0o7jin6BRoSDd/fMyTq1COtNy7cnIxDUDrr39jnEulH4UOD5CIPACbnD3ZFw1whl58OrqwzhW3Yxlu52L3XdP7gvV2QWGBI3zd7UlDGqKazh4pMPCXdsnC9KdHVzkRMmjY+bf1CoFNCrP72OxnAJHLZTBEnQBp1QqOat7h8rVUZg+fToAYOnSpW6Pffvtt2hra8NFF10ErVb6xeK7776LP//5z0hJScHKlSsxYMCAoM9v2bJlADzHHBAdj+IKHR5eul+8PTAnCWN7hzZ7xSpwobZQrv6tEo8u/9XtftbBLpqwId6hWnxnJmm5FilS4dzh59+i2z5JuKNUCKJCBdjDvz3BhXiHSYFjFfHKEC5o2fbJRK2KU8G8xQnsZwK8h/RI8jozmJvq/Hv3VMDxDpTtq30S4OfY6vzECPARAsEpiQsv7g/12QKtvLENc97dJjp99kiJxaxRzgIvnmmhNFqssmeSul7gR/qC3zVCoE+mc5GAFDge1jwn0cv8GwDEM1ECZGISPEEXcDk5OdixYwdaWzvm6vXtt9+OpKQkfPXVV1i+fLl4f1VVFR5++GEAwAMPPOD2vMLCQhQWFuL06dPc/UuXLsUf/vAHJCQk4Pvvv8eIESP8nsO7776LQ4cOud2/fPlyPPLIIwCAu+++O5Bvi2iHNLaa8IfFu8Q3qiStCv+aO0pcwQwWVoELxcRk5/E63PPJblg9rL9U69qJAsd8UGbLcCE6Ko8CvX3BKnBsnhXRfmDt8b05UbILO+GagWMLwxajhVuFDwRP7ZMOHHECDhydDPuY+bfhHtonHfgzMmEVuGjlHfqCz4GTbmISjAIH2H+3bhnvHB9hf4/umtyHa+91NaqQO0qgxeUC3/V2uGFbNlUKPoORFDgetoXS2/wbwCtwrdRCGTRBm5hcddVVeOutt5CRkYHMTHt7w9KlS7Fhwwa/zxUEAceOHQv20LKQmpqKDz74ANdddx1mz56NyZMnIy0tDWvWrEFDQwMWLlwozsyxOIxTTCbnh1RVVRXmzp0Lq9WK3r1745133vHoHHnVVVfhqquuEm9//PHHuPPOOzFs2DD0798fVqsVv/32m1jUPfTQQ5g1a5a83zgRUaxWGxYu2SvaYAsC8MYNI5HvodUnUFgFrrbFCJPF6nNuxhOHK3W47b87xdalRI0Kc87NxTub7OHzNe0kIJxtoZQjo2lkrxR8s88+kE+B3u4UsRlwpMC1S/LYfDMvYd6RmIFLi1cjRinAZLGvAFU2tSFRG7jy48mBksVTnACrwHmaf3PgL8z7THtX4FgXSr0RNpvNa5snW+BJdaD0xN0X9sWSnWXQMRfl2UlaXDuaz86NdwlmbjaYJUcXSMFVcQtH1pzP4zMFY6xaiRwmYqK8qWMKGOGC/V3xNv8G8L8zkf55diaCLuBefPFFAMBXX32FEydOQBAENDc3cxlr7Z1rrrkGmzZtwnPPPYdt27bBaDRi0KBBuOeee3DrrbdK3o9erxfbSX/99Vf8+qt7KxoA5OfncwXcHXfcgYyMDOzduxerVq1Ca2srMjIycPXVV+Ouu+7CRRddFNL3R0Sf/1t3FGsPOWc27p/aHxcWes52ChR2psVmsxdbOQHkF51paMWtH2xHY6t9MUKtVOA/t4yG2WoVC7j20kIpR4g3y0hmDu7X041oMZi5VqCuTFObSXR8BKIbIUB4J09CFlwV10IZHgVOoRCQmagVf2cqGg3omxn474wnB0oW1ziBqqY2rhjz5EDpwF+Yt9wZcHLDulAazVbojRav71ds0HcohVS3eDXumtwHL/3odPv+wwUFbnNNCoWAOLVSLHTkjhJojrILJVvAxamVLi3DBlittpBC2jsTzQansJGo8b6Iw83AUQtl0AR9xRIXF4c333wTb775JgBAoVBg3rx5EYsRkIsJEybghx9+kLy9pzm//Pz8oOb/5s6di7lz5wb8PKJjsO5QJV5f6zQFmVqYiT9N6Svb/rUxSiRpVWLAdVWT9AKuQW/ErR9sF1sTBQF4/YYRGN8nDb+dcaovNe2ghdI1xFuOFqfB3ZMRG6NEq8kCo9mK5777DS9cPSzk/XYGiiuc7ZOJGlW7s1Qn7HCqkocZOKPZilpGjWEVe7nJStI4C7ggjUx8tVAC7nECH2w5Lj7WLS7GY9HnwF8LZXmDfDO24cA1ELmuxei1gGNjBLqFmOM5f0JvLN11CiXVLShIj8cNY3M9bhevUYkX4nIrKtHOgWNbKOPUKs5Ey2ix/41lhKk9uaMhVYHjXCgpBy5oQhvCYbjgggtQWFgo1+4IokNzvKYF93+2V3RIy0+Lw6vXj5B9pS4ziTUykXbh1Gay4Pb/7sSRKqda/syVgzFtaA4AID3RuWrbarJEvcXBNcQ7PcgQbxa1SoHbJ/YWb3+6vQw/Hqjw8YyuA98+mdjuHPkIO3lpzjbssnq9aPHuoNql/TmcF5nsRW1FkK6u/hQ41ziB/209Ln49rGeKz99TNguuptnIvaeZLVZUMq2mcoR4y02MUoEk5oLYlxNlqDECLNoYJZb94Tz8a+4ofP6H8V6D4FnDCp3sM3DRVeBaXRS4RG0MN/dHc3BOdBJn4NjFB1Lggke2Am79+vWi+QdBdGX0RjP+sHiXqBrFxijxzs2jw5ItFEyUwF+/PICdjHHHn6b0xc3j88XbqXFqsNdCtVGOEmA/IEMN8Wa5d2o/ru3qkeX7JRfBnRnewITm39orrKpkstjc4jDY9smUuBivF99ykBVilIB7BpxnFYyNE2DNLIb7aJ8EgKxEreiqCNgLXgdVOgNX/LbHFkrAZQ7Oh5GJHCYmLN3i1bh8aI7PhTP2glx2E5N2NAPnUI7YBQuKoXHCFXA+RhJimfciV5dPQjqyFXAEQdgvRB5Z9isOMW1oL187DAPC5OTHh3n7L+DaTBYs3+N0UL1hTC8svJjPOlQpFdzMhetKfqQ50xgeh7gYpQJv3DBS/DBp0JvwwOf7YPVkx9lO+fVUI2b+cwsufnUjjlbp/D9BAhQh0DGIVSs5Z0nXNkrWOTArjO2TAP93WdEY+PuFawacpxZKAF5D3H0ZmAD2Oa2ebJQA81qxF+Dd4mKinnnoDalZcHKZmARCPJcFJ3MLpcsFfqQVG7bFL/as+UaOTOH1nQ1uBs6HkRH3+0ItlEET9AzclClTIAgC/vvf/6Jnz56YMmWK5OcKgoC1a9cGe2iCaLe8/1Mpvj7rbggAd04qwIxh3cN2PPYCrkrCB8nhSp242qxRKfDsVUM8th6lJ6jFVd7aKBdwrAIXaoi3K73T4/HUlYPw52V246Gfjtbggy2luH1igazHCQff/1qOhZ/vRZvJfuH7rw0leOW64SHt02q1cTNwhWRg0q7JS4sTF25O1OlxHvNYFRMtEq4QbwdZQbRys/jKgGNxxAk43Cgd+DIwcZCbGoeSavvzWCOT05zDbftU3wBpWXBmi5WbF3adnQsXbEuh3EHb7UqBO7vYx0bZUBacE6kzcLGMCyUpcMETdAG3YcMGCIIAvV4v3pYKzVQQnZGKxja8+IMz1298QRoevjT4MHcpsDNwUhQ41qCkMDvRa+xAWrwGgH1GribKLZTlYVLgHFw3uhfWH6rGjwftM3Av/ViM8X3SMLi774vCNpMFX+wsg95owbwJ+W7ubOHCZrPhrXVH8crqw9z9B880enmGdE7W6cULFkEABmRRAdeeyU2Nx47j9nZo1zBvtpAKp4EJwF/QBqNI+DMwYWHjBBzHzpQQkeDNyKScy4BrvwWclCy4hla+sAt1Bk4qXAHXFl4FriXiJiZMAXdWOeIVZyrgHLAOpK75gCxxMawCRwVcsARdwJWWlgIAevTowd0miK7KxsNVMJ9VtzISNXhrzsiQw7r9wSpwUla+WYMKX+1x6cx+o50Fx65whuMCSxAEvHD1UOwpq0dlkwFGixX3fbYX39xzvtd2qrVFlXjqm4Moq7Nf/JVUt+Dvs8PvYtlmsuDhpfs5ldfBsepmGM1WqFXB/86x7ZN5qXEUrdDOYaMEXN0Vq9gWyjArcKwyXtNsCDiTUsr8mwM2TgCQpr4B3rPg2AiBHu3QwMSBaxacJ9jCTq1ScG5/4YSbgZO5Jc41liDiJiYmTzNwTBYczcCJNEk0MYnT0AycHAT96ZyXl+fzNkF0NbaV1IlfXzo4C2kyuCX6g2uhlKLAMQXcoO4+CrgE58VCe2qhDIcCB9hXt1+9bgTmvvcLAOBoVTNe+KEIz8wcwm1XVqfH098cxJqiKu7+JTvLMGtUD4xjXPLkpkrXhjsX7cJeJng8Py0Op+pbYbbaYLLYUFLTHJLxCBmYdCz4LDi+rbAyAiHenvZvswHVOkNA7Yj+HChZ2DgBABjeK0XSMbxlwbEh3jntuIWSnUv2psDVu0QIRKrbiW+h7FwzcGzLZpynGThS4ESa29gZOF8xAqwLJc3ABQuZmBCEDNhsNmw9ViveDueFPAt74VTbbIDZYvW6rdVq4y7QfSpwCawCF90WSnaFPJwXWBP6puPOSc7Zt0VbT2BtUSUAu/L1xpojuOjVjW7Fm4PHVvwKgzk8FxcHzzTiqre2cMXbeX3S8OXdE9AnI0G8j51fCwbXCAGifcMWJSdq9VweaSRCvB1oY5TcvFWgc0GBtFBqY5S4bHC2ePuC/p6NTVxhFbhT9a2iWdGZdp4B5yA13vn6enOhZO/vFiEDEyC8LZTRnoFzjREAXF0o24LKAe6M8DECPkxM1NRCKQey9cecOnUKGzduRFFREerr6yEIAlJTUzFo0CBMmjQJ3buHz8iBIKLNiVo9N/sRqQKONSew2oDaFqPX1fZT9a3c6mihD2dMVoGLZgtlOEK8ffHAJf3x05EaUal8eOl+/HXGILy6+jC3ag8AY3un4oYxvfDgF/tgtdnbKN9efwwLXFw9Q2XlwQrc/9lerpVnzrm5ePrKwYhRKjAgOxHFlfbCrahch5kjgj8W655KClz7J48pSnRtZjToTeKsFDcDF2YFDrDPojlCpAM1MglEgQOAp64cjIE5SeiflYghPaS1ULLFrsFsRXWzAVlJWq7Y7B7m95dQ4BQ4by2U+ugUcOGKETBbrJw7KWD/2Zkt1rCPJzjwFCPAfg4ZzFbu764rI3UGjh1NMEb459mZCLmAO336NO677z58+eWX4iqE43+HfK9QKDB79my89tpryM7O9rovguiobCtxqm/9MhNkCZuWQpxahUSNSgxPrWoyeC3g2PbJ3NQ4nytkdhMTO9Es4MIR4u0LjUqJN28cgelv/gSD2YraFiPuX7KX2yYjUYO/TB+IK4d3hyAI2H+qUZzJ+deGY7hieHf0zUxw33kQfLe/HPd8ulsMhFcIwBMzBuHW8/LF99fCnER8vc/+eDEzwxYozQYzV6QOogiBdk9qvBoJGpV44XSiTo9u8WoYzBaunS4zjCHeDrKTteICQCBtZVIz4FhS4tT4/QV9Ajq/BI0KafFq1J5VqU7W6ZGkjeFUq/bcQsnnwHl2oWQLuEgZmADha6H0ps60GC1Ijo1QAccsnDncE5NjY7g23vLGti5fwFmsNq7Y9RnkreYf05ssSKICLmBCesWOHDmCMWPGYMWKFbDZbDjnnHNw22234ZFHHsGf//xnzJ8/HyNHjoTVasWSJUswZswYlJSUyHXuBNFu2MoUcOP7REZ9c5CRJM3I5DfOwMR3exxvYhK9FspwhXj7om9mIv4yY5Db/UqFgNvO7411D1yAmSN6iAXUg5cOEFdkjRYrHlvxqyxZcjabDS+vPCQWb4kaFT6YNwbzJvTmZlsGMkrZoRBaKNn2y3i1UtKFNBFdBEHgWgNP1Nrn4Kpd5mEzIlHABRklIDUDTg64ObhaPWdAoRCArAi8TsHCFggNeqPHtj12Ni5SEQJA+ExMvM1HRXJuSs/OwJ11TxQEgTPUqmgiIxPX1tlEjfffP1dzMDIyCY6QFLg5c+agoqICl1xyCd566y307dvX43ZHjhzBPffcg9WrV2Pu3LnYunVrKIcliHaFzWbjFLhItU86yEzUiPlGvoxM2PmmQTm+247YFsrGVlPI7obBwoZ4y50B54ubzs3FpsPVWP2bfQZubO9UPDtziMdA9gSNCk9fORh3/m8XAGB7aR2+2FWG68fkhnQOu0824PhZa3hBAJb8frxH4xn2nMob29CoNyE5iIs39vdjQHYiFBEolonQyUuLExdnHAHVbIh3arw6IhEXWUFGCUjNgJODXqlx4hzpyTo9d85ZSdp23cbF5sCZrTboDGYkuXRRsKprRBU4bXhm4LxFBkRyDo5VldgA6uwkrRhnQVlwgM7Aq8K+cuA0KgUUgn3sA4i8MU1nIeh3qzVr1mDXrl2YOnUqfvjhB6/FGwD069cPP/zwAy688EJs376dQryJTkVpTQt3wXRu79SIHj+Ly4LzocCdCUCBc2lV9DY0H24i4UDpCUEQ8K+5o/CPa4dj0fyxWHLnOI/Fm4NLBmfj0sFZ4u2/fX8o5NbT5btPiV+PL0jz6hqak6xFEvNheSjINkr2eb4Mboj2RS7nRGkvhqq4DLjIqErZQTrznW6QbmASKrmpTtWkrE7PLRC1ZwMTAEiKjQG7puLJiZJX4CLZQuksbOQM8vamtEUyC67VQwslQE6UrrAGJnFqpc9uGUEQuDbKSBvTdBaCLuCWLl0KQRDw6quvSrKqVSgUeO2112Cz2bBs2bJgD0sQ7Q42PmBAVmJE4gNY+Cw4z0VDY6uJu1DyFSEA2J3e2LmGaM3BlUepgAMAlVKB2ef0xKT+GZLe456+coj4mjW2mvDst78FfWyD2YJv95eLt68e1dPrtoIgcIYjwbZRHmIjBKiA6zDkpcaLXzsVuMgamADBt1AGOv8WCq5ZcJzDbTs2MAHsLdxsUeZpUY2fgYtSC6WMF+Pe5ukimQXHFpFsrp6rE2VXh/1Z+Zp/c8C2UbJFMiGdoAu4nTt3Ij8/H0OHDpX8nGHDhqGgoAA7duwI9rAE0e7YyrVPRlZ9A3gFrtqLAse2xyVpVeghYbW5PThRsjMq4QjxlpPsZC0eunSAePurvWew6XB1UPtaf6gKja32lhRtjAKXDfFt/sRa/gdTwFmtNu55A32ojUT7wlMWHNtKHam5LtcWSqnW6oE6UIaCaxZceYPz/VLKe2K06ca0RntyouRz4CLoQsmoKa0mCywyzAADgN6L0ubt/nDAHis2xll0sIY3pMABOiYDzpcDpYNwFf1diaALuLKyMgwZMsT/hi4MHjwYJ0+eDPawBBE2isqbMOvtLbj7k91ok7gi5Dr/FmkDE4A3KPA2A1dUzrfHSVGU0tpBFly0WiiD5aZxeVyw8F++PBDUgPay3afFry8bnO33A5FX4AJvoTzdwEdM+GoXJdoXrKpU2WRAm8nCKfHhDvF2wP59tpms4gKEPwLJgAsV9rWq0hlQUtMs3u4I7y/+nCijlQPnqrjI5UTpTWmLlAJns9k4F0q26MhJYhU4MjGRmgHngC2GycQkOIIu4BobG5GSkhLw85KTk9HY2BjsYQkibLy1/ij2nGzAd/vL8cGWUknPOVbdwjm+je0d+QIuM9F/6xI//yatPY5V4GqjpMCxLU6RNDEJFqVCwAuzhor9/yfr9Hhz3ZGA9lHXYsSGYmdY+Cwf7ZMO2IKruEIXsAsmW+D3So2V9AFMtA+6p8QiRulckCmr03OzsOEO8XaQEhfDGR1JNTKJZAtlTnIsVMxszr4y57VIe5+BA1yy4FxaKM0WK5raomNiEu+ywCSXouLdxCQyF/xGi5VTE321UHb1MG++gPOvwLGvJZmYBEfQBZzRaIRSGbizlVKphMkkbWWOICKJw8kRAD7fUSbpDZlV3wqzEyP6oemAvUCraTZ6bF8pYlQZf/NvDngFLvIFnGuId0e4wALsr+/t5/cWb7+7qSQgVezb/Wdgsth/hpmJGkyQoOqyBZzeaOEuiqVAAd4dF6VC4JSrE7V6biEnIzEyCx+CIHBzcFLayuwZcJFrobS/Vs5jGC3O+IKO8P7CKXAuLZSNrSawH1mRjBGIUSq44l2uAi7aMQKuyhA7t8UqtnqjRcxi7apIDfF2EMdsE8lYiM5E+/XMJYgIwzq3Ha/V45fSOh9b29kaxfgAB6xJgcVqQ20LX2yZLFYcrnS2CkkNaE6Pcgslu4IfiRBvObnvon7ihaLZasNjy6Vnw7Htk1eN7CHJ2jxBo+Law4oCbKPkWmypfbLDwc52najT8zNwEVLggMCNTGpbjGIYMhD+FkqAf61YOkIBx2bBuSpw7PxbjFKQdBEtJ4nM8eQqZqKtwLkGiccxbX+p8Wqomfdmdp6yK8LOwElS4GJIgQuVkP7Cly5dig0bNgT0nJqamlAOSRBhwWC2oNblA3HJjjKfRZnNZsMvUZ5/A+wX7/FqpfhhU9Vk4NoqS6pbYDwblKtUCOibmSBpvxlRNjFhPxAjFeItF3FqFZ67agjmfWg3bNp9sgHL95zG7HN8t0Meq27GvrM5VQAwa2QPyccckJ2Ik2dt5A+V63DpYN/GJyycgQk5UHY48pii5EilDg3MxXykZuAAIIuzVvf/nsEqxeHOgHOQ66GA06gUnEFIeyXVhwsla2qSEqeWNOcsJ/EalfgZGm4FLlIzcK3McdQqBbeYJggCspO14ntueWNrl54dZvP/EnyEeDuIY6InXAtlQhohFXDNzc1obm72v6ELkX5jIQh/VHsw//j+13I8dcVgr6HIR6uaRWVKECKf/8aSyYSKun4vrLrSNyMB2hhprc/RNjEpj1KIt1xMHpCJy4dk44cDFQCAF38owiWDs9zCd1lWMOrbwJykgIqpgdmJYvB4caV0BU5vNON4rbN9mCIEOh6sE+WO43znQEaEXCgBIJtR+6TMwPHtk+FX3wDPBVyPlNgOcV3CKXAuLZSsIpcaQQMTB+FwFfQaIxChdkVWGWJnthywBVxXd6IMZQaulVoogyLoAq60VJrJA0F0BDy1+xjMVny17zRuGZ/v8Tns/NvA7KSIBqe6kpGoEQs41+/lt3LpAd4sbMtiNExM2GydjljAAcDj0wdifXEV2kxW1DQb8caaI/jrjEEet7VabVixx1nAXTNKuvoGAANYJ8py6VECxRU6cXYmNkbp8QKXaN+wP7NjzCxvWrwaMRJacOUiK8AWykgamDjw9Pudk9Ix3l/YbDdfCly3CGbAOQhHmDdbQKkUAsxn29Aj1XLHFXAeFj5zKAtORBdgDlycmp2BIwUuGIIu4PLy8uQ8D4KIKt7afT7dXoabx+V5XJ1tD/NvDtgLJ9coAVaBk2pgAgBprAtlixFWqw0KiW2MVqsNja0mbsU4UNgPxO4dtIDr2S0Od0/ui1dWHwYAfPTzcVw/phf6Z7kX0tuP14lh6woBuHJ494COxWbBHa9tQavRwg3de4Ntn+yfndihWlUJO3lp8R7vj1SItwM2q1HKBW0kDUwceJqBa+8Zkw44F0o9bwbHxgpEMkLAATtz19wmj1Edq7RlJGrE3ym5Ygr8wbZwenovzeZahrt2AddMLpQRh0xMCAL8anE+045UVN6EA6fd29Hs+W/OVqVozb85yGTapNjvxWazBRUhAPAKnMVqQ4PEXCezxYqr3t6Ckc+uxqtnC5dg4FsoO8YFlifumFQgrvpbrDY8+dVBjw6ny3efEr+e2C8j4Ivv/LR4aM46wVltwJEqaSrcIbbAD0ChJdoP3lTTSBqYAEB2suf3IW9EMgPOQW6a+3E6goEJwLtQNuh5x+EGToGLcgulTBfk7IU92wocKddC9viuUQmASxacxNiMzorOwAZ5S5iB4wo4aqEMBirgCAL8xca4gjSMzusm3v5sh3vw/JGqZrGFRRCAsfnRm38D+AKOVeCqdQbOnCWQAi5Jq+JctqS2Ue44Xo/9p+z5Sv/acDRoA5SKTqDAAYA2RoknmLbJrSW1+O7Xcm6bVqMF3/9aId6+OsD2ScBuUMMqe1LbKIsoQqDDE6tWcu8BDrIiFCEgHo+5oK1rMcJg9n0hH40WyiRtjJvFfkd5f2ELM6sNaGIW1fgQ72i0UDIKXBhm4Njf70i5ULIFXKyHFkp2YbGii4d5B67AUQtlqFABRxDgB+6zkrS4fkwv8fbXe8+4rRBtPeZsnxzcPcmr0Umk8NZCeZBRVzITNQFZ8QuCwLVRVkssxE4whhgmiw1f7DzlY2vvdIYZOAdTB2biwgEZ4u3nvyvifqdWF1WKFyvxaiUuGSTdQZKlkHFBY1sjvWG2WLkW28Iu7KLW0cnzoCxlRliBy3QpGKuavL9nRDoDjsVVsewoClyiRsUFkbNZcGxLZfRbKOV3oWTzDCOmwDEFpCcTE5qBc8KamCRQC2VEoAKOIMArcFlJWkwfliN+IOkMZk4dAXgDk3G9o9s+CbgocMz3Euz8m4NgsuAcrlwOPt1+UnIGmgO90YxGZnW5o1xgeUMQBDxxxWBR0SxvbMM/1x8VH2fbJ6cNzZE0u+aJAVwB59+JcltJnfjBG6MUgvodIdoHuanuc3CRnoFTqxRIZxZ9fDlRRiMDzoHrHFz3DmJiIggCZ5bFOk9yJiadxIWSVdoyoqHAmVgXSg8tlEwBp2szR2w2rz3CmpgkUQEXEaiAIwgAlcxKcXayBnFqFa4c4TSRWMK0UVqtNr6Ai7KBCcBfqFXrDGLBFOz8mwP2YkxqC6VrAXeyTo+fjgaW/8iuZio7WIi3N3qnx+OOSb3F2+9uKsXxmhZU6dqw6XC1eP+sINonHbA/40MVOo+zdizf7Dsjfj2pXwYSfUQcEO0bTwpcVgQjBMRjMu9FvowdopEB58BVgesoJiaAdydKtoBLjcIMXDhaKHkFjingIpYD5ztGIC1BwymiXdXIxGC2iFmzgNQZOLaFsusWvqFABRzR5bHZbNwbr+MC5AamjXLH8Xocq7ZnHh6u0ontKgoBGBPF/DcHbKuU2WoTP8yLykMr4PgsOGkFXJlLAQcAn/ziPkfoCy7EO1HTaZwR776wr7hqa7RY8cy3v+HrvWfgECi7J2tDUnTZFsi6FqPPtleD2YIfDjhn8dgFC6Lj4bGAi7ACBwDZEqMEopEB54At4JJjYzwaVLRXeCdKpoBraU8mJvIrcGyXiT4KM3CeCjilQpC8YNGZcW2ZJRfKyEAFHNHl0RnMaGVaJRxvyEN7JHMXxJ/vKAPgOv+WHNGVY28kalTQxjj/nCubDGg1WsRsOAAYFJQCx2bBSWuhLKt3H+ZeXVQpyZXOAetAmdPB2ydZ4tQqPD59oHh73aEq/N86ZyvlVSN7SI5q8ERaAj/n6MvIZPPhGjSd/eDVxihw0cCsoI9LRB9P9vhRKeCYtrJiH3OY0TAwcTC0R7L49eAO1jbMqmuO6ABXl+ComJho5Z2BM5qtMFqcqg5bwBktVk7xCRd8jIDnoiSbm4PrmkYm7PybIHgudl3hFLgu3HoaClTAEV2eSmbVLEYpIPXsCqcgCJwKt2z3KRjNVq59MtrxAQ4EQXAxMmlDcaVOVHa0MQr0TvecFeULtoVSigKnazNxbT2O4tZitYkFsBQqOpGBiSvTh+ZgPNN2y876BeM+6Qob1u7rAvprpn1y6sCsDqVCEO7kuRRwgsD//UaKIUxxtHzPaRyp9Pw7GC0DE8B+jk9eMQhXDO+OJ68YHNFjhwqrrjkUuKZWE9hu6WgocHyQd+gX5K0uqkyGSztwJNru/ClwAGXBAfzPO0Gj8pib6wqnwJksftv9CXeC/sRetGhRSAe+5ZZbQno+QcgFO/+WmajlFJCrRvbA3344BKPZippmI9YUVeKXUmf+27iC6LdPOshM1OBErf2iqKrJgDNMG+KA7KSg2hBZNadaggJXVudcgdSoFJg/oTdeW2PPgvt0+0n88cK+ks7jTCeJEPCEIAh4euZgXP7GZi7HaXjPZPTNDN0FckBWIjYfsc8cFnkxMtEbzVj9W6V4+4ph1D7Z0UmNVyNBoxIvptLiNVApI79GO2tkD/xrwzGcrNPDYrXhmW9/w6L5Y90u6qKRAcfyuwm98bsJET9syKTGsQqc/T2ZdaNUKQQkRmExJl7NmpiE3hLX7FKguc5BtxgtSAnzr42UAo79fOqqWXBNTHB7ksQ5avb1tNkAg9kKrYeoBsI7Qf+Vz5s3T1KV7YrNZoMgCFTAEe0GPkKA/5BIiVPj8iHZ+GqvXa34+4+H0MDOv0U5/40l00WBYwvTYAOa+RZK/woca2DSKzUON4zthTfXHYHFasOZxjZsPFyFKYW+W/UMZgu2lzpVzo4c4u2N/lmJmHdePt7/qVS8b9bI0NU3AChkWmW9KXDrDlWJbcOJGhUmMxEHRMdEEATkpsbht7Nzr5EO8XagjVHi8ekD8fv/7QIAbD5Sg7VFVbhoEP93H80Wyo4Mp8CdLeDYEO+UOHVQ12ahEi+ziQnbVqdWKaCNUUKjUsBwtnUyEm13vImJtxZKNguuaxZwbMtsgsTFgziX7VoMZirgAiToAu6JJ55we5M4duwYFi9ejLi4OFxyySXIz88HAJw4cQKrVq1CS0sLbrrpJvTp0yekkyYIOWFnszy1610/ppdYwDkULsA+R9GeXPtcw7x/YyMEgph/A8DlwNU0G8QFGG+wBia5qXHIStLiooGZWHnQrvZ88stJvwXc62uO4Fi1c3bv3HZgEhMO7ruoH77dfwaVTQYkx8bgiuHyqGDs3OaRymaYLVY3Jebrvc72yUsGZ9MHZychL40t4KKnXF8yKAvn900X3Wef/e43TOyfDo3K/nsWzQy4jg7nQnm2cHPMwgHRmX8DeOOKFqPZ72eFP1qY4slRFCRoVDCY7d9zJCz7WTMWbwocZcHxM3BSDEwA92B0vdGC9jGQ0nEIuoB76qmnuNtHjhzB2LFjcdNNN+H1119Haip/0VVfX4/7778f33zzDbZt2xbsYQlCdlwz4FwZ1zsNeWlxXPEGAOPayfybAzZEt6KxDYdCdKAEeAWuzWSF3mjxOSt10qWAA4C55+aJBdy6Q1U409DqNddt5/E6vLPxmHj7lvF53ExNZyJJG4MVf5yA5btPYfKATM7xMxT6ZiZAqRBgsdpgtFhRWtOCflnOoq6pzYQNxc7YgiuG58hyXCL69M1MEL+OZlFkzz0cJLYJn6jV44OfjuOuyfbFW7cMuHD3wnUiunnIgeMy4KIw/wbwCpzNBr+fFf7wFKIdp1Gi9uzaXiScC1kFzls2Jz8D1zVNTLgZOIkFnFIhQBujEN8HyIkycGRrkH/00UfRrVs3fPjhh27FGwB069YN77//PlJSUvDoo4/KdViCCBlPEQIsCoWA60b3cru/PeS/sbAtU7tP1nMrmIVBFnDd4mLALqL6MzJxbaEEgPP7povFnNUGfObFzKTFYMYDX+wTjVd6p8fjkcsLgzrvjkL3lFjcM6WfrEWqNkbJGdYccmmjXHmgQnR3S41XY0LfdNmOTUSXm8bloTA7EQXp8bhlfH5Uz6V/ViJuHpcn3n5r3RFUnV0s4zLgNCokxZKBjlR4F8qzBRxjHJUahRBvwL11LtQwb7YocMzX8XN2kTUxiffSQskqcPV6E9pMXa8QYX9WgXQlURZcaMhWwG3YsAHjxo2DUum9FUelUmHcuHHYuHGjXIcliJDhWii9tB3NPqcnZ76hVAjtav4N4BW4GsZwJC8tTnJfuisqpYK7IPBXwLm2UAL2AviGsc4CeMmOkzBb3C2g//Z9kahyKgTgleuGe507IHzDtlEecjEy+Wa/M/vt8iHZiImC0QURHrKStPjx/klY+8AFnBoXLRZc1F9s6WsxWvD3H4sB8A6UPbrFRmVmq6PCKnBNbWaYLFbOxKRbfHRaKDUqBRdqrQuxwOIMRM46XIYja07qOXhT4DISNGB9ubpiGyVrYhLItQbbRunqOkr4R7ZP7tbWVpSXl/vdrqKiAm1tXe8XnGi/cC6UXgb/s5K0uHBApnh7aI/koIuicOHNtCDY+TcH6VyYt3cnSovVxq2ss2G5157TCzFK+6dcZZMBaw9Vcc9dX1yFj5mw7z9O7otRud1COu+uDFvAsUYmtc0GbDk7lwQAV8o0d0e0L9pLQZQcF4OFlwwQby/bfQp7yxqi7kDZkUl1aZFs0JvQwM3ARUeBEwSBL7BCLODYAs3xWcvOocnhdOkPvYQZOJVSwS2edsUsONbEJEliCyUAxDPREy1UwAWMbAXcsGHDsHnzZqxZs8brNmvXrsWmTZswbNgwuQ5LECFhsdpQzahK3hQ4APjDBQWiCnft6J5hP7dAYT9EWIKdf3PgamTijcqmNi54lZ3ByUjU4JLB2eLtT5hirUFvxJ+X7hdvD8pJwr1T+4V0zl2dwmznz7yICfP+/kCFGF2QnaRtdyoy0fmYMzaXW1B46uuDXKs1GZgERpxaCbXKeelWrzfyClyUCjiAV19CNRnRG9wt/OMj2HJns9lEp172HDzR1bPgdEG4UAJ8ODq1UAaOrDNwVqsVM2bMwPz587Fy5UocOnQIhw4dwsqVK3Hbbbdh+vTpsNlseOSRR+Q6LEGERE2zgcvi8uXcNjo/FasWTMKXd0/AnLG5kTi9gEiKVXEf7A5CLeA4BU7nXYFjL8rSE9RuA+xzz3W+ZpuOVIvtln/96iCqdPbCUK1U4LXrR3j8PgjpDGAumE83tIotLt8w4d0zhuVwmYcEEQ6UCoELy95b1sC5oFIBFxiCILhlwTW0AxMTwEVRCVEh8zQDFyfj/v3RZrJy4ei+2vm7uhMlPwMXgALHhnmTAhcwsl0lXXnllXj77behUCjw0UcfYdq0aRg8eDAGDx6MadOm4cMPP4QgCPi///s/XHnllXIdNmS2bNmCadOmITU1FQkJCRg7dmxQIeUfffQRBEHw+u+GG27w+tyDBw/i2muvRUZGBmJjYzF06FC8/vrrsFrd54QIeWHn3xI1Kr+uWX0yEjCiV0q7aVFiEQTBYxvloO7yFXC1Ld4VuDIPBiYs4wvSUHDWXMNmswd7f73vDFdUPHTpAK74IIKjZ7dYbiX0cIUO5Y2t2HHcGUIvV2wBQfhjfJ80TBvqVODZCz5qoQwc1yy4uhZWgYtetA2vwJl8bOkfVpGJZ2IEHITbxMRVESIFzjs6dgYuIBMTKuBCQdYhnj/84Q+YNm0a3n//ffz00084c8Z+YZaTk4OJEyfid7/7nZgN1x5YtmwZrr/+elitVkyaNAnp6elYu3Ytbr31Vuzfvx//+Mc/At7n8OHDMWLECLf7zz33XI/bb926FVOnTkVrayvGjh2L/Px8bNq0CQsWLMDPP/+MJUuWtMtiobPAOVB6yIDraGQmalFW5+zBT9Kq0D3E70tqC6UnAxMWQRBw49hcPP99EQBgyY4ymBn1c2zvVMw/v3dI50rYEQQBhdmJ2HmiHgBQVKHD3rIGcUU5Ly0Ow3p2zngGon3y6OUDsbaoSgxidkAKXOC4ZsE16JkZuKgqcGwBF9oFeYsHExNWBQv3zJRrQeGaW8bS1RW4YHLgAL6FspVaKANGdheG3NxcPP3003LvVnbq6uowf/58WCwWLFu2DFdffTUAoLKyEueffz5eeeUVzJgxA5MnTw5ov1dddZVbRp43TCYT5s6di9bWVrz66qtYsGABAKC5uRmXXHIJvvjiC0ybNg3z5s0L6BwI6VTqpM2/dRTYMG/Arr6FugCQIdHExFMGnCvXnNMTL68qhtFsRS2zahyvVuKVa4dzTp9EaAxgCrhD5U349XSj+NgVw7rTwhARUXqlxuH3kwrw5rqj/P2kwAUMO+dW22zkc+DayQxcyCYmHmMEWMUm3Aqcs4DTxih8tptnJzsXISqaup6JCVfABTADx/48ycQkcLrsoMl7772HpqYmzJw5UyzeACArKwsvvfQSAOCVV14J6zmsWLECpaWlGD58uFi8AUBCQgLeeuutiJxDV6eSWS3z5kDZkXCd4Qt1/g2QrsB5yoBzJTVejWlDst3uf+KKQV6fQwQHm/23obga+08xBRy1TxJR4A+T+3BqBWXABQfrRHm8tgVMI0PUcuAAFwWuLdQCzoOJCVcghluBcy8gvZHTxVsogwnyBvhoBooRCBzZC7jffvsNCxYswIQJEzBgwAA8/PDD4mM///wz3nzzTdTV1fnYQ2T47rvvAACzZ892e2z69OnQarVYs2ZNWCMPfJ3DqFGjUFBQgAMHDuD48eNhO4euToWEDLiORIarAidDAcebmPgq4DxHCLgylwn3BYCphZkeg9KJ0Ch0MTJxMCArkeYMiagQp1bhkcsLxdsDZegQ6IqwKltJdYv4tVIhBNTCJjeyulB6iBHgTVLCq8C1SsiAc8BeO9Q0G2Ewd8xi5FBFEx5Zth+/+3A7isqb/D8BdrfOYIO8Ix3M3tmQ9S/91VdfxSOPPAKz2f6DEAQBNTU13DYLFiyARqPB73//ezkPHTD79u0DYC+UXFGr1RgyZAh27tyJw4cPBxR7sGvXLjz00ENoampCdnY2pkyZggsuuCDgc3DcX1JSgv3797er2cHOBBfi3Qlm4MKhwKUzRWFTmxlGs9XNJVJvNHPqnK8CbnReN5zXJw0/H6tFdpIWL1wzlC7iwoC3Iu2K4TkRPhOCcHLl8O6oaTZib1kD7r6wT7RPp0PCKnDHqpvFr1NiY6LqLCtrCyU3A+fIgYuc7Tx3fD8FnOvnblWTocN0lNhsNmwrqcM7m45hQ3G1eL/FBiyaP9bv81tNFs7JO7AYAaYl1tQxi95oIlsB99133+HBBx9E79698corr+D8889HZmYmt815552HjIwMfPXVV1Et4JqamtDYaG8n6tnTc55Xz549sXPnTpw4cSKgAu7bb7/Ft99+K95+5plncMEFF2DJkiXIysritj158qTfcwCAEydOSD4+ERhsAectR60jwc7AqRQC+mUlhLzPNJeh+NoWA3KSeeMB1jhFrVT4jGMQBAH/uWU0dhyvw8heKUiJYstPZyZJG4MeKbGc+gZQ+yQRXQRBwG1kVhQSrFEJO38UTQMTwKXFMcQCi5+Bc7RQRm5mii0QY/20UKpVCqQnaMRFzPLGtqgUcDabDUermqFSKtCzWyxilN6b7CxWG1YdrMC/Nx7DPqa93kEJszDgC51Lq2ywMQLUQhk4shVwr776KuLj47F69WoUFBR43W7EiBEoLi6W67BB0dzs/MWMi/P8RxYfb7c71+l0Hh93JScnB0899RRmzpyJgoICtLa2Yvv27Xj44YexceNGzJgxA9u2bYNS6fyFdZyHXOcwePBgj/cfO3YMffp0/pXO9YeqsOtEPa4b3Qu5adLePNl+9c6gwA3vlYJErQq6NjOmFGZCo/K9cigFbYwSiRoVdGc/UGt0RrcCzjWY158ZSYJGhQsHZPrchgidwuxEroAb3jMZeWnxUTwjgiBCxducWzQjBAB+/sn1wj5Q9EwBFyeamESu5Y4tKOL9KHAAkJ3sLODYheFI8sbaI3h9zREA9gXc3NQ4FGTEo3d6PAoyEtA7PR65qXFYX1yFdzeV4Hit3uu+qnUG2Gw2v90x7M85RilAE0CGaxy1UIaEbAXcrl27MG7cOJ/FGwCkp6dj8+bNIR9v1qxZKCoqCug5ixYtwtix/iXhYLj00ktx6aWXireTkpJwxRVX4MILL8Q555yDnTt34vPPP8eNN94YluN3ZVoMZjzx1UEs230KAPDzsRos/+MEv89rNVrQxLz5eMpQ62gkx8bg63vOx56T9Zg6MMv/EySSlqB2FnAesuCkGJgQkacwJxFrD1WJt0l9I4iOT7d4z4VaNB0oASBBxhk1VmFzzsBFMgdOegsl4JKX6sPsK5ws331a/NpstaGkpgUlNS0+nuFkQt80XDe6F+77bC8AwGC2otlg9jvT5jr/Fsg4BGdiQi2UASNbAWc0GpGY6H8wvqqqCipV6IctLS0NWMnT6+0XmQkJCdx9SUnuc0ItLfZfeinfky8SEhJw77334p577sHKlSu5Ai4hIQH19fXieYV6DgcPHvR4vzdlrjPw25km3PPpbm6Qe/fJBrQYzH5DudlVMoXA2+V3ZHqn21fc5CQ9QSOu1nkyMvGXAUdEh8Js53ubIAAzhlEBRxAdnVQvrZLRLuB4hSy0C3K2hdGZA8cHP0tRiOQ4vr8WSgBIi2cKuBbvcTvhwmyxurXL+0MhANOG5uD3k/pgaM9kWK02LPx8nzjTVq0z+C3guBDvAObfgMia0nRGZCvgevfuLZpyeMNoNGL//v3o379/yMfbu3dv0M9NSkpCcnIyGhsbcerUKQwaNMhtm1On7GpOXl6e22OB0q9fPwBAeXk5d39ubi7q6+tx6tQpj3N2cp5DZ8Nms2HxthN49rsiGF2CYQHgcKUOI3O7+dwH60CZnqCByke/eFeHW1308OHEK3AUzNteOK9PGhI0KjQbzLhscHanaBMmiK6Ot0It2jNwcrlQGswWmCxOYwwxB47Zv9lqg9FilWVMwBP6AFso07m4ncgXcOWNbZyZyLd/Oh+nG1pRUt2C0ppmlFTb1bi6FiM0KgWuG90Lt0/szbXUKxQC0uLVqDq7SFutM6Agw/ccfXOQId4AEBvDBnmTAhcoshVwV155JV566SW8+uqrWLhwocdtXnrpJVRXV+O+++6T67BBM3z4cGzatAm7d+92K+BMJhMOHDgArVYrS7FZX28P03XMtLHnsG/fPuzevRvTpk1ze97u3bsBICATla5Ao96EPy/bjx8PVoj3CYL9Td7xoVFc4b+AYxU4X6YbhEsWnAcFTkqINxF50hI0+OZP5+PX042YUkgzhwTRGdDGKBGnVnJFBgCkemmtjBTsDFwoBZzeRb1zKDWuXTUtBktECjh/MQIA/xkZjRZKtgsmM1GDIT2SMaRHstt2ja0mxKmVXg1OMhI1zgJOwvfBzsCFosCRC2XgyCY5PPzww+jRowceeughXH/99fjss88AAJWVlVixYgVuueUWPPnkk+jduzfuueceuQ4bNNOnTwcALF261O2xb7/9Fm1tbbjoooug1YZ+Yb9s2TIA7nEBvs5hz549KCkpwZAhQyhCgGHXiXpMe3MzV7ylJ6ixaP5YTB/qtEc/VOHf+IUKOOlwWXAub+o2m4378KAZuPZF7/R4XDm8e8AfrgRBtF88qXDRdvR1nVGz2Ww+tvaOq4Olw+wiNoYvpMLZdtca4AxctFsoy+qlfQYnx8b4dKdks2R95b460AWZAQe4tMSGOZi9MyJbAdetWzesWbMGgwcPxhdffIG5c+cCIETlcAAA8pRJREFUAH788UfMnj0bixcvxsCBA/Hjjz+GPFcmB7fffjuSkpLw1VdfYfny5eL9VVVVYvj4Aw884Pa8wsJCFBYW4vTp09z9L7zwglvmnclkwtNPP40vvvgCsbGx+N3vfsc9PmvWLLH19LXXXhPvb2lpwd133+31HLoiNpsN/954DNe9s5Xr857YLx3f3zcRE/tlcLlXhyulFHDON6fs5M4x/xYu2PYQ1w+nap0BBqaNlQo4giCI8OJpDs6bO2WkSHBpcTR4GG+QAqt+aWMUoquxUiFwRZyrAiknbBEZJ2EGjs1LdV3kjARslE8oXTCsF4A0Bc45AxdwCyXzuhotVpgswf2+dFVkXZLt378/9u7di2+++QarVq3C8ePHYbVa0bNnT1x88cW45pprOBv9aJKamooPPvgA1113HWbPno3JkycjLS0Na9asQUNDAxYuXIjJkye7Pc9hnGIymbj7H3vsMTz99NMYPXo0evXqhaamJuzduxdnzpyBVqvF4sWL0aNHD+45MTExWLx4MS666CIsXLgQS5YsQV5eHjZv3ozy8nLMnj0bt956a9heg47EyoOVePGHQ+JtpULAA5f0xx8m9RGDSwuZAq5YggLHzsBldYIMuHDCKnDVLqtybPtkt7gYJAW4CkcQBEEEhqd5N2/ulJHCVeVvNpihjQn8mq+Zy4Dj9xmvUYmOhaG0afojcAWObaGMvALHzaF3C34OnS1EXT/rPRHKDJzrbKHeaEFyLHkRSEX2nhqFQoGZM2di5syZcu9adq655hps2rQJzz33HLZt2waj0YhBgwbhnnvuCbhweuKJJ7B161YUFxdj9+7dsNls6NmzJ37/+99jwYIFGDBggMfnnXfeedixYweefPJJbNiwAfv27UOfPn3w0EMP4b777gubw1JHY9eJOvHrnGQt3pozEufkpXLb9GcKuNoWI6p1Bq4dwJVKJgMui8wdfMK+qbsqcDT/RhAEEVlSPWS+RduFMk6thCAAjs7JFoOZW/yTCttO5zr3Fq9RouZslK8+xLBwn+cQQoxAs8GMNpMlqOI1WNgWyp4yKXBSzFhCmYFznS1sNVqQHEsLwFKRrYDbtGkTlEolJkzwnb91+PBhVFRUYNKkSXIdOiQmTJiAH374QfL23nq6n3766aDPYfDgwR7n4Agnx5iYgFvG57sVb4D9DTQ9QS2+6RRX6HwXcDomxJtm4HzCri7WtRhhtdpE5ZMy4AiCICKLRwUuygWcIAicmViwChnfvshf5MfJGFXgC9ZUQ0qMgGtLa22LET1SIufILFsLZaAKHPMzTghQgVMrFVApBJjPumeGsyDvjMimVU6ePBmTJk3CVVddJeaXeeKFF17AhRdeKNdhiS5CSXWz+HVBhveMM3YO7lBFk9ftbDYbNwNHJia+YRU4i9WGer1zZY4UOIIgiMjiOu+mEICkdqBe8NlewRVYrDmJmwLHZcGFUYFjziFOgpKmVimQxBQwkXSi1BvN3NxdKAupvsYlPNHEzcAF9vsnCAKnwoVzprEzImuzqUKhwNdff43x48fjxIkTcu6a6MIYzVaU1TtXl/r4yCUZkOUMLvY1B1evN3H5caTA+SZRo4Ja5Xy7YNsoKcSbIAgisrgqcClxatHsI5rwWXAmH1t6p8VH+6Kr02W44FooNdJaIbm81AjOwZ1iro9ilEJI1zMZLmYsVqtvJ1FWgUsMwunYNZydkI6sBdycOXNw77334sCBAxg7diw2bdok5+6JLsrJuhYxoFKpEHwWCZyRiQ8nSjZCQBujQFIsWaz7QhAEpMd7zoIjBY4gCCKyuLbspXiYiYsGfAEX3AU5q365zlVxCl8YL/hbTWwRKe36ID1AB0e5OFnr/AzukRIbUiHPFnBmqw2Nrb6LcF0IJiYAb1LjGh9B+EbWAk6pVOL111/Hf/7zHzQ0NODiiy/Gf/7zHzkPQXRB2Pm33NQ4TglyxTVKwOJl9ajCJQOOzGL8w7lTnf1wajNZuFZUmoEjCIIIP67zbtGOEHAgh0LGK3B8UcDe1odVgfM+h+cNPsw7cgqc1Aw4KSRp+W4bf4VocwgmJgBvZNJKClxAhMWv8/bbb8eaNWuQnJyMu+66C3/6059gtVK+AxEcJUwBV5Duff4NAPpnJcJRi7WZrJw6xMI5UFL7pCQ82SSfYj44VAoBOeTmSRAEEXbcFbj2V8CxF/eBwM/A8cWTHAqfPyxWG9pMzmtW1wBxb/AFXOQUONbAJNQCThAEPgvOzxxccwhB3gCvwFELZWCELXBh4sSJ2LFjB4YMGYK3334bl156Kerr68N1OKITI9XABLCv5uQxb2DFXoxMyMAkcNIT3INK2QK5R7dYqJSU4UIQBBFuXDPfUqOcAecgkSuwgivgWPXL1cQkLgImJmz7pKdz8EZavPe4nXDCZ8CF3gUjNQvOYrW5FHChKXDkQhkYYb3aysvLw9atWzFz5kysXbsWY8eOxbFjx8J5SKITUlLDKHA+DEwc8E6Unufg2BbK7KTAc2q6IlwW3FkFju29l+ODgyAIgvCPawtltCMEHMjSQsnmwPkyMQmTYuNaSEhtoUxnFLiaCCpwp7gWytCjCzI8LNZ6wnVmLZgCjkxMgifsy+VxcXFYvnw5Hn/8cRw7dgxbtmwJ9yGJTsYxVoHz00IJAAOy/TtRVjVRC2WgsC2Ujjd11h2U5t8IgiAiQ4xSwV0we8qFiwZ8gRW6Auc+A8dc8IdpBo4NEhcEQONj7p4lLQoulDabTXYnaKlZcDqXFlmpSiVLpGYaOyOyFXC33norzj//fK+PP/vss/j000+Rn5+PvLw8uQ5LdHLqWoxo0DtdkKQocJwTpQQFjgo4abjaCwPkQEkQBBEt2Dm49mJiwhaVrhf4Umn2MQMXL0OLpj+4CIEYpWSTM09jBuGmrsXIKZFydMJkMEqirwKOnXGMjVEiJogRClLggkc27/QPP/zQ7zbXX389rr/+erkOSXQB2Pm3RK2Ka1HwBttCeby2BW0mC7QuQ8hsjEA2GW9Igu3vrzm7ukgZcARBENFhQFYiTpxtY+/PfO5FE7blMdgWSvZC3j3IO/ymF60mRgEMQFViTUzqWoywWm1QhDmbj+2CSdCoZImTyPDgOO0JHRPinRBE+yTAZ+zpTVTABQKFXxHtGtaBsk9GgqSVsPy0eGhUChjMVlhtwJHKZgztmSw+brJYxQIEoBBvqaQn8i2UNpuNFDiCIIgo8ei0gUjUxmBQ9ySM6JUS7dMB4DoDF9wFOedC6dpCyeXARUCBkzj/BgDp8XyGWlObKezuoOwiaq/UOFkikSS3UIZoYAIAcTHUQhksQRdwjpDusWPHQqvVBhzaPWnSpGAPTXQhjtVId6B0oFQI6JeVgAOn7Q6UhyqauAKuyuUNiX2zIrzDtocYzPaIBvaDjgo4giCIyNE7PR6vXDc82qfBwdr862RQ4FwLKHb/+jDFCLCFp9QIAQBIilVBpRBgPps/W9NsDHsBxztQhm5gAkhvBeVCvIOYfwOohTIUgi7gJk+eDEEQUFRUhP79+4u3pWKx0A+K8I+rAieVAVlJYgHnOgdXwWTAdYuLcWuvJDzTLU4NhQA4stF3n3TGgiRpVUiWoXWDIAiC6LiwrXTBtlDyM3DeTUyC3b8/uBbKABQ4QRCQlqAWY4pqmw3omyn9uiUYWAdKuRZR2UXt2hYjzBarx4ggLsRbjhZKKuACIugC7pZbboEgCEhOTuZuE4SclAToQOlgQLbzTbO4ki/gyIEyOJQKAanxarH9dPeJBvGx3DRS3wiCILo6ocYI2Gw2yTNwLUYzbDab7Neevo7vj7R4jbOAi0AWnJwh3g5YBc5ms8/zZXq4VmJn4BI1wS3gRiLXr7MSdAH30Ucf+bxNEKFisli59gApDpQO2CgB1yw4cqAMnvQEjVjA7SlzKnDUPkkQBEEkhOgSaTBbYXG0ecA9B45VbKw2+/Zyd9G0GoNroQTO5qWW27+OhBMl10IpQwYcYC9a49VK0d2yutngsYBjf75BK3ARMKXprIQ9B44ggqWsTg+Txf5GLghAXgAqDxslUK0zoI5ZCeNDvKmACwTWZauo3FkYUwYcQRAEwSpWBrMVJos1oOe7qnauLpAJLrfDESXAzsAF0kIJAOlcXmp4FTizxYozDU4FTs6F1HQJRibcDFzQBRy1UAYLuVAS7RZ2/q1nt9iAVtkyEzVIiYsRM+SKK3QY3ycNAFDV5HwzykoiA5NAYFsr2FVSUuCISGOz2WCz2fxvSHQKBEGgMY0OgGuB1WIwB2Tk4XoRH+fyua9VKSEI9tY+4KyRicxjZnpmBi5WHWALJbPIWRtmBa68sU00TAGAnjJkwDnISNCIERWSCjgyMYk4QRdwixYtCunAt9xyS0jPJzo/JawDZXpg79CCIGBAViJ+Ka0DABRXNIkFHGtikkUZcAHBZsGxUAFHRAKLxYLa2lrodDoYjeGfLyHaF0qlEnFxcUhKSkJiYiIVdO0Q15bH5gALODYaIE6tdMtRUygExMU42/vCESXAtlC6fj/+SGMWOWvDrMCVMQYmGYkaWVtJWSMTb0pis4GZgdMGOwPHtlDSDFwgBF3AzZs3L6g3T8fAKRVwhD+CdaB0UJjNFHCMkUmljloog4XNgmPpJePKH0F4wmKx4OTJk2hra/O/MdEpsVgs0Ol00Ol0SElJQVZWFhQKmgRpT6iUCmhjFGgz2VsnA21x5NsXPV+ixmlUYgEXjov+YHPgACCNaaGsbQmvAneqLjztkwDfbSNFgQt+Bs75+raaLGExpemsBF3APfHEE/QiE2GFLeCkZsCxeDMyqWwkE5NgYd/UHSgEoHuKPMPTBOGN2tpatLW1QalUIisrC/Hx8XTx3oWw2WwwGAzQ6XSoq6tDQ0MDtFotunXrFu1TI1xI0MSgzWS/6A/UiZIL8dZ4Lp4SNCqxqGgOQxYcWxQG2kKZHkEFLhwZcA64MG8vraDNcgR5M6+vzQa0mayIDbBo7qoEXcA99dRTMp4GQbhTEkSIN8sAxsjkcIUOVqsNLUazuHIHUAEXKOkJ7gpcTnIs1Cq6kCbCi05nX4TJysoS42uIrkVcXBzi4uKgUqlQVVWF+vp6KuDaIQkaJRwf34EWWGzxFO9NgWPnpsJgYhKSAsd8RnorfOSiLAwZcA64Ak7nueuBU+BkmIED7C2xVMBJg666iHZJY6uJ67sOpoWSLeBajBacbmgV81kAQKUQuHYHwj+eFDiafyPCjc1mE2fe4uMDX8whOhdJSfbuCoPBQEY27ZBQsuDYFkpvChyfBRcOBS4EF0rmM1LXZobBHD5jjjJGgespdwGX4H8GjnehDG4GzjWmoZWMTCRDBRzRLmEDvOPVSmQmBu4WmaBRoSfTVnCoQodKJkIgM1HjNiBN+CaNCjgiCrAX6dQ2SSiVzos+KuDaH2wB19wWWAGn50xMPKs6bGEXTFi4P1qN/ufwvJHqsihcF8Yw75NsiLfMc+jSYgRYE5PgFDiFQuCKuHCY0nRWZI8ROHnyJL755hscOXIEOp3O45urIAh4//335T400Yk4xs2/JQQ9b1mYnYhT9fY3ueKKJuQkOws6cqAMHE+KZW4A+XwEQRBE5yYxhDDvZgkKHJsNF44LflcnzEDQxiiRqFFBd/b7rm02ctcdctFqtHBB4XJ/DrMtlI2tJhjMFmhUztfCaLbCYHZm/AVbwAH217jV5DClIQVOKrIWcM888wyeffZZWK3OH6qjgHNcgDscZqiAI3zBKnDBzL85GJCdiDVFVQDsChyruJEDZeBoY5RI1Kq41gkK8SYIgiAchNJCKWUGLp6bgZN2we8oJKXMarEKXDDzWGkJarGAqwnTHNwpZv4tRinIfj3jOu9e02xED8aszLUwD3YGDgDiNErUnl2zpxZK6cjWi7JkyRI89dRT6NWrF/7zn//g4osvBgCsXLkS//rXv3DBBRfAZrNh4cKFWLdunVyHJTopnANlgBlwLKwTZXGFjhwoZcB1Do5aKAmCIAgHXAtlgAoZPwPnzcQkMAXu11ONGPPcGox5bg1+PdXod3s9lwMXeGESiSw41oGye0oslDKPg2hUSiTHOufaalzaKNnWWEEI7nVyEBcTfMHflZGtgHv77behVquxfv163HbbbcjJyQEAXHzxxfj973+PdevW4ZVXXsEbb7zB9a8ThCdYB8o+mcErcIWMkUlJTQvK6p0941TABYfryhwVcARBEISDBKb1MbQZOO8xAg6kXPB/vrMMrSYLWk0WfL6zzO/2IStwzKhBuBQ41sAkXJ/B7Ge96xxcEzP/lqBWheQnEKfhs+AIachWwO3fvx/nnXce8vLyAPAtkw4WLFiAAQMG4LnnnpPrsEQnxGK14Xit880pFAWud3o8YpSCuN/tZ4O9ASA7OXBjFIJX4BI0KnSLC859iiCI0BEEQfy3detWr9t9/vnn4nb5+fncY8ePH4cgCJg8eXJ4T5boEiRonJ8JgSoqzVwOnLcgb9b0wv8Ff3ljq8evPWGyWGG0OMeAAp2BA3gDkNowmZiwi9E9ZTYwceArC479OQUb4u2AfY1bwpDr11mRrYAzGAzIzs4Wb2u1dnWjoaGB22748OHYsWOHXIclOiGn61thZIZje6cHr8DFKBVcBAH7ppOVSApcMLA5N71S44I2mCEIQl4+/vhjr48tXrw4gmdCdGVY85HAc+DY9kX/MQJScuDKmdGJiibPmWaejg8EWcBFQIE7GQEFLoO5RnJV4Jq5CIHQCrhYpoVSTy6UkpGtgMvJyUFVVZV4u0ePHgCAgwcPctudOnUKFgtV2IR3jjHtkz1SYkMOdWTz4FjIhTI4Mpk39dxU+d21CIIIDKVSiaFDh2LJkiUwm90vgGpra/Hjjz9i1KhRHp/fo0cPFBUVYdGiReE+VaILkMC5UJp8bOkOq9jFeVHgeJMU/9eTbHxQRaPvAs7VRCPQGAEgMjNwbAtlrzB9DrMtlK6FqI75uYZiYALwBT+ZmEhHtgJu6NChKC4uFm9PnjwZNpsNTz75JFpa7IYUn3/+OTZv3ozBgwfLdViiE8IZmITgQOnAawFHM3BBceXw7kjUqKBSCJhzbl60T4cgCABz585FTU0NVq5c6fbYkiVLYDKZcNNNN3l8bkxMDAoLC5Gbmxvu0yS6AGxLXaAtcS0BulD6MzExmq1cEHVNs9FnuDarAKkUAtSqwC+T2S6V2hb5FTibzcYXcJFoofSpwIU2RsG1UFIBJxnZCrgrrrgCp0+fFh0mJ0yYgAsvvBDr169Ht27dkJ6ejhtvvBGCIOCvf/2rXIclOiHH2AiBENonHRR6KOASNKqQV426Kvnp8dj22FTsePwiXNA/I9qnQxAEgDlz5kAQBI+tkosXL0ZCQgJmzpzp8bneZuA++ugjCIKAp556CidPnsScOXOQkZGB2NhYjB49Gt988004vhWigxNSjECAOXD+csOqdO6KW1WT96JKH6KBCQCkxYdXgavXm7hCJ2wtlAneC7imNjln4JzPb6UWSsnIVsDddNNNKCoqwogRI8T7VqxYgTvvvBOpqanQ6XQYNGgQ/ve//+Gyyy6T67BEJ4TPgAvewMQBGyXgICuJDExCIV6jQjcPod4EQUSHXr16YdKkSfj666/R3Ox8Dy0pKcHWrVsxa9YsxMUFd6F3/PhxjBkzBtu3b8fUqVMxcuRI7Nq1C1dddRVWrVol17dAdBISQgjy5hQ4by2UnOmF7/17apn0NQfHFnDBzL8BfOthbbORM/OTA1Z9S9CokBImIzGpJiZJcpqYkAInGdkkCI1GgwEDBnD3JSUl4d///jf+/e9/y3UYogsgdwtl92StW/g0tU8SROfBZrNxK8IdhSStSlYToJtuugkbN27E8uXLccsttwBwGpt4a5+Uwn//+1888MADeOmll6BQ2Nd9X3/9dSxYsADPPfccLrnkktBPnug0sK2PgRZwrALnrYAKROHzVKyV+5iDkxIk7g/WqdlosaKpzcxlqoUKa2DSs1ts2IzE2O/DNQdO1ybfDByrdNIMnHSoh4xoV+jaTKhi3ij6yKDACYKAAVmJ2HmiXrwvmwo4gug0NLWZMfzpjqcE7XvyElkv7GbPno177rkHH3/8MVfA5eTkYOrUqaiurg5qv71798bf/vY3sXgDgHvuuQfPPPMMtm3bBqPRCLWaFHnCDutKqDdaYLXaJOWE2Ww2iTNwzP5NvvfvUYHzESUQagYcACTHxkCpEGCx2pW32maDrH/nZfXhd6AEgExGgWsxWtBiMIvFs5wzcNzPk1ooJSNbCyVByEFpjVN9i41RylZouRqZkAMlQRCdjZSUFEyfPh1r165FRUUFduzYgeLiYtxwww1QKoN38508ebJbgaZSqdC7d2+YTCbU1taGeupEJ8K19dGf0YiDNpMVVqbbUEoOnM0GtPkwJfFcwHmfgWuRoYVSoRCQGs8amcg7B1dW5yxAe4WxgEuNV4MV91gnSrajSU4FjloopSNrAXfw4EH87ne/Q0FBAWJjY6FUKj3+U6lI+CM8w7ZP9k6Pl7RqJwVXI5OsRJqBIwii83HTTTfBYrHgs88+Ew1NQmmfBICePXt6vD8x0f6+ajCEJ+uK6Ji4mo9IbaN0LfS8mZi4KnO+9u+phbKiyZcCx8QYBNlCCQBpbAEncxZcWQQy4ABApVRw3wdXwBlYBS7EGAHOxIQKOKnIVklt3LgRl19+Odra2iAIAlJTU5GQEHr7G9G14A1MQp9/c+BqZJJNChxBdBqStCrse7LjzWGFOvzviWnTpiElJQWLFi3CmTNnMHDgQK/5b1JhWycJwh8alRJqpQJGixWAdCdKdv5NEACtynMBp41RQCFAVOv0BgvgOS2Iy4Bz4HsGLnQFDnDMj+kAgIsxkAO2hTJcGXAO0hM04vmzTpQ6GYO84wKIhSCcyPbp8fDDD6OtrQ1/+ctf8OCDDyIpyd35jyD8cayGNTCRbwFgQJaLAkczcATRaRAEQdYZk46MRqPBtddei3fffRcAcO+990b5jIiuSLxGCaPeXsA1S8yCY5W0uBil1w4cQRAQr1aJKpCvi35PxVqlxAIu2Bk4wCULTsYCzmK14XQ900IZpgw4BxmJGhyqsBeibAHXzAV5h/beSyYmwSHbstr+/fsxbtw4PPPMM1S8EUFzrMqpwPWRUYFLjovB2N6pAOwWv4UeogUIgiA6AzfffDPS0tKQnp6OuXPnRvt0iC5IMFlwegkRAg7YOThvWXA2m81j5lulziAajPg6h9AVODs1MrZQlje2wsyce89wF3BesuDkVODYFspAcwO7MrIVcGlpacjPz5drdxFjy5YtmDZtmtjyOXbsWCxatCjg/eTn50MQBJ//CgoKuOc4wlO9/cvOzpbr2+wQWK02HK9lFLh0eVtw3547Ci9ePRRL/3BeSCtrBEEQ7ZmJEyeipqYG1dXVyMvLi/bpEF0Q1thCJzHigzWw8FfAxUvImqtrMYptnCwWq81rUcUWg8HGCAAuClyLfAUca2CSkagJ+7VMOpcFZ1cSbTYb50IZapA3p8CZSIGTimwtlNOnT8fq1athsVhCcruKJMuWLcP1118Pq9WKSZMmIT09HWvXrsWtt96K/fv34x//+Ifkfc2ePRs1NTUeH9u4cSOOHz+OiRMnenw8KyvLY7h5cnKy5ON3Bs40tqLN5Hyz7S2jAgfYV8RuGJsr6z4JgiAIguBJCEKBY7fzp35x1vNeWjRZA5PYGCVUSkEsJisa2zyOUsgRIwAA6fGsAidfCyU3/9YtvPNvgGcFzmC2cipgyAoco6aaLDYYzVaoVTR36w/ZCrjnnnsOa9aswW233YY33nij3RcfdXV1mD9/PiwWC5YtW4arr74aAFBZWYnzzz8fr7zyCmbMmIHJkydL2p+3Ys9qtYoOXjfffLPHbQoLC/HRRx8F/D10NlgHyqwkTcjWtARBEARBRB6uhVKiMQVbwPltoZRgfMFGCGQnaxGjFKBrs49plDe2YXgv9+fIZWLCz8DJqcBFxoHSQQanwNm/jyYmxBsAEkOcgYuL4X/WrUYLFXASkO0KOSMjA9u3b8cFF1yA/Px8jB49Gj169PDoXiUIAt5//325Dh0U7733HpqamjBz5kyxeAPsathLL72Eq6++Gq+88orkAs4ba9euRXl5OXr06IEpU6aEeNadG9aBUo4Ab4IgiM6OzeZ5lscT2dnZHrfPz8/3eP+8efMwb948r/vbsGGD5GMTXYtgWij59kU/CpyGVeC8FHCMApedpEWMSoHDlfbrDG9h3mwxGBtSC6Wz8JEzB44t4MKZAeeALeBqzipwbPukSiFAGxNaseWqdOpNZiSDTKn8IVsB19TUhGuvvRZFRUWw2WxYu3at123bQwH33XffAbC3Proyffp0aLVarFmzBm1tbdBqg3csdOTwzJkzh6yY/VDCOVDK2z5JEARBEERkYNviJLdQsgYiAczAeQt/rnRR4NRK5zVYuYd4AYBvofRXRPqCzU9r0JtgslgRowz9GvBkhAs41oylutkAm83Gh3hrVRCE0PJ61SoFYpQCTBb7IlKLRNfSro5sBdyDDz6IDRs2YMiQIbjjjjtQUFDQrnPg9u3bBwAe83HUajWGDBmCnTt34vDhwxg2bFhQx2htbcWKFSsA+A5SraysxJNPPony8nIkJyfj3HPPxZVXXgm1Wu31OZ0RtoVSbgMTgiAIgiAiA2stH8wMnF8FTu2/QGQjBLKStFxbnrcoAXlz4JzUtRhliS8qi2CEAMArcEazFU1tZs40JtT5NwexMUqYLPb9UpSANGQr4L766iv06tULW7duRXx8+1ZPmpqa0NjYCADifJorPXv2xM6dO3HixImgC7gvv/wSOp0Ow4YN87mPQ4cO4ZlnnuHuy83NxRdffIGxY8cGdeyOSLhCvAmCIAiCiBwJjAInNQeOVV78z8AxLZReLvjZFsqcZL6A8xbmrZephTJWrUS8WimqgzXNhpALuFajhbPyD3eINwCkxMZApRBE05JqnQG6Nvky4BzEa1RoOqvs6SnMWxKy9fS1trZi3Lhx7b54A4DmZmehEBfneQXD8X3odLqgj/O///0PgHfzEo1Gg7vuugsbNmxAZWUlmpqasHXrVkybNg0nT57EpZdeihMnTkg+3uDBgz3+O3bsWNDfQ6TQG804w7yh0gwcQRAEQXRMeJt/k48tnXA5cH6KJ75A9HzBX9nEK3DZyc4CqsJLC6VcChzgMgcngxPlKcaBUqUQkJMc/gJOoRD4NkqdQdYMOAfsHJy3gpzgkU2BGzFiBCoqKuTanV9mzZqFoqKigJ6zaNGiiClaVVVVWL16NRQKBebMmeNxm5ycHLz99tvcfePGjcN3332HuXPn4pNPPsHf/vY3vPPOO5E45ajCtk+qVQp0Twn/GxNBEARBEPLDZoNJnWliZ9nYoG5PsDNy3hSbcpcZOA2jwFU0tsFms7nNb7XKWsCpxZk1ObLg2AiBHt1ioVSENnsmlfREtVjw1jS7FHAyuYXHUQEXMLIVcE888QQuv/xy/Pjjjx4zzeSmtLQUxcXFAT1Hr7f/8rOzeXq9HklJSW7btrTYC4rExMSgzu+zzz6D2WzGxRdfjO7duwf8/MceewyffPIJVq5cKfk5Bw8e9Hj/4MGDAz5+pGENTHqnxUfsjYkgCIIgCHlJkBC07Qo/A+fHxISbgXO/4NcbzVyhkeNSwBnMVjToTejGmI3YbDboTWwBF9olclq8vAocG+Idifk3B65ZcOGYgWNfa6mxE10d2Qo4tVqNu+++G1dccQXmzp2Liy++2GuMAABMmjQppOPt3bs36OcmJSUhOTkZjY2NOHXqFAYNGuS2zalTpwAAeXl5QR3D4T7py7zEF/369QMAlJeXB/X8jgbNvxEEQRBE54AtwKSamOiDnoFz3z+bAac82waoEABtjAJtJisAu0LHFnAGsxUWJqA6VAUuncmCkyPMO9IOlA5cs+DMFqt4O0G2As75WpOJiTRkK+AmT54MQRBgs9mwaNEicf7LGxZLdH9Aw4cPx6ZNm7B79263As5kMuHAgQPQarXo379/wPs+fPgwduzYgbi4OC5jLhDq6+sBoEPMFMrBMaaFkubfCIIgCKLjEh+MAmcMwIWS27/79SRbwGUkaMSunuwkLY7X2guhyqY2DOru7MByLRxc88kChZ0dq5EhzJvPgIvcmAn3fegMUCmdHVKJWplMTCSY0hA8shVwt9xyS8hZEJFk+vTp2LRpE5YuXeqmkn377bdoa2vDjBkzgsqAc6hvs2bNCjpKYdmyZQA8xxx0Nmw2G3YerxNv988Orm2VIAiCIIjow7bWSS3gOAMRvzlw7MyUBwWONTBhzEuyk50FnKsTJds+CQBxMaHPwDmolaOAi3CEgANXBY4tnhNkmoHjTUyohVIKshVwH330kVy7igi33347nn/+eXz11VdYvny5qJRVVVXh4YcfBgA88MADbs8rLCwEAKxduxY9evTwuO+PP/4YgHf3SQfvvvsuJk6cKO7TwfLly/HII48AAO6+++4AvquOSWlNC/dGOr4gLYpnQxAEQRBEKHBB2wazR8MQV9hCL8GfiYnat0kKFyHA2Pezzo0Vja3cc1qZwkGtUkAVYvA250LZEloLpc1m4xS43Gi1UOoMsDm7TGWcgSMTk0CRrYAbNWoU+vTpgy+++EKuXYaV1NRUfPDBB7juuuswe/ZsTJ48GWlpaVizZg0aGhqwcOFCTJ482e15DuMUk8mzLe7PP/+MkpISZGdn46KLLvJ5Dh9//DHuvPNODBs2DP3794fVasVvv/2GQ4cOAQAeeughzJo1K7RvtAPw87Fa8ev+WQncmwVBEARBEB0LViGz2oBWk8WvKYieKeD8bcvu39OMXaWLA6Wnr10VOLYQDHX+DQDS41kFLrQCrq7FyBW4EZ2BczEx0TLKZDhMTEiBk4ZsBVxxcbGbktTeueaaa7Bp0yY899xz2LZtG4xGIwYNGoR77rkHt956a1D7dLRP3njjjVAqfb8B3HHHHcjIyMDevXuxatUqtLa2IiMjA1dffTXuuusuvwVgZ+HnYzXi1+f1SY/imRAEQRAEESqurXXNBrPPosxq5R0g/btQOh9vNVlgsdo492q2OGMDtLOTvGfBcS2cIbZPArwCV9NskKRCeuNIldPorVtcDLrFyTN7JoX0RF5JTIp1HluuIG9S4AJHtgKuX79+qK2t9b9hO2PChAn44YcfJG9vY7VjD7z99ttu2W7emDt3LubOnSv52J0Rq9WGrYwCN6EvFXAEQRAE0ZGJjVFCIdjVN+CsuuVjvL3VZOFa8/zlwLm6VLaaLFzRyIZ453hR4CpcFLhWE6MAyjDbxc7AGcxWtBgtQc+MHanUiV/3y0qMqOcE2xVlsdq4QHFqoYweoTX4Mtx2223YuHGj2P5HEFL4rbwJ9Xp7O6pCAM4tSI3yGREEQXQcBEEQ/23dutXrdp9//rm4XX5+fuROkOiSCILgNgfnC9fsL3+FjmuLo95l/5yJCTcD572A08sY4g0A3eLUYCNta3TBG5kcrnQqcP2zIuvUnahRcRl6jhgGQD4TE2qhDBzZCrg//elPmDdvHi644AK89tprOHr0KIzG0HMviM4Nq74N7ZmCJJksaQmCILoaDgMtTzja+wkiUrAX92yotifYDDiFAK5g8IRGpYCKqY7Y+TCzxYpqpljiZuCYYk5nMHPPY88hVoYWSqVCQCo7B9cSSgHnVOD6Z0XWqVsQBK/eBHJds/GuoqTASUG2Ak6pVOLdd99FdXU1HnzwQQwYMACxsbFQKpVu/1Qq2To3iQ7OFmb+bUIfcp8kCIIIFKVSiaFDh2LJkiUwm90vlGtra/Hjjz92iVgaov2QEKQCF69W+W0RFATBa9tddbMBTB43V7SlJWi4wo9V4VjlRw4FDgDS4tk5uOBFDXYGrm9m5LNy2Sw4FrmCvNmCWe/BVZRwR7ZKqlevXh0qB46IPkazFdtLnflvNP9GEAQRHHPnzsUjjzyClStXYvr06dxjS5Ysgclkwk033YTdu3dH6QyJrgbXQumnLY5zgPQz/8buv+mssscWiGxRlhwbw2WMKRUCspK0ON3QKm7rKIhYExU5ZuCAs3Nwlfavg3WirGk2oI6JIYi0AgfAqwIXlhZKE7VQSkE2Be748eMoLS2V/I8g9p9qEFfN1CoFzsnrFuUzIgiC6JjMmTMHgiB4bJVcvHgxEhISMHPmTI/Ptdls+PTTT3HDDTegf//+iI+PR2JiIsaOHYu3334bVquV2/706dNIS0uDRqPB3r173fb3/PPPQxAEXHbZZX6Nv4jOSyAtlJwCJ7EoYFUy9vlsAceqb+J9yZ6dKFtldqEEXLLgggzzZtsnU+PVXtWwcOKpgNOoFFD7aXWVClu0t1ILpSRkK+AIIlC2HHXOv52T243LFiEIgiCk06tXL0yaNAlff/01mpud7VYlJSXYunUrZs2ahbg4z9lRBoMBc+bMwZo1a5CdnY0rrrgC48aNw8GDB3H33Xdj/vz53PY9evTAO++8A6PRiDlz5qC11RmIvGPHDjz11FNIT0/Hhx9+SJ05XRh/WW0sbNucvwgBB3yLpvP5bFHGFmvifWyUABPmLXcOHACkcTNwwSlwRxgDk35RaJ8EPLdQJsroWcAV49RCKYmwFnD19fWor68P5yGIDgw3/9aX5t8IgiBC4aabboJer8fy5cvF+xzGJjfddJPX56lUKqxYsQLl5eXYtGkTPvvsM6xevRrHjx/H6NGj8d///hebNm3injN79mzMmzcPRUVFeOihhwAALS0tmDt3LsxmM959913k5OSE4bskOgpsRlggM3BSiydvzoVcAedHgWPz4tgYgViJRaQ/WOWqWgYFLhrtk4BnBU6uCAHAPdfPaiXl3h+yF3Dff/89Lr30UiQkJCA9PR3p6elISEjAZZddhu+//17uwxEdlFajBXtOOov782j+jSCIYLHZgNaGjvdP5vbC2bNnQ6PRcG6UH3/8MXJycjB16lSvz1OpVLjqqqsQE8OvqGdkZOCFF14AAHz11Vduz3vzzTdRUFCAf/7zn/j+++9x//3348iRI7j99ttx1VVXyfNNER2WBEaBa/ajqrAFntQWSl7hc+6/kg3x9qDAsVECbF4ca4QSHw4FLsgCjjUwiXSEgIMMjwqcfAVcrMvr3WoiFc4fstpBLliwAG+++abY856cnAxBENDQ0IBVq1Zh9erVuO+++/Dqq6/KeViiA7LjeB1MFvvvSYJGhWE9kqN8RgRBdFjaGoG/50X7LALnzyeA2BTZdpeSkoLp06fjq6++QkVFBcrKylBcXIwFCxZAqfR/Qbp3716sWrUKJ06cgF6vh81mg05nX/0/cuSI2/aJiYlYvHgxJk6ciBtuuAE6nQ79+vXD66+/Ltv3RHRc2EKs2WDyuS1XPEku4Dy7XLKqWo6HAo7NhStv9FzAuRYUwcLPwAXeQmmz2dxCvKOBJwVOLgMTwEOun9Ei+fegqyLbq7NkyRK88cYbyMzMxF/+8hfcfPPNSE62X5Q3NTXhf//7H5577jm88cYbGDduHK677jq5Dk10QNj2yXN7p0KlpHFMgiCIULnpppuwfPlyfPbZZ6JhmK/2SQAwGo2YN28ePv30U6/bOAo5V8aPH497770Xr732GgDgf//7H+Lj44M8e6IzwVrM+5tr4hS4IFooW5jiq9JPC6W3MG8+RkBGF8qzBDMDV9NsRL3eWfxGrYXSgwInZwGnVSkhCM6mBDIy8Y9sV81vv/02tFotNm3ahHvuuUcs3gAgKSkJd999NzZu3AiNRoO3335brsMSHZSfGQMTap8kCIKQh2nTpiElJQWLFi3CkiVLMHDgQL/5b6+++io+/fRTDB06FD/88AMqKythNBphs9lQXFwMAF7dJPV6Pb777jvx9q5du+T7ZogOTQKnwPkxMWEdICUWT/FcDpx9/zabjZuBy/IzA1fbYoTBbPFwDvIocOlMDly93gizxepja3dY9S09Qc0Fg0eS9ET348ppYqJQCFwWnL/YCUJGBW7fvn2YMmUK+vfv73Wb/v37Y8qUKfjpp5/kOizRAWnUm3DgTKN4mwxMCIIICW2yvR2xo6GVv3Vco9Hg2muvxbvvvgsAuPfee/0+Z8WKFQCATz/9FIMHD+YeKykp8fncBQsW4PDhw5g5cybWrVuHBx98EFOmTEFhYWGQ3wHRWWCNKfyZmLAFXoLEHLg4Dy6Uja0mtJmcRZInF8rMRP6+qiYDeqXG8TECsrVQOgsfmw2o15u8Zqp5gjUw6ZcZHfUNsBfVCRoV93OScwbOcQxHEa0nBc4vsr36RqNRUttEfHw8jMbg0+iJjs/WklpRJk+LV6N/FN+UCILoBAiCrLNkHZ2bb74Zy5cvhyAImDt3rt/tHW7RPXv2dHvs888/9/q8r7/+Gv/5z3/Qt29fLF68GF988QXmz5+PuXPnYtu2bW6mKETXIj4gBY5pX5TYmpfgIaaAVd/UKgW6xbn/DqpVCqQnaFBz1lSkvLENvVLjglIB/RGvUSE2RimactQ0GwIr4NqBgYmD9AR1mAs4d0WV8I5sLZR9+vTBxo0b0dLS4nUbvV6PjRs3ok+fPnIdluiA/MzMv43vkwaFgnKCCIIg5GLixImoqalBdXU18vL8m7s4Omf+/e9/c/cvXboUixYt8vicyspK3H777VCpVGJQ+O9+9zvMmjULu3fvxl//+tfQvxGiQ8Ne4Psr4FoMgTtA8jNwZws4lxBvbzmEOVyUgD0Lji0a5DIxAVzm4AI0MmFbKPtGaf7NgWvhKecMHOBawJEC5w/ZCrjrrrsOVVVVuOqqqzy6VR07dgxXX301qqurcf3118t1WKID8vMx5/zbBJp/IwiCiCoPP/wwlEolHnnkEYwePRpz5szBmDFjcO2112LBggUenzN//nxUV1fjr3/9K84991zxfkf+28svv+yWHUd0Lby5RHqCfVzyDJzG/YLftYDzRraHKIFwzMABLk6ULdKjBGw2Gw4zId79oxTi7cC1gJNzBg7gX3MyMfGPbAXcgw8+iFGjRmHt2rUYNGgQzj33XFx//fW4/vrrMW7cOBQWFmLVqlU455xz8MADD8h1WKKDUdnUhqNMS8CEPlTAEQRBRJNJkybhp59+wpQpU1BSUoJvv/0WarUay5Ytw9133+22/dtvv43vv/8e48ePx+OPP849lpaWhg8//BA2mw0333wzGhsb3Z5PdA34HDh/Qd6BxwjEeZix40K8Pcy/iY+5RAnYbDYueyxephZKAEhnjEdqAlDgqnUGNLZG34HSgasTZUIYZuAckImJf2R79WNjY7FhwwY8+uij+OCDD7Bjxw7s2LGDe3z+/Pl44YUXEBsbK9dhiQ4G2z7ZIyUWvVLpd4EgCCJYvLlDeiI7O9vr9uPGjcPatWslHeOPf/wj/vjHP3o9zqWXXgqrNTC3PaLzwRZiJosNBrMFGpVnZYttX4yXaGLCtvA5LvgrpRZwLlECbSYr2F/z8LVQSlfgWPUtPUGDblFyoGTPgSWcM3CkwPlH1lc/ISEB//d//4e///3v2LVrF86cOQMA6N69O8455xzExcXJeTiiA7LlKNs+mea1P50gCIIgiI6L64xUi8F7AcfOwEltoeRmpgzuLZSeIgQccFlwTW1uik/YWigDUOCOVDnn36JtYAJ4aKEM4wycv9xAQuYCzkFcXBwmTpwYjl0THRibzYatNP9GEARBEJ0e1zbEFoPZa44ZF+QtUYGL96DAlTMFXI7EFsqKxjY3xYfNJAsVVrmqCVKBi3b7JBD+GbhY5vdFb6IWSn/INgNHEP44UavH6YZW8fb4PpT/RhAEQRCdEYVC4FQVXZvni3KLNbj5M7aAazNZYbZYuRZKXwoc20JZpTNw5xYbo5TVHTudaaGsaQlAgWMz4NqhAif3DFy8TC2UTW0mfLr9JPaVNchwVu2XoF/9Z555JqQDP/HEEyE9n+h4bGHm3/plJriFaRIEQRAE0XlI0DjDmb0ZU7DFGyDdxMQ1bqBeb0K93mn6IXUGzmK14WSdMwJLzvZJAEiLZ1sopSlwdgdKtoWyPSpw7bOF8tFlv+K7X8uhVimw8v5J6J3uP6O6IxL0q//UU09BEISABqjZeScq4LoeFB9AEARBEF2HBI0KVTp70eLNiVJvCG7+zHVWrqTa2XIoCECmj8DsOLUKybExosvjsWpnASengQkQXA5clc6AJkYV7J8Z/QIuO0mLsb1Tsb20DpP6ZyBJ7hgBpnBvDbKFsq7FiB8PVgAAjGYrlu8+hQcuGSDL+bU3gi7gXnjhhYC2P336NN5//320traScUUXxGrl59/Oo/ZJgiAIgujUsGpas5cWSrawUykEaFTSpnvUKgVilAJMFruQUFLjLMLSEzSIUfreT3aSVizgSpgCTs4IAYAv4FpNFuiNZr9GLaz6lpmoQXKcvMVSMAiCgI9vPxdF5U0YlJMk+/7lUOBWHayAxeoUlr77tRwLL+7fKeuOoH9L//znP0varrKyEn/729/w3nvvoa2tDUlJSbj//vuDPSzRQTlUoUPd2d5vhQCcW0AFHEEQBEF0ZlhDEm9h3q4B2oFcbMdrVGg42zZ5jMmY9RXiLW6TrEXx2UKptMb5XLkVuNQ4NQQBYkxBbbMRcan+Cjjn+bSH+TcHMUoFhvVMCcu+WeOYYGfgvj9Qwd0uqW5BcaUOhdnyF5zRJmwmJtXV1XjggQdQUFCAt956CzExMXj88cdx/PhxPPXUU+E6LNFOYfPfhvZIRnJs9FeTCIIgCIIIHwka52e9txZK3oEyMF2BVcuOMS2UvgxMHLAulax6J/cMnEqpQLc4pwpXLWEOjjMwaQftk5GA/dkH40LZoDfi56M1bvd//2uFh607PrIXcLW1tXj44YdRUFCA1157DUqlEo888ghKS0vx7LPPIiUlRe5DEh0Adv7tPJp/IwiCIIhOT4LGf1ucqwIXCOz2bBHmK0LAAVvkNTDmJ3IXcACQFh/YHFx7MzCJBLEecv0CYfVvlTBb3X05vv+1PKTzaq/IVsDV19fjscceQ+/evfGPf/wDAPDwww+jtLQUzz//PFJTU+U6FNHBMFms+KWEMTDpQwUcQRAEQXR2WFWlXu+5cGGVOdfw70D2X1anF7/25UDpwFuRJzVIPBB4IxPfCpzNZsORKjYDrv20UIYTVk3VB9FC+QPTPnlB/wzx66NVzVxB3FkIuYBrbGzEX//6V/Tu3RsvvvgirFYrHnjgAZSWluLFF19EWhrNOnV1SmtaYDq7KqJWKnBOXrconxFBEARBEOEmP81p4b7yYAXMFqvbNnomXiDQ4omdsWPFFyktlN6KvLAocEyYd62fLLjKJj6Xrl8XUeA4ExMvkRPeaGozYfORavH27ycVYGiPZPH2d/s7nwoXdAHX1NSEp556Cvn5+Xj++edhMpmwYMEClJSU4OWXX0Z6OqkshJ3+WYnY/+Ql+OSOc/HMzMGyDwgTBEEQBNH+mDmyO2KUdlOS8sY2rCmqdNuGba1kCzIpeCv4pLRQeivgwnGNksXk3m5jOpI8wapFWUmaLuMZEBtCkPfaokrRjTQ1Xo2xvVMxbWiO+HhnbKMMuoDLy8vDs88+C6PRiPvuuw8lJSV45ZVXkJmZKef5EZ0EbYwS5/VJxw1jc6N9KgRBEARBRIDMRC0uG+K8kF609YTbNqyJScAKnJdiS5KJSVKsx/vDocBdNNB5bbz5SA0OVTR53bYrzr8BfAul2WqD0eyu1nqDNSq5dHAWVEoFpjMF3JGqZs4YpjMQdAHX2NgIADAYDPjnP/+JXr16Qa1WS/qn0XgPVyQIgiAIgiA6B7eMzxO//vlYLY5W8RfSLUZWgQt+Bo5FygxcUqyKs653EI4ZuPF90rjstPc2l3rd9ggbIdBFHCgBd+VTL7GNstlgxsbDzvZJh/KWmxaHIT2cr/l3nUyFC2kGzmazwWq1wmw2B/TPZDL53zlBEARBED4RBEH8t3XrVq/bff755+J2+fn5kTtBosszOq8bCrOdhcj/XFQ49kLdm6LmDU8FXKJGJckMRRAEj62W4VDgBEHAHZN6i7e/2nsaVU1tHrc9XMUqcF3DwARwf92lGpmsLaoU1bqUuBiMY3KGL2fU3x86WZxA0AWc1WoN6R9BEARBEPLx8ccfe31s8eLFETwTgnAiCAJuGZ8v3l62+zTXNsnOwMUFqMB5KrayJKhv4rYeWi3DUcABwIxh3cWAcZPFhv9uPe62jc1mw1EuxLvrKHAxSgXUSmdZIlWBYwuzSwZlIYbZB9tGWVypw1HG3bOjE7Ygb4IgCIIgwo9SqcTQoUOxZMkSmM3uFz21tbX48ccfMWrUqCicHUEAV43sjsSzxVmzwYwVe06Lj7VwMQIBKnAe2h2zJcy/OfCkwMWGoYUSsBco8ybki7cXbzvpVqSUN7ZBx7wefTO7jgIHuGTBSVDgWgxmrC+uEm9fzhRsAJCfHs+1rnYmMxMq4AiCIAiigzN37lzU1NRg5cqVbo8tWbIEJpMJN910UxTOjCDsc2XXnNNTvP2/rSdgs9ldA1tCihHwUMAFoMB52jbQNs5AuHFsrrj/xlYTlu46xT3OGphkJ2m7jAOlg/gAC7gNxdUwnG2fTNSqPOYMTx/WOd0oqYAjCIIgiA7OnDlzIAiCx1bJxYsXIyEhATNnzvS5j6KiIsybNw+9evWCRqNBVlYWbrjhBhw8eNBt27a2Nrz//vuYOXMmCgoKEBsbi5SUFEyaNAmfffaZx/3PmzcPgiBgw4YN2LRpE6ZMmYLExEQkJSVh+vTp+O2334L75okOwc2MmUlxpQ7bS+sA8BfqgcYIeNo+EAXOUwEXzqij5NgYXDeml3j7vc2l/8/eVYdXdTztOVfiSjwkJJAAQYK7u4QSvDgUaSmuBQo/XIsXa3GnxUspXrS4F7egCQ7BY/fe9/vjfjs9J/dGIFCgnPd57lOaOWd3ds/s7Ozu7AwZZQnsFAFMPqP7bwLKE7i0XSg3nf1nQVY1tw/Z6CyXNfJ0AhfvvaCoh/8NN0p1AadChQoVKlR84ggMDKRy5crR77//Ti9f/mOgXLt2jQ4ePEj16tUjBweHFN//7bffqGDBgrRo0SLy9PSkyMhIypo1K61cuZKKFStGe/fuVTx/48YNat++PR07doyCg4OpTp06VKBAATp06BA1bdqUhg4dmmJdGzZsoEqVKtHr168pIiKC/Pz8aNOmTVSuXDm6d++/FWhAxT8I8XKiMqH/nJAsPmQOZpKRNALWnn+TO3DWFnvvIwqlHG1LZyWNOTUe3Xrymraf/0fmrzz4PFMICMhPVNM6gYtLNNKui/+4T0bk9bP6XFZPR8old6P8jyT1fr9SqkKFChUqVLxnAKAXSZ9ejh9nvTNJkvTOymvRogXt2bOH1q5dS61atSKifwKbpOY+eePGDWrRogXp9Xr6448/qEqVKkzbsmULRUZGUosWLejq1atkY2NDREReXl60fft2qly5sqIN169fp0qVKtGIESPoq6++shrxcsqUKbRmzRqqW7cuEREZjUZq3LgxrVmzhmbOnEnDhw/PaFeo+EjRsmQQ7bv6iIiItp69Rw+exytcKNMTPVIOaydwfm90B84yF9z7CmIiEJjJgWrm9eOw9nP+us658i7LTuA+pwiUAvK0Dq8TUl/A7bn8kBd5TrY6KpPd0n1SoFa4L124a869t/HMXepaOfs74PbDQl3AqVChQoWKTxovkl5Q6V9Kf2g23hj7m+4nFxuXtB9MJxo2bEhdunShZcuWKRZwfn5+VLlyZXr48KHV96ZMmUKvXr2iadOmKRZvREQ1atSgjh070tSpU2njxo1Ur149IiLy8PCweJaIKGvWrDRw4ED6+uuvacOGDdS1a1eLZ5o2bcqLNyJzEJbvv/+e1qxZY3HSp+K/hcph3uTvakd3nsWTwQT65chthaH+posnq0FM3iQKpatlXuL3vYAjImpfNisv4I7fjKUTt2KpYKCbIkri5xSBUkB5Ape6C+Vmmftk5VzeZGclp59AzXA/mrDtMhGZ3SivPXxJ2bw+7QWy6kKpQoUKFSpU/Afg5uZGtWrVoh07dtC9e/fo6NGjdOnSJWrSpAlptSkbN9u2bSMiovr161ully1bloiIjhw5YkHbt28fjRw5kjp27Eht2rShr776ilatWkVERFeuXLFaXrVq1Sz+liNHDiIiunv3v+HepMI6dFoNNSuehf9/+ZGbiqiLb57I20oagTc4gfN0tCWdRnkK/r5dKImICmZxpyJB7vz/c/+6RneexdNLWV9k/8wiUBIluwOXlPIJXHySkXZckEWfTMF9UiDEy0mRi3Dz2U/fVfuzXcC9evWKlixZQl27dqXixYuTra0tSZKUqt9+erBhwwYqX748ubi4kIuLC1WoUIE2btyY6jvnzp2jRo0akZeXF9nb21N4eDhNmTJFzZenQoUKFSreCC1atCCj0Ui//vorBzRJK/rkjRs3iIgoc+bMisTg4teoUSMiInr06BG/8+zZM6pcuTKVLVuWBg0aRD///DMtXLiQFi1axAvCFy+su7UGBARY/M3Z2WxcJSQkvFmDVXxyaFw0C+m15kXT/ecJnISZ6M1Pv5IvtvRaiTwcbdL9vkYjWSz4/o0TOCKi9mWz8b+3nL1HOy/c5//3d7UjZ7vPKwIlEZFDOl0o/7ryiBe7DjZaqpDTK82y5cFMNv4H7sF9ti6UV65cYReTd4UpU6ZQz549SafTUZUqVcjW1pa2bdtGX3zxBU2bNo26dOli8c7BgwepcuXKFBcXR8WKFaPg4GDau3cv9ezZkw4cOEArVqx4p3ckVKhQoeK/Bme9M+1vuv9Ds/HGcNa/exepiIgIcnNzo8WLF9OdO3coV65caeZ/E5uFrVu3TvW54sWL87/79etHO3fupPLly9OwYcMob9685ObmRlqtlrZt20bVq1fnMPHJodF8tnvHKojIy9mWIsL9aP2pOxa0N78Dp3ze29mONJo3s5n8XO0o5mkcERFJEpGtlUiG7wNVc/tQkIcD3Xz8mkwgGr/1EtM+R/dJovQHMdksSwdQMSx190mBiHA/mrTd7EZ5/u5zuvHoFQV7OmaA2w+Lz3YB5+zsTO3ataOiRYtS0aJFaePGjTR48OC3Lu/SpUvUp08fsrW1pV27dlHJkiWJiOjy5ctUqlQp6tmzJ9WoUYNCQ0P5naSkJGrevDnFxcXRpEmTqGfPnkRE9PLlS6pWrRqtWrWKIiIi6KuvvspQW1WoUKHivwxJkt7pXbJPGba2ttSoUSOaM2cOERF169YtzXcCAgIoKiqKJk6cSB4eHumqZ926daTVaun3338nFxdl31+7du3NGVfxWaFVySCrC7g3j0KpNNzf5P6bgDxqpaON7l/bNNdqJGpXJisNXm9O0/E8/h/3yc8xgAlR+tIIJBiMtF12WplS9MnkCPV2opw+znTp/3PtbTxzlzpXDE3jrY8Xn+02WEhICM2dO5c6dOhAhQoVIr0+Y0fVP/74IxmNRvr222958UZk9usfOHAgGQwG+vHHHxXvrFu3jq5fv0758+fnxRsRkZOTE02fPp2IiCZOnJghvlSoUKFCxeeFli1bkoeHB3l6elLz5s3TfL5q1apEZJ6T0ovY2Fi+KpAcK1euTD+zKj5LFMriTrn9lLKj10pW83ilBr1Wo3jnTXLACcijVr7PHHDW0LBwgNVk3Z/tCVw6EnkfuPqYXvz/YtdOr6GKYWm7TwrI3Sg/9aTen+0C7l1D3HNr2LChBU38bcOGDel+p1ChQpQtWzY6e/Ys309QoUKFChUq0kLZsmXp0aNH9PDhQwoKCkrz+d69e5O9vT316dOH1q5da0FPSEig1atXU3R0NP8tR44cFBsbSytWrFA8O3nyZNq1a1fGG6HiPw1JkqhVSaVsvmkAEwG52+XbnMDJ3/m37r/9U5+OWpTIYvH3zzGACRGRvY3ShRIAvYhPoujY13TuzjM6EPWIlv5//kAiooo5vd/o1DYi3Jf/fe7Oc7r5+NW7YfwDQF3AvQM8ffqUbt26RUREBQsWtKAHBgaSp6cn3bx5k54/f85///vvv4mIUryfIP5++vTpd82yChUqVKhQQUREoaGh9Msvv1BSUhI1aNCAsmfPTpGRkdS0aVMqV64ceXh4UKNGjRRBTL7//nsiImrSpAmVK1eOmjVrRnny5KE+ffooPEpUqEgJkQX8ydnuH+PbWkqA9EC+6HqbE7gA939ywVk7DXvfaF0ymGy0SnP8cz2Bk3/LXZceUOjAzRQ+dBuV+WEX1Zq6j5rNOUw7ZMm7a4anz31SILuPs2JxvPETPoVTF3DvAGLx5u7uTo6O1i9EiqhbN2/etHjPWkSulN5RoUKFChUq3jXq1KlDp0+fpk6dOpEkSbR9+3bauHEjPXjwgGrXrk0rV66k3Llz8/PNmzenjRs3UokSJejUqVO0efNm8vf3p507d1JkZOQHbImKTwUONjpqVDhQ9v9vd/olj9b4NidwFXJ6U5ivM9loNdSyRNon1u8a3i52FFnAn/8/s5v9Gwdz+a9AfgprNIGMJuuBkIjM7paVwrzfuA65G+XmM59uOoHPU0LeMV6+NCdedHBwSPEZsbCTh1VO6z1r76SGPHnyWP17VFQUhYSEpKsMFSpUqFDx6SClSI/W4Ovrm+rzISEhNGPGjHSXFxERQREREenma+HChbRw4cIUy3uTtqj4b6BtmWBaezKanr5Oosq5fN6qjHoF/enC3efk5WybrnDyyWGn19Lm7mXpZYLhg4Xu71ghhDafuUuvEo1UPY9v2i/8R1EsOBO52OkUAV0EHG205GqvJxd7PXk521K7MlnfaqFbK58f7b/6iCLC/ahm+Kfb15/sAq5evXp04cKFN3pn8eLFVKxYsffEkQoVKlSoUKFChYr0IsDdgbZ0L0fRsa+psCyx9Zvgm3IhVD2PL/m42KUrnLw1SJL0QfOuhXg50abuZenG49dUJtTzg/HxoeHrakd/9i5Pl++9JCc7Hbna68nVXk/OdjrSa9+N02AOH2da3bHUOynrQ+KTXcBdv36dLl26lPaDMrx+/fq98OLk5JRm+a9emS9KimSl4r3Y2NgU37P2Tmo4d+6c1b+ndDKnQoUKFSpUqFDxIeHravdWro9yBHl8uvm8BII8HP8T7cgovJ3tyNs5Y/LwOeCTXcCdOnXqQ7PAyJLFHEEoNjaWXr16ZfUenIjeJY8IliVLFoqNjaXo6GjKly9fut5RoUKFChUqVKhQoULF5ws1iMk7gJubGy/iTp48aUG/ffs2PXr0iIKCghQ5c/Lnz09ERCdOnLBarvi7tcWdChUqVKhQoUKFChUqPj+oC7h3hFq1ahER0erVqy1o4m+1a9dO9zsnT56ka9euUd68eSk4OPgdc6tChQoVKlSoUKFChYpPEeoC7g0RFhZGYWFhFBMTo/h79+7dSavV0s8//0yHDh3iv1+5coVGjRpFOp2OunfvrninXr16lDVrVvr7779p8uTJ/PdXr15R586dicicYFWFChUqVKhQoUKFChUqiD7hO3DvAvXq1aO7d81J/O7cuUNERHPnzqUtW7YQEZGfnx+tW7dO8Y4InJKUlKT4e86cOWn8+PHUq1cvKlu2LFWtWpVsbGxo27ZtFBcXR1OnTqXQ0FDFO3q9npYuXUpVqlShXr160YoVKygoKIj++usvunv3LjVs2JBat279XtquQoUKFZ8KJEnif6uh5lWYTCb+t1w2VKhQoeJzwWe9gDt58qRFkuyYmBg+XXvT4CE9e/ak0NBQGj9+PP31119ERFSkSBHq27cvffHFF1bfKVWqFB09epSGDBlCu3fvpr///ptCQkLou+++o+7du6uTkwoVKj57SJJEWq2WjEYjJSQkpJpzU8V/H/Hx8UREpNPp1DlShQoVnyUkqNuZ/3mINAIppRlQoUKFio8d0dHR9OLFC8qUKRP5+Lxdwl8Vnz4AUHR0NL18+ZLc3d3J1/fTTcSrQoWKzxsZsc8/6xM4FSpUqFDxacDFxYVevHhBT548IZ1ORy4uLqTVvl3SXhWfHkwmE8XHx1NsbCy9fPmSiIhcXV0/MFcqVKhQ8WGgLuBUqFChQsVHD2dnZ3J1daVnz57RgwcP6MGDBx+aJRUfEJkzZyZ7e/sPzYYKFSpUfBCoCzgVKlSoUPHRQ5Ik8vX1JXt7e4qNjaWEhIQPzZKKfxk6nY4X8uriTYUKFZ8z1AWcChUqVKj4JKDRaMjd3Z3c3d0JgBqR8jOCJElqwBIVKlSo+H+oCzgVKlSoUPHJQTXoVahQoULF5wo1kbcKFSpUqFChQoUKFSpUfCJQF3AqVKhQoUKFChUqVKhQ8YlAXcCpUKFChQoVKlSoUKFCxScCdQGnQoUKFSpUqFChQoUKFZ8I1AWcChUqVKhQoUKFChUqVHwiUBdwKlSoUKFChQoVKlSoUPGJQIKaSOc/D2dnZ0pKSqKQkJAPzYoKFSpUqFChQoUKFZ89oqKiSK/X04sXL974XfUE7jOAo6Mj6fX6D80GRUVFUVRU1AejqzyoPKg8qDyoPKg8qDyoPPwXeVR5eLdl/BvQ6/Xk6Oj4Vu+qJ3Aq/jXkyZOHiIjOnTv3QegqDyoPKg8qDyoPKg8qDyoP/0UeVR7ebRkfO9QTOBUqVKhQoUKFChUqVKj4RKAu4FSoUKFChQoVKlSoUKHiE4G6gFOhQoUKFSpUqFChQoWKTwTqAk6FChUqVKhQoUKFChUqPhGoCzgVKlSoUKFChQoVKlSo+ESgRqFUoUKFChUqVKhQoUKFik8E6gmcChUqVKhQoUKFChUqVHwiUBdwKlSoUKFChQoVKlSoUPGJQF3AqVChQoUKFSpUqFChQsUnAnUBp0KFChUqVKhQoUKFChWfCNQFnAoVKlSoUKFChQoVKlR8IlAXcCpUqFChQoUKFSpUqFDxiUBdwKlQoUKFChUqVKhQoULFJwJ1AadChQoVKlSoUKFChQoVnwjUBZwKFSpUqFChQoUKFSpUfCJQF3Aq3jkAZPjZtMr4N+p423LfVfnpLcNkMr2Tct5XHemp22g0vlXZ77KOtMpKTx0vXryg+/fvv9c63sX7b/pscqRHHjLKQ0bryOj76ZWXjPDxLupIrYx/Qwelp47nz5/T+fPn37qc981Det5/33X8GzykVc6/oT8+prn9fdkG6S0rNd3wMXwLovTJU2plfQz64b8KdQGn4o0xatQo2rNnT4p0SZIU/29tcD169IhMJhMrsOTPpFWGnJ7S4M1oHRltZ3p4TAtplbFmzRoaOXIkXbhwgYgsJ4SVK1fS06dPSZKktzZoM1pHWv3066+/Uo8ePejEiRNWy09PGe+ijjlz5tCjR4/S1Q5r32L8+PHk6upKa9euTbGOUaNG0fXr10mSJKtlpFVHRt9PTxlpIS15SA/SGpsZrSOj76dHXtJCdHQ0GQyGFBdY76KOtMpIPi7epo60ZCo948LNzY2WLl1q9Zn06Kj3zUN6xs37ruPf4CGjc2J6yvg35s2MtuNd9ENayKiOexffIi1kVJ6I3mz+/1D64b8K3YdmQMWng3Xr1tF3331H165do969e1OZMmVIq9UyfefOnbR69WoyGo2k0+noyy+/pPLlyysG16ZNm2jWrFn06NEj0ul0VKxYMRo6dCg5Ojqmq4xt27bRb7/9Rk5OTuTp6UnNmzenzJkzK/jMaB0ZbeeOHTto8+bN5OTkRO7u7tS0aVPy9vZW8Lhx40ZydHSkChUqkNFoVJSfnjI2bNhA33//Pe9aPXr0iKZOnUoajXlP5tChQ1SnTh16+PAhjR49mvr378+09PKQ0Tq2b99Oa9asITs7O3J1daUmTZpQrly5uJ82btxIffv2pYsXLxIAevjwIf36669vVMa7qOP48eNUr149io6OpjNnztDUqVMVMvvnn3/S9u3bydXVlby9valp06YsS0JeevbsSbdu3WL56Nixo6KOdevWUd++fSkqKooSExNp2LBhb1xHRt5PTxkZlYe03idKe2xmtI6Mvp8eeVm9ejXZ29tTrVq1yGAwkE6nsyhj+vTpdPfuXSIiypMnDw0dOpSyZ8/+TutIrYzt27fT8uXLKS4ujrRaLX3xxRfUtGlTpqdVPlHaOigt+rp166hHjx50+/ZtIjLrC6J/jK306Kh9+/bR/v37yd7enhwcHKhhw4bk5ub2znhIq/x/o470zBcZ5SGjc2J6ysjovPlvzO3voh/S0iEZ1XHv4ltk1L5IS57E31Ibvx+DfvjPAypUpIGzZ8+iQoUKkCQJOXPmxJgxYxAVFcX0S5cuoXr16pAkCZIkwcHBgf89Z84cAEBUVBRq1aoFSZJgb2+PwMBAaLVaSJKEdu3aYffu3amWcfv2bdSpUweSJEGj0cDW1haSJCEgIACLFy8GAFy/fj1DdWS0ndevX0dERAT/TaPRQJIkhISEYNmyZQCAmzdvom7dupAkCXnz5uWyTSaTRRuSl7F06VIkJiZi6NCh0Gg0CAkJQdeuXXHw4EE8fvyYywCACRMmQJIk2NjYoFChQrh06RIAwGg0psqDyWTKcB3Xr19HZGQkt8He3p77a/LkyXj27Bl++OEH6PV6hIaGomfPnjh27BhiY2O57Js3b6ZYxpQpU3DhwoUM1yGwZMkSboeHhwdOnjwJALh27ZqiDvErUaIENm3apJCXHDlyoEWLFnBxcUGlSpVw//79FMfO1atXU2xnanW8zfvp5SEj8pCWTKc1Ntu2bYuoqKi3riOjPJpMJhgMhjTl5fbt22jcuDEkSYKTk5NFO2/cuMHfwtnZGdmzZ4ezszMkSULNmjVx9OjRDNWRHj4vX76MGjVqsBy4u7vzv0eNGoXr16+n2obk38qaHkuLnlzeunTpAg8PD4SEhODy5ctcT2o66uLFi6hSpYqFXJcqVQpbtmxJU9emxUNa5QPA1atXM9TOjLYhPd8iLR4yOicCac/d27dvz9C8+W/M7fK+ftt+uH79errn7rfRce/Cjjp48OBb2xdvMnZTG79yHj+UfgAAg8GA/zrUBZyKFPHq1St07doVkiQhU6ZM6NKlC44cOcL0pKQkzJ8/H76+vnB1dUXbtm2xefNmnDlzBmPHjoUkSXB1dcX58+dRpkwZSJKE9u3bY8+ePTAajdi2bRvy5s0LGxsbZMqUCa6urmjTpo3VMlq0aAFJktC6dWvs3bsXJ0+exLBhwyBJEuzs7DBy5EiULFnyreogItjY2HA7O3fu/MbtdHZ2Rrly5SBJEr766its27YNu3fvRq9evVgZ9+7dG6VKlYIkSTxJ/fTTTwDMyubBgweoWLFiqmUMGjQI7u7uKFy4MP76668Uv93XX3+NoKAgVnK9evUCAOzZswdly5ZNkQcAOHPmzFvX8fLlS9SuXRuSJKFly5bYuXMnTp8+jXHjxrEy7tChA/z8/JAnTx7s3r3boty0yhDGq6Dv2LHjjeuQY+jQociaNStq1qwJSZLw5ZdfwmAwoEGDBjz57ty5Ezt27MC3336rkBd3d3d07doVp06dwrFjx+Dh4YHs2bPj3r176NatW4pjR/R3eup4m/fFuBELipTKyKg8pOf9W7dupTj+w8PD4ejoiNmzZ2eojozyeO3aNfj7+yNPnjzYs2ePhYwcOHAAVatWVRhWI0eO5DIeP37MxtdXX33FZRw9ehRffPEFbG1tMXr06AzVkRqfBoMBixcvRubMmeHi4sKGdUxMDObOnQuNRgNHR0fWkSmVn5YOsre3R86cOa3SiQg6nU4xLo4fP45r166hUKFC8Pf3x7Vr15hna/rDaDRi+fLl8Pb2houLC5o3b46FCxdi3LhxKF68OBv2wgB8Ux78/PwwZcqUVMsPCgpCTExMiv3wLupIrQ3i+4wfPz7FOSU9fb1v374Mz7upzd158uRhHt523nzfc7uYm8X77dq1e6t+2Lx5c6o6JCM67l3YUeHh4bCzs0N4ePhb2RdvOnZTGr/vso631Q9BQUFITEy00K//RagLOBUpol69epAkCeHh4di/fz9ev37NNIPBgCVLlsDW1hZBQUFYuHAh4uPjFe8L47FZs2aQJAk9evRAUlKS4pnGjRuDiODm5pZiGUQErVaLMmXKWPA4depUBAQEwM3N7a3r8PX1BREhICDgrdtJRKy05LvZANCrVy9otVo2mpo2bYoFCxZAkiQEBwfj4cOHAIC5c+emWoZOp4OLiwskSeLdqOQQhlivXr0QGBiInTt3wsvLCz4+Pli8eDEbiCnxYDQa0a5du7euQ0y8derUsXhvwoQJ8PX15cnl0KFDVstfvXo1JElC3bp1rZbh5uYGIkLu3Lnfug7RVgCYNGkSXFxccOHCBQQGBkKSJG5HRESE4p3jx4/DycmJ5WnYsGFISEhgerZs2SBJEsLCwiBJEooVK4a9e/cqZErI57Zt2yBJEmrVqmXBm5+fH4gIDg4OmDBhwhu/369fP0iSBCJCzpw5rfJw8eLFDMlDet+fOnVqimPzhx9+gCRJKFKkSIbqyCiP3333XYrycvPmTdaFTZs2xYYNGyBJ5h3fu3fvAgDmz5/PBqKQK4GlS5dCkiQULlw4Q3UYDIYU+Vy3bh3c3Nzg5+eHefPmWeioZs2asY5KrXzRjpR0kEajARGhTJkyCvqVK1fg6uoKIoKTkxO6d++uGBfi+65ZswYGgwEmk8mq/pg6dSqCgoLg7u6OBQsWKMqIiopC8eLFFbpW3tfp4YGI4OXlBXd3d8yfP99q+ZIkoVGjRlb74V3VkVIbRD/rdDp4e3u/NQ9CFiVJQvfu3d9q3k1r7s6TJw+ICL6+vhmaN9/n3C6fmzPyfu7cuVPVIT/++ONb67h3YUf16dNHMb7fxL54k7EL/DP/WBu/Yt5p3br1B9MPkiRh3LhxAGChw/5rUBdwKiwgJpT9+/fDyckJ9vb2FjsaiYmJ+OOPP+Dv749z587x35OSkvDq1SsAwKBBg0BEbLicP38eABAXF8dKavDgwSAi/PzzzwDMA85aGWIHBgASEhJYiTx//px3qYgI69evBwC8fv06zTpevHgBAPjqq69ARLC1tX2rdg4YMIB53Lp1qwWPt27dQoECBXhi37hxI4B/FsjfffcdEhMT8eWXX6ZaRokSJdioP3jwILclJiYGR48exY4dO3D27Fm8fPkSjRo1Qv78+fH48WP07t0bkiShevXq8PPzQ9u2bXHlyhULHkR/BgcHo0CBAnj+/Dm3N7115MiRA5IkYf78+TCZTIo2PHjwAM2bNwcRQaPRYMOGDSxv0dHR+Ouvv7BhwwY0bNiQywCA+Ph4RRlhYWEgIri4uODgwYNISEhgRZ7eOnbt2oVHjx4BALp37w4fHx88ffoUU6ZM4YlPkiRMnjxZ0Y6zZ8+ia9euqFixIjQaDerUqYPr168jMTERSUlJaNu2LbRaLfr06QNJkpA9e3YLA038v5j0J0+ezN9byOyqVatARCAiREZG4vr16/y9U3s/KSkJBoMBDx8+RN26dUFEcHR0ZJcmMaEZjUb8/fffLA/CpfJN5GH58uXw8PBAvXr1cPr0aavvv3jxghdQ1sb/hg0boNfr4ePjgwIFCuDZs2dvXEdGeUxISED+/PkRGBiIO3fu8PtCXmbNmoWwsDA0aNAAZ8+eBQA2jlu2bImkpCR89dVXkCQJO3fuBGDWP0ImDx8+DL1ejyxZsiAwMBAxMTFvXEdafM6YMQOZM2fGnDlzWK7lOmr48OEgIpQqVYrLb9++vUX5qemgqKgoeHh48KbA6dOnkZCQgMTERMTExOB///sfsmXLBiJC8eLFmW4ymTB06FBIkoTBgwcz30J/PHnyBL179wYRIVu2bLCzs8P27dtZVuXG6syZM3lciLEteEyLB6GndTodtm/fzmXKy589ezaICJkzZ7baD++ijtTaAJh1fcmSJfkZMW8JPZievhZ9mdF519rcLRZq48aNU8ybcmP5TeyD9zG3izr69evHdWSkHzw8PFLUky9fvsyQjps8eTI8PT2xfv16vHz5kvtJ/Ds9dtTUqVNBRKhWrRquXr0Kk8mUbvvibcYuYH38ig3QrVu3KubNf0s/zJ49G5IkoX79+oiLi8N/HeoCTgXDms+wcANr3749DAYDnjx5gtWrV6N3794oV64cihcvjiFDhuDw4cNISkpi4/Lx48coWrSowlVk4cKFCmP28ePHqFOnDry9vXHq1Cm8evVKsaMiynB1dYUkSejSpQsAy12VO3fu8IlF5cqVFTRrdch3uEQdTk5OvGuZVjut8ShOfITPvmin4FVMqk5OTujUqRMA4NSpU9BoNLCzs8OpU6fY9WDJkiWKNoj7L2Iy0ul06NSpE+Li4rBmzRpERETAz88PkiRBq9WiYsWKqFChAvvAX7hwAWFhYbC1tUXPnj1x9epVJCYmwmAwKHg4efIknjx5gsyZM6NWrVowGAyp1lG+fHmLOoS/+8SJExUKVLRhxowZbJjUqlUL8fHxWLduHWrUqAFPT0+erIkIzZo1U8ik6EuxGE5++pRSHa9evbKoQ5IkFCxYEEuWLEGvXr3g7++PuLg4xMXFoWjRolz+0KFDLXZEnz9/jlOnTqFKlSrQ6/UYM2YM8yZcjseMGcMT6JAhQ2AymfDkyROsXbsWAwYMQLVq1dgNtGfPnrCGSpUq8UJ07Nixab6ffKF46tQpeHl58bgwGo0WZZQsWRIDBw7ErVu3YDQaU5UHo9FoIQ9iB71ixYrYuXOnhUwDYONm4cKFFmMzMjISPj4+8Pb2Rs2aNQEg1Tq2b9/+xjKb1vsmkwmFCxdG8eLFYTKZrMoLESFPnjw8Nu/du8f3Lg8ePMj3ysTOr8CjR4/QrFkzODs7I2fOnChSpAhMJlOKci/qMBgMijoOHDgAk8mEQoUKoXjx4khKSrIoQ8hswYIFefND9HPlypXh6OiIv/76i+Xkzp07ijYAYF1tTQcBQHBwMIgIdnZ2rMcE/fXr12yIOTs7o2PHjkwX7s09evSAwWCAwWDAl19+aaE/tFotGjVqxAvg5CcN8+bN47G9ZMkSBd0aDx06dFDwIBaxr1+/RlJSkmJDRJQvSRK8vLxYn8vp1ur49ttv06wjtTaIu43yfhbzhRi7yfVgWjxIkoQsWbLwRlha8668H1Oau+UQZQivkjZt2gBAmvZB8jqSz+3Jv3fyuV3eD+m1H8TcvGDBgnT1gzUbZsmSJbzpCVjO3dWqVcuwjtPpdKhQoQK2b9/O7UzpW1jrB3d3d2zYsAHx8fEWujy5fZGWTHfs2JGfkY9dANzH1savmFuXLFliMf+nNTat6Yc8efIoyk+uH6yNLUmS0LBhQ673vwx1AfeZw2g0skEvh/j/6OhoeHt7g4iwZs0aDBgwAPb29tBqtax8xanQb7/9BpPJhM2bN6NChQrsaiP+nTt3bhw5cgSvX7/Gb7/9xpdtvb29UapUKeTNmxe//PILnj59is2bN6N8+fKQJAn58+cHEcHf358vqApjXQzQnj17goig1+vx888/cx3CiPb29kbRokUt6hB3DIoWLQp7e3sQEcaPH2/RTjGZOjg44Pfff+d2li9fnt1IJMnsMrdr1y7m0WAwwGg0YsuWLWxghYSEYO/evQDAJzV16tTBN998A51Oh759+/IundzAWLFiBfORNWtWvvNiY2ODZs2a4csvv0RAQAAkyXyHQijXV69eYfr06ZAkCaVLl8bhw4eRmJjI31i4X9SvXx8PHjyAm5sbMmXKhJcvX+LMmTPInDkz9Ho9mjVrhkaNGsHf35/7QtTx7NkzxcKpdu3auH//PrdfTDgHDhzgZ2xtbTF//nwEBQXB1tYWbdu2Rbdu3eDj48MLl6lTp7I8Cn67du3KZdjY2GDDhg0wGo08+cvrsLGxwdy5cy3qyJcvH7RaLezs7FCqVClkz54djx8/RlJSEjZs2KBYAIq/yycDo9GIRYsWwdnZGUWLFuXAJ6tXrwYRoWLFirh+/Tov2jds2IDvvvsODg4O0Gq10Gq1XIezszMHHJEbc9euXePxlT17dnz11VdW33d0dMQff/zB7wNgQ2TSpEksdxMmTEiRh7CwMIVMCnl4+PAh3N3dU5QHucxlz54df/zxB7v51atXDwAwffp0aLVaFC1alMf/+vXreWx6enpCp9NBq9ViwYIFOHjwoIXMCSNHXsfbyKy19589e8anujExMbh16xbLS5s2bVhedDod7O3tsWDBAgBm1ygiQrFixfDHH3+w4b9x40Y8fPgQv/32G58mu7i4wNbWFkSEESNG4ODBg6nK5Lx58yzqiI2NRdasWUFEuHPnDvNpY2ODdu3aoWvXrsiRIweXsXjxYmzevJnvkISHh6Ndu3Zo0aIFNm3aBIPBgPHjx7P+i4+PT1UHxcfHo2bNmgodtG/fPoXMbt68WaHnxF2f7du3Q5LMrsVCn1WpUgXlypVjHSX0h1xHiXKFPK9du5br79mzp4JH8eymTZv4GcGD0WjE9u3bWdaNRiNiY2OZF/Hu77//zjv9Wq0W3333nVVdLNqZUh05c+a0MB5FHfI2tG/fnk9MRflGo5H5ICK4urryqYlcl1rjAQC2bt3Kc55Go0GRIkUU407Muz4+PihbtizCw8MVc2LyuVuSJOTJk8fq3O3h4QEbGxv+Hr1794a9vT10Oh3Pm5IkwdHR0WLelM/t3t7eCh0m/55ibtfpdIq5Pb32gzj18fPzw+LFi1Psh9y5c2PZsmVW+6F79+4YMmQIjh8/zjIg15Nvq+OSz91CR23YsCFDdtSyZcvw7Nkz1nPCvkhNpoWNIuQ/tbGbfPzGxcVhypQp/H7Hjh3x7NkzdocUc7O1sQn8cx0gLCwMBoMBV69eRaVKlazqhzJlyuDBgweKMSXKF67hjRs3VoOYqPhvY8GCBShYsCBCQkKQM2dOfP311zh69CjT586di4IFC/Lurrj/8OWXX+LIkSM4ffo0B4AQBrl4XpLM94fKlCkDBwcHHrTy3U2x4MmWLRsyZcrEBnfp0qXh4+PDZRQoUIDrdnFxwZo1axSKZMGCBShfvjyyZMnCdYjTIkky3z3x8fFhPvR6PYoVK8Z15M2bF5kyZVIYtESERo0a4ciRIxg7dixKlCjBClZcnpXzGBISwu9ptVo0aNAAUVFRzGelSpWg1+t5Qhw7dixMJhPu37+PzJkzg4iQJUsWaLVa5M+fH9u2beP2GY1GzJs3D0FBQdDpdFxPaGgowsLC2KViwYIFKF26NJ/c2Nvbs7vLzZs3kStXLhCZ3UGCgoIQGhqKPXv2KHjIli0b3/EqVaoUihQpwnUsXboUlSpVQlhYGFxcXPibizqmTJkCR0dHlpWCBQuiWrVqePHiBS+wRLQxeRuCgoL4tGbBggXw9/fnZ7RaLX799VcAZmW9YMECrlv8atasyQaewWDgOwPyBY6XlxfXsWTJEhQtWpRlTqPRwNvbG4B5Qlu2bBnLvF6vR/PmzdG4cWOcOXOGv8mvv/6KGjVqwM/PDxqNBo0aNcLLly9x+PBh+Pj4IEeOHAq5ELLXqFEjHDx4EAMGDEChQoXYqA8ODla41sydOxft27fnu0tCrsX7Bw4cYONAfIc+ffoo3PPmzZuHQoUKQa/XK8avKGPo0KEoVKgQb9B4eHjg9OnTCnkoVKgQn+JVrVoVJUuWtJC5MmXKoHLlypAkCYUKFcKNGzfYBa1r166oVKkS07VarcXYDAkJYdc8W1tbhISEcB1CRwUEBMDZ2ZkNcFFHemU2V65cLDc5c+ZUvN+zZ08e215eXggJCYGPjw/Ly8KFC5E7d27Ur18fkmS+gC/cm3LmzMljVwTeEX0p2tiwYUM0btyYv7WIQpdc7nPkyMHt0Ov1LPeijoCAAB5fOXLkQLly5biMBQsWIG/evAo9Z2Njw/eoIiMjUa1aNbi7uzMPVapUwZUrVzgoSfv27ZEnT54UddCCBQt4zAjjfNy4cWwELliwAEFBQXBxcUGDBg0U9Dt37iAkJASenp4oW7Ys/vrrL1SrVg2FChXiOlLTUQJffvkljwdbW1tERETwnCV48PT0hEaj4b7s2rUr85AtWzZ4eXmhdOnSKFeuHMqUKYPvv/+eXZTHjx/PCwqx8GjatCnTRRCFLFmyQKfTsdz279+f6/Dy8oK9vb2FN4aAqEN8o8DAQJ575fOFGK9C5kWAkPTw4O3tDTs7O/7eyefdBg0aoFatWjy2nZ2dUblyZcWcVqpUKcXcrdForI5dcSor11FHjhzBiBEjUKpUKTRv3hySJCEwMBAdO3a0mNvFmNFoNKhatSoOHz6smNtz5crFOiz52ErLfihQoAB8fHwUc7u4Ry76ITIykvtBq9UiODhYYcOUKlWKZcnLy4vtD7meDA8P542SN9Vxop05c+bkeuzt7TmCrOBB6A/Rz2n1Q9++fRETE8M8CvdGazINmD0l9Ho9z30VK1bEtWvXFGO3XLly2L9/P5KSklC1alUULlyYv9WECROYR51Oh1KlSqFmzZo8/6c1NkUdYWFhCAkJgYODAzw9PXl837x5E5UqVYIkSRg/fjyOHDmCFi1a8FUYMbYkScLo0aPxOUBdwH2GePz4MUd+Cg0NReXKlfnOj6enJ2bOnKmgV6pUiXfZnJ2dsXjxYvz9998c2cnOzo5Pr8QzAwYMUNDFgCUyn0i4uroiV65cTE9utGfOnBlr1qxh1yRB0+v1KFKkCEcvEnSNRoN69erByckJfn5+KFasGHx9fdlwFO0USlS0VUSOE+0Uk6ZGo0GnTp0UbRC7+KINOXLkwKJFi/gZ+YRLRChatChWrFiBsmXL8t9q1KgBW1tbNG/eHEajEVFRUShcuDDTxSK0bNmyOHDggCLst5hA5HUNHjwYUVFRfOleo9GgVatWrEinT5+O+/fv8/cUylW87+joiJkzZ/LFeltbW2TPnl3RjkqVKnH0KycnJ4SEhCgWSF9++SVKly7N/SR/l4jQokUL3Lx5k3cQRf+K8Mdt2rTB06dP0aZNGz7JEQa1+A0fPpzvGYkTBjl9yJAhePToEapXr87tkPNIROjTpw/z6e7ujnz58rFhoNPpMG3aNBQrVozrSN6ODh064OTJk3xRWgSmEfRWrVph/PjxvMjWarVsQBERypcvj40bN/JCwMHBATly5GD6qFGjEBUVxQszBwcH/Pjjj1xH2bJlYTAYcOfOHf7e8n6QJPNF+R9//JFPlr28vBAaGsrP2NvbY9KkSSnKdUhICBYsWMCbMuL0SF5H69atcf36dQuZE7vp06dPx8iRIxXfumfPnpgxYwbatm2L0qVLw9vbm3fINRoNT8xCbhs0aGCho+R8Nm7cOEMy++233/LdEiKzS6Cc7urqinnz5nHgAEkyB9UR/fq///0PN2/e5BN4IvPCeO3atejevTsiIyOROXNmzJ8/n9sh2iraWLFiRTx79ozlPmfOnKhZs6ZCV44dOxaVK1dmeSpatKhCHxYvXlzxfp06dZA/f36mZ8qUCfPnz1d8bxEEiIhQoEAB3qUX3yotHSTkUZIkVKtWDdevX+f7NUTmxeW8efNgZ2eHZs2aAQD++usvrlej0eC7775DeHg46tWrxxtZqemoGTNmoEWLFgr9K/7t5uammLPEhowwRG1tbfk0UvBua2vLc44kSahUqRIWLFjAAa30er3CYC5cuDAWLFjA/ZhcZnx8fLBixQp88cUX/E6bNm0UniImkwmPHj3iBbmYU4WuEnOv0JOOjo4K/eLm5oYRI0akysOCBQtQqFAhfkfMF0KmPTw8sGjRIv5eYiHv7+8PFxcXhISEKOZdjUaD4sWLw87ODl5eXsifP7/F2O3duzf8/f2h0Wjg4eGBhQsXKt7/7rvv0LRpU0iShHz58qFQoULYvHkzf6/kG3Jubm78vcX4l8u0TqeDs7NzqvaDt7c38ubNy3Rxgi/orq6u+OmnnxTjQu5pY29vjxEjRijaUbJkSbi6uqJkyZI4duwYrl+/zkFriAjdu3fHzJkz0aZNm3TpuGbNmuHx48esY0JDQ1GsWDGWC41Ggz59+ij6Qdhpoh2p9YOdnR169uzJJ5CSJHHZQqb37t2Lv//+m2VSo9Eo9EOlSpUwffp0i7FrNBqRL18+1K1bV9EGMX7kv3r16qU5Nk+dOsVzZZYsWVC5cmWemz09PdnrYfv27dDr9XB3d+cF7Pjx4/m0W1z5EXEGVBdKFf85rFu3Dnq9HjVq1OCL/Q8ePMBPP/3ECkan06F69epM/+WXX3iH2c7ODoGBgXByckKPHj1w+PBh7N69G1qtliddZ2dnODk5oWfPnjh48CAuX77MIfTFBCneP3DgAF6/fo1Zs2axkRYaGsoh0kXIWDlvP/zwA09C3t7e8PLyQkBAAHLmzAmdTocyZcqgWrVq0Ov1qF69Ok6cOAHAHG5cTJA6nY7bKXbCp02bxjxIkjlstuDx1atXGDlyJLehdOnSCAsL43ZOmTKF70+Jn1CYYqc0ICAA7u7u8PPzw7Fjx7gNwuh3cnLiicfd3V2hTEVksqCgIP7bzJkzrfaDeK9t27ZYuXIl9Ho9cuTIAWdnZw4pLoIkiD4VE1iXLl2QN29ehdFua2uLrl278i7xqlWrePEgXMs6d+6MTp06KYwr8a2FcpZHVxP0qVOnYteuXZyK4fTp0zh69KhFX4p3smfPjlWrVqFevXoKmthFFQafMHzFZCe+Z5cuXXD06FEkJSVhxIgR/IyLiwtcXFzQuXNndgWS9339+vUREhICFxcX9OzZE4cPH8auXbvYlViv18PNzY0Xf8OHD0d0dDTat28PV1dX2NjY8L01T09PODk58eQqDJcvvvhCQS9ZsiR69eoFJycnODo64uXLl+znX6dOHQwePJj7RfCr0Wjg7OyMHj164MiRIzh//jxcXV2ZLycnJzg5OaFXr14cPW7IkCHMhxi7wujp2LGjQuYKFSrEroFC5rJkyYJmzZpBo9Ggbt26fAIj6gsKCsLGjRvx7Nkz3i2Wv+/r66uoQ0zgch0kcjmJNr6JzJpMJmzYsIGfcXNzQ758+VhftW7dGjVq1FDImTAQfHx8+LTj66+/hiRJKF68OJo0aaIwijQaDZYuXYrXr1/jiy++QLVq1fj0wcfHR2FcEZldgbZs2aKQe8B8+irKs7GxUSy6vv76a96kEHJpZ2eH0qVL4/Tp05wTr0uXLtwWb29vha6Ojo5GnTp1uAz5ZomDg4PC+LWmgzw9PdngE30ppwcEBGDp0qXw9fWFr68v6zm5PGTJkgVZs2ZFlSpVYDKZeE5KS0c5OTnBxcUFs2bN4qBO8m9GZHYxu3v3Lv7++29eHIg5S4yBvn374vXr14oNNlGvra0tVq1ahd9++41PBMX3EPKSK1cuXL9+HT/99BMbmsKVWMhDpkyZFC7/Yu4V+n7IkCEoV64c1y1vh4uLC3bt2oVFixYpvC4EH4KHa9euKXgQY1folSlTpmDFihUoVaoUiMzBWZKP3YCAANSuXRtarRZdunThjRk5vXz58py+JfnYDQoKQr9+/XhjS4Syl9OnTJkCvV4Pe3t7PH/+nL939erVOQqwvb0936FOboPcvHkThQoVUmyS2tvbo3v37jw3y+0HW1tbnpeFfTFjxgxeOLi5ufHcLfTktWvXFGNLfM/k/WBjY6OwP0Tfu7u7Y8uWLenWcW3btsWaNWsUttjz5885YIdoh06nQ7Vq1XD69GncunULdevWTbUfZs+ebdEPQpe3adNGIdOOjo48j/v4+GDz5s0cMVLIsVw/CF2+bt065M+fH5UrV+ZvWaVKFXTr1o29BNI7Nh0dHTFo0CB+7vvvv8eLFy+QJ08evlfn6OiItWvXYsSIERyV1NnZmb0UBPLnzw93d3dcvHiR/5b8BPy/BHUB9xlCJLTcv38//00IuXwgzZgxw4Leo0cPpvfo0UOxwyF2o4VC6927t4Iuv5gt6MkHl8iBJfKF1K1bF0ePHsWoUaMU7+bJk0dBHz16NCRJYncOsdsnSRL27dunaMdPP/2kKCt5O4VvO5F5Nyh5ziYRPlmv10OSJEyfPp3befnyZYvTAD8/P8ydO5d59PT0RFBQEL755htuw/Tp0/n5KlWqoFq1arz76uLigvr166NDhw6QJAm5c+dmQ0ScGNWpU0fRD/KTjIIFCyqUqZOTEzJlyoRjx47hf//7H3/PLl268C5ahw4dFDuSAQEBFvnU5PSKFSvyDqNQuHq9XuH6EhgYiIULF2LZsmWKU4Svv/6ad/DWrl3L5a9YscJi1zBfvnzsN//o0SN2C5EbNqNGjYLJZMLMmTP5XdGXnp6eCqW/ceNGRd+0bduWd02JiPtZp9PB0dEROp0Os2bNUsitMPyF8VGqVClotVpFZLMpU6YwLxEREdi3bx/fSRATpFhkfvnllwr6wIEDORDIn3/+iVKlSsHR0RFXr16FwWBg48zW1paN6fDwcEWy7mrVqinG5rhx4yzGnjh5EWNT3Evw9fVFx44deWIW+emSy5wwqMVpV+nSpdmolCQJzZs353ucQu6PHDnC7+fOnZsXoeIdeb4ko9GI+fPnK8bWm8qsfKEqSRI6duwISTK7XI8cOVKRvoGIULBgQZw+fZplNnfu3NDpdAgKCoIkSahRo4biTlPhwoW538TpvzDORBlCprJly2ZV7jdu3MiLGWFctWrVimVy8ODBiuAsRKQIhGMwGLBlyxbFGBOnQQIi2qJYHMi9ACTpH5fLlHRQZGQkG1LW6M2bN0exYsWQNWtWjnYpFs9ifDk5OaF9+/YAkG4dRWR2kzUajTh//jxq1aql2DAqU6YMz2tGoxGlSpXizT8i4gVT//79+d7MokWLFCfZJUuWhNFohMlkwuXLl/lkWeiPDh06KOZOYWSK8sWdLSJC06ZN8fTpU9YDFStWBBEhb968MBqNuHTpkiKQhfiJqIyAOWiTnZ0d6ze9Xo/IyEgFD/JT9l69emHixImQJInbKU6bxYld8nmzWLFiICJ2p01ODw8PV3yf5GO3efPmfN+WiCzeb968OeuDTZs2cc7N/fv3w2g0omnTpvxuwYIFrdogIrqwfG5OPr5btWrF9OT2CQD+/kJXT5s2TfGMsA3EuChXrpzVfhARIZPP3XXr1lXouNq1a6eo4/Lnz2/VFhMbOHK5kveDiJyZ3n6Q63I/Pz/s3r0bRYoUUSwCy5Qpo7CTSpUqxRs/QlfJx27z5s2RNWtWdO7cmdtQqFAh1jn29va8YBd1lC1b1mJsurm5Qa/XK/S2kNmQkBB07twZgwYN4tNGuX7w8PDAsWPHAJgDq1y/fp3nXzF+k+O/diKnLuA+Mzx79gxZsmSBm5sb35eRX4yOiopiQ61Jkya4c+cOjEYjB5AQuy1EhHbt2iE6OprDlufMmRMODg7sHnfw4EGYTCbExcXBYDCgb9++iklABPuIj4/nyEYiHK5Go0GBAgU46uPhw4cVu77FihVTXHY/dOgQChcurDD2XVxcuJ1JSUlISEiAwWDAL7/8olDSTZo0QUxMDOLj45GYmMiTSdasWSFJ5siUt27d4oS92bJlY+XXuXNnAODod0ajkXfQxA6+SKZ57NgxjqAYEhKCdevWoVy5cuzzLxYCIvDGoEGD4Obmhm+//RbPnj3D0aNHUbhwYTg5ObHhFhAQgJIlS+LIkSNITEzE4cOHeeEqV7xEhC+++AJXrlzhOxj16tXD2bNnFd9b7Mw3bdoUZcqUUUx2bdq0wbVr15CQkIA7d+4oDCfxTIMGDXDx4kVMnDgRRMT3D8LCwrBjxw4kJSXh3r17XDaReQexaNGisLGxwblz5zhgyV9//cULAbGjWLx4cVy6dAkGgwHx8fEoUKAAQkNDObCJJEkcEOTMmTN8J0Cn06F58+bQ6XSoXLkyTp06haSkJGzbto358PDw4I2DevXq4e7duxwWXPyGDh3K40HIU82aNRW+/506dYIkmfMviSAu58+f50XJ77//DsAc8CQoKIiDtggDSkQEXLNmDYKDg5E9e3YUKVIEzs7OOHHiBOzt7ZE5c2bcu3cPADBnzhx+v1atWuxuNGTIEA4kU6NGDZ5Mc+TIwfpA3B28du0aT5Bi7AL/5Elq2bIlG59E5oBC+/fv57FZuHBhNvarVq3Kci3u8IlT9Fq1aoHIfDohQu4fOnQIBQsW5JMZ8R1dXV0RExODxMRExMfHw2AwYP369RZjNy2ZvX79OkcsW7p0KbdBBFlp0qQJiMw74t98843ie48YMYIT9YpFMJHZPbpixYq8wGzfvj3TWrduzTvvWbJkwaZNm5CQkICYmBiF3EuS2eVVLvcGg4Hlvnz58ggMDORFhXAhrFmzJjp06KDgs2jRorhw4QLi4uKQlJSE/fv3cz8IN7lLly5xKHqhAzJlygRJkjB37lx069YNROaTBBcXF/Tr1w+FCxeGv78/Fi5ciNevX+P48eMoVKgQnJ2d4e7ujtDQUBQtWhRr1qxheuHCheHi4gJ3d3eULFmS9Vz//v0hSeY7PUJHBQUFcf+nR0eJ/hVzVlJSEpYvX87y0KhRI8TExMBoNOLhw4cIDAxEzpw5ec4Si4gWLVrw2Fy5cqXidK1z586cjgP4516NRqPBqlWr8OrVK373yZMn8PPzYzfIuXPn4ueff+a+Fa64Yu4V901FLi4RHGb37t1o0qQJLySbNGmC6OhoPH78GAEBAcidOzecnJy431q3bo3o6GjmQeg5MXYFD82bNwcAjB07luUhX758PG+KsSt0Q9WqVVG2bFkcOXIEJpMJhw8fRqFChRTzqnxTU9BdXFx489XX1xcHDhwAAJ6zXFxc4OvrCzc3N5w9e5ZtkFu3biExMRF79uzhzadKlSrx927cuDFu3LgBo9GoGGPijlm7du1w48YNHt/du3fnsSUWJElJSRwRUe7G/8033wAA63Jxt5Donw2kuXPnwmQyWdgXPXr0SHHubtu2Lc9rvXv3ZvukUKFCCh3n6uqKzJkzw83NDTdu3EBiYiIH6pEk811C+dx88+ZNiw3mN+kHMc569eqFqKgo3jQoXrw4nj17xvIoZK5QoUJsA7Vs2RKSJKFmzZr8Pd3c3FCgQAHF2GzUqBEuXrzI47dcuXIsW40aNWJ7UozNIkWK8MJc2DQtW7ZEdHQ0/Pz8ULhwYYV7dtasWbF//362L+rVq8d38Ddt2gRJktgmEzh37hz69u3L+ff+S4s4dQH3GaJ8+fKKHFkCycO0Ozs7Y+nSpRb07t27I2/evPDw8OBw0yLyVZkyZXg3cO3atfzOuXPnkDVrVoW/uwjkIXD79m3eIZMkZdjfuLg4/O9//+N3HR0d8f333yvoo0ePBpF5h9LGxgaurq4W7bx9+zbKli0LjUbDixt5O8+dO4fg4GA4Oztj2rRpKFSoEDJlysR00U6xYzR79mzFbo9op7e3N+fy+e2335hHYSQFBwfj2rVruHfvHu8EX7hwgQ0RX19fnpQbNmyI169fIy4uDjNmzGBDQywQW7duze2Li4tT3D8SfSXvh/Pnz/M9iWnTpim+96RJk2BnZwe9Xo8yZcpwXba2top+uH37Nhv8RObTNfkuoLwOSTKnFZBD7NYKpS2CXsydO5fLFwFPli5dqrgHNGbMGABmw5/IHMnt7NmzvBCqX7++gi4mqho1aqBatWrQ6XQYPXo0bt++rTDKicwnV/IT1+fPn7MRLYw7+Vi4desW/Pz8UKBAAfj6+kKr1fKObIkSJRSBSfLnzw+tVou9e/dyFDwRsVGUny1bNsyaNQsAmK7RaGBvb488efLg/v37yJs3LwIDAzmowcqVK7mfQ0ND8e2338LHxwdZs2bF3r17cevWLXh7e7Mb648//ojY2FhF5NmVK1fyySkRYfXq1TCZTLh27RrLQ9myZZlXLy8vnjiFzAna/PnzcffuXV4Y2tra8oJKfG8PDw/s2LGD358xY4bFnUJJkizGrggUIB+7byKzVatW5fJ/+OEHAOZNK/F+9erVFafGkyZN4voXLVrEbRw3bhwePnzIKSbi4+MVAQjEc/nz50dsbCyXsXDhQsX3Tk3uly9frpD74cOH84JLfkck+biQl1G3bl1+Vlzsv3TpEvLkyQN3d3e+R/jbb78hPj5eEfRClF+9enWOlJhcB3Xq1An3799nHZac3rlzZw5ScPbsWUiSOZiFCLefHh01depU1lFyw/vu3buYPn06tyG5Lt+xYweICAMHDuQ5SywSsmTJgsePH/NYlt9FFfdt7t69i5kzZypcNdetW6eYs3bs2AFJ+sfzY926dTh79iwvhLVaLSpXrsx5GIULnQjJLuoQnhRC54t2iPK///57TJ06FatXr7aYk8Qz4t1169bhzJkzkCSJ27l48WKWu//973/Mf/L5YuHChYo5ST7varVa6PV6lCxZkhcF8rErTmR//fVXXuAml4ciRYrg2bNnKFeunOJ7nzt3jmXb2dmZdbbohzt37iBHjhzIlCkTdDodcufOjZw5c1qMb2E/iH6QRyI8d+4cu1ASmV31xPeXvy82j4UNk7wfbGxssGPHDoXcy+fu5J4p8rlbruMkSeK7g6If5Dpq06ZNFraYsC+cnZ2h1WrT1Q/CDpPr8ubNm/P4Lly4MF6/fs39IORp4MCBrH+mTp2qGLvyEzHxE/pHjF9xqi6CqKQ0Nvfs2aPYDBYyK7wCxLyWI0cOeHh4YOnSpawfJEniSNVDhgyBJP2TykE+fiVJ4tP+/xLUBdxnhpcvX6Jdu3bQaDSYMmWKRd6Vly9fom3btrzjKHZNgH/cKA0GA8aOHQu9Xo+GDRti6tSp8PX1hYuLC1auXMknANevX0dcXByWL1+OkJAQaDQa9OvXj3dkSpQogS1btuD27dtYtGgR7/ALRS6iJL169QpLly7lyU9MRJIk4dtvv8Xvv/+O+fPn80Sr0Wg4rD+ROWDB9evXuQ6xoyS/WFu/fn3MmDEDISEh0Gq1mDJlSortdHZ25p0pwaNoZ2hoKLRaLcaNG6foh5cvX2LZsmXw8/ODg4MDLyLkiZUBKE457O3tMWfOHKa/fv2ajVWxUBEnRo0aNcLSpUvx448/KpLAVq9enb/35MmTeZd/6tSpcHZ2xooVKzgIgvje8rtjTk5OPPlKktlNbvjw4YqdTEmSFBHSRNjgyZMns1ugoL9+/Rq//PILsmbNyjmE5MECKlasiOHDh/N3qlu3LqKjozFixAhIktmdslixYhg/fjxCQkKQOXNmnD17Fkajke/Z+Pn5Yfjw4UxfvXo1T9zZs2eHvb09goKCFJFDxcTh7u6Ob7/9lu/IAf+cCovf8OHDsW/fPixZsoQnyh9++AE+Pj5wcnLCvn37EBQUhOLFi+Pu3bscvEDQHz9+zN9z4MCB3EfyABVDhw7Fvn37MGTIEJ7cWrdujQcPHnCwnV9//RXz58+Hh4cHPD09OTgKEXFZ1atX5xD5wsgTboA1a9bE1q1bsXDhQmTKlAne3t7s/ijkGvjH5VqMLdEPQuamTp3KMidJksJANBqNfOomfuI+XsWKFbFp0ybcvn0b48aN4wBDYmwRmYNsLFmyBFOnTmUX3S+//JLvlqVXZkeMGMFjS9xFEzIJALVq1eI6hXEkFvTTpk3DuHHjFEaRfNNEjOGGDRsyXXghhIaGYsmSJTh79izmz5/PEWKFIfgmcl+8eHF2PROGrjDCxCKid+/e3E5Rxi+//MLBFyZMmMBBD4YMGaLQUUajUXEaRvTPKaVcB02ePJl1UN++fdNFF2OgUKFCCAoKwu+//86uibVr105TR4lTDSLzCc3y5ct5kerq6sr3q4Uunz59OgIDAxESEsLeE2PGjOH7uAEBAThx4gSMRiNevXrFvLi5ueHQoUPYunUrn+y6urryfSgxLl6/fo1ly5YhMDAQ2bJlQ+7cuSFJEq5du4YHDx6gcOHCyJw5M8qWLcu6/uXLl3wC17NnT2zatInb4ObmhilTpijm3uLFi8Pf35/bYG3unT59OgICAix4ePToEQoXLoygoCCcOHECZ86csZh3b968iUWLFvG8KUnKRMqvXr3iaJdCpv39/S3G7vjx43nsFi5cmF1GhTzIoxO2bNlSYYOMHz8eixYtQmhoKN+flcufRqNB0aJF+eS0d+/efBcuW7Zs0Ol0qF27Nn788Ueek5LbD3FxcVi2bBlvLIsxY83+kCQJrVq14jug169f534Q9odGo7E6dyefEzUaTYo6TtgZ4vmWLVti8uTJrOOaNWuG27dvW8hDcHAwNBoNBg4c+Mb9kFzPiXnHmkyHhobi0qVLrB9OnDiBQoUKITg4GOvXr8cXX3zBMQ/EptmUKVP4BFCMXycnJ3YlTz42Q0NDcevWLTx79oyDwQjvkhMnTmDu3LmwsbHB7NmzFXLfuHFj3L59m/WDSPBeqlQpeHh4KMavGFvCC+q/BnUB9xliypQpkCQJVapUUewOA//c1xEGgru7O44fP840k8mEe/fu4Y8//kBISAhsbW1hb28Pe3t7TJs2Dffv30e2bNng7++PefPmoUmTJnB2doaNjQ0mTJiAe/fuwcfHh3erJEliQ164nYkohBcuXMAvv/zCZej1erRu3Rpubm7w9/fn3Wuxu6/RaNCwYUO4ubkhMDBQsdsmdsXFbmZiYiKfGgh/bQcHB9jY2GD8+PFISkrC3bt3sXHjRot2jhkzhnk8f/68gkd5O7Nnz47MmTNj+vTpqF+/PpydneHi4oIuXbpwEAXhgrFq1SqFEWpra4vw8HDs378fN27cwNy5c1G/fn04OTnBwcEBDg4OyJcvH8aMGYMqVapwXwpjr06dOlxHz549eddefO8XL15wbjPxvUX+HlFW7969ER8fj9WrVyt2uQV/PXv2RP78+eHi4oIbN25YbAY8evQIBQoUgIuLC/bv348ZM2YgMjISzs7OHKnU19cXTk5OijsW4nv16dMHiYmJMJlMWLJkCRwcHPiiu52dHZydnTF79mwO3DBs2DA23G1sbODs7Mz30Hbt2oXWrVsr6hC/tm3bYvny5RgxYgQHzsmTJw+fMF25cgU+Pj7sKiXKFzI3dOhQXLx4Eb6+vihYsCDu3r3LiwRxD+3KlSvw9fVFvnz5cPfuXaxatQotW7aEvb09bGxs+PSnS5cubGgJA1+SzEEPIiIicPDgQY60FRQUBAcHBzg6OmLWrFl4+vQpBg8erLh3IP+1a9cOGzduxMSJEzk4hLjE7uTkhIkTJyJ79uwIDAzE06dPsWbNGp58JUlCp06dEBoaCk9PT06HIZe5unXrKuR6zZo1bJAQEd+bCgwMRPbs2fnv8lM3Ozs7hIeHK6L5iT6wtbVF3759kZCQ8NYy26tXL5bJGzduWLSxd+/eeP78ObJnz65YHIm6WrZsya5gIsz+ihUruAwi82mSo6Mj7xCLdolyWrVqlW65/+WXXzgaoXzjSrRz0KBBFkEuRBkJCQm4f/8+/vzzTxQuXJijt+p0OowdO5Z1VJYsWbBw4UJFG8S3ypcvX5o6KC260HFPnjzhKLCHDx/G6tWr06WjDAYDhg8fzm0XgV0kSUK3bt0QHx/Pd77ECYODgwNcXV2xcOFCNrRPnTqFggULcn+J8ORizpIkc7CZr7/+Go6Ojlx+TEwMj4vTp09j/vz5rM9dXV0xbdo07senT5/i6dOnvOD44YcfuI0bN27kE+7MmTPzN+3WrRsSEhKQmJjIQYnkQa3EiYLQc5s3b0ZwcDBsbGy4nYIHMXYFD5Ik4ejRo3j48CF8fHwUMi2Xqfr168PNzQ25c+fGH3/8gV9++QWNGze2mHfTM3Y3bdqEa9euYcWKFWjWrBkcHR1hY2MDe3t75MuXD1u2bMGwYcO4v52cnHjeFIa/WOjIF3L/+9//YDAYsHDhQsW9RLmO6NatG8+7ye0HGxsbDBs2LMV+ELbB3bt3kS1bNmTOnFlhw8j7Ia25OyAgIM1+GjNmjOLkV7zbt29fJCYm4v79+9xPQh50Oh33U2r9IOyotHR5lixZ8Pfff1vI9MKFC/Hw4UOW65s3byrG7po1a+Dm5obw8HAeu5UqVVLYky9evEBiYmK6xqa4XiP6SwRLk+dfPX78OIoUKQIPDw8cP36c9YPRaMSdO3fg6ekJPz8/i/ErT+7+X4O6gPuMIHaKHj58yJEAV6xYkSJdKIRRo0YBAJ+szJkzRxHSO1euXLh69SpMJhPf3xCGrrOzM+rVq4crV67AZDLh4MGDkCSzq2WjRo1QqVIlVKpUCZ07d0ZUVBTT8+bNy/7d8jLu37/PF+Xbt2+PiRMnYsyYMRg2bBiuX7+Oe/fucXCQ+vXrMx958uThOsQdpokTJypOFHLnzs18ptbOAwcOpMojAG5HQEAA5z2pW7curl69qmhD165dsW3bNnYZqV27Ng4fPqygt2nTBlqtFo6Ojqhbty4OHz7MbezatSsSEhKwevVqLFy4EDNnzsTNmzdx//59fqZ9+/b8veUBPOTfWx5JLiwszCK5u9Fo5JOY2rVr4+bNmzh//jx0Oh1KlCjBdwzkuHTpEnQ6HUqWLImBAwfC0dGRgx1cvXoVSUlJGD58OCTJHOhBXKBv3LgxLl++zDIHmN0JhSsGkfmURgTxEM/069ePJwAPDw++VyTHrl270KZNGxCZA6JcuHCBF57iv8JNb+jQoQCA9evXQ5LMF7cvXryIoUOHon///hgwYAC7RolEziJserVq1VC8eHE8ePAAJpOJ6fnz5+fTYRsbG0RGRmLbtm2oVasW7OzsMGHCBJw7dw5Dhw5Fv379MGDAAMydOxdVq1aFra0tatasqdiYKFOmDDZt2oT79+9zGwWPwjgoUaIE32lLSEjAqFGjFOGeg4ODsWnTJr5DEBERwTIpvvemTZuwefNmpicmJmL16tVYtGgRZs6cie3bt+PChQsst/Xq1eN2fvHFF9i0aRPOnz/P9KZNm/L4r1ixIpo0aYJt27ZxlEe5zHbq1AnTp0/HzZs3+RtZk9lLly7xPZLkMhsZGYlr167hwoUL0Gq1KFmyJDZv3qxoo7gjIdyhSpYsia1bt2L9+vVYvHgxbt26hfj4eI5cWrt2bRw7doxPciMjI3H27FmmV61aFT169EBkZCQiIyPRvXt3XLlyBUlJSRg6dKhVub9w4QKAfxLUxsbGKqJrBgUF4erVq2yUxMbGcgAKInP0zlu3bvEdsfXr1yuiyIWEhODKlSswGAyso8qWLcsLmMjISKs6ypoOehO6GIdNmjRBeHg47t+//0Y6as+ePTy2RTsuX77M5T58+FARiEToarkLnclkYhd2Hx8fdoET/SA36qtUqYLLly8r5qyIiAhup1yfi/kgIiKC5bNp06YIDw/H2bNn+V5ls2bNFDIbHh7Oek60M3mgDhHMRB5wSGy+yNu5f//+FHm4d+9emvPu/fv3eeyJRaazszPq1q2LK1eu4N69eymO3c6dOyu+d6VKlXhc2dvbo06dOkwnMp82y+/9Fi1alOdN0c6LFy8q3Or69OnD7U9KSsKpU6fQuHFjlu3ixYsjKioq1bn58uXLafYDgFRtmLTm7kOHDqXZT0IeunbtipiYGI5i3K5dO9y8eROA2SV71qxZin6Sy0Na/SCXh+S6/OrVq2nKNADFMwaDgeUp+dht27ZtmvaFtbEpktYD5ijoYuPR1dWV726LdgqIzUthk4pnTp8+rciLJ8bvfx3qAu4zgxD4MWPGQJIkFCxYkIMhCLrRaGQ6kTnykHwQnTp1CgMGDGAXxKpVqzJd3PGoWbMmRo8ejcOHDysGqqCPHz+e63rw4IEFvXfv3hg9ejRGjhzJZQjs2bOHd8jE8bm4iwIAe/fuRdasWZE5c2a++J83b142pkU7//77b0XY4AoVKih2fOTtJCJUq1YNSUlJWLBggQWPwkAGzIbC/PnzIUkS+vTpg8WLF+PIkSPcBpPJhL1793Ibfv75Z3z33XfcluRtnD59OhYvXqyoI60+MJlM2LNnD/dDy5YtQWR2SZP3gzB45RejK1SooFj4iG8rAiOIfhAG/Xfffcd9KjeY5PSbN29i7dq1OHnypILHGzdusJuGiLQXEhLCu+NiMW00GjF27FiWycDAQLx69QoAeKfu5s2bHHRDp9Nh7969XJdcBk+fPg0iszulCEhgMpnYKD548CAby8+fP2c3NhGMwGQyITY2Fi9fvuT2imfmz58PwHzi9vz5cwt637598c0336BXr17YunUrl7F06VJ4enqiSJEi/J3PnDmDSpUqwcXFBT/88AM8PT2RI0cO1KlTh1MsiNQH58+fZ/dVUeesWbNAZD7JO3v2LLfzzz//xDfffMPBNsR9BhEpbfz48bh69Sr69OmDWbNmMQ+CPm7cOO7T8+fPM/38+fMslz4+PqhXrx7mzJmDihUrMl0u9+vWrcPFixdRpkwZBT25zPr7+yvuV1iT2ZCQEEyZMiVFmc2ZMyemTJnC0Rm/++47XL16Fb1798b8+fMxadIkTJs2DQAUzwi5joqK4mdu3LjBCYgnT56MiRMnYvbs2VbpwgX6xIkTijqSyz2ROcKhqFMus+LCvvjeT548Ucj97NmzeVzI6YD5jubPP//MfZUpUyamC107ZMgQTJw4EdOnT8f333/P40r+rYQOWr58OQYMGIBXr169EV3cA963bx/TAVjVUT4+Prhx4wa3QUTVlAdLyJMnD+tqIQ/y6I9yuugrk8nE8lCgQAGmi34QuT3nzZuHNWvW8FiXz1nnz5/HwoUL8dtvv2HdunUAwPOByEdlNBpx8uRJposx4eHhAVdXV96AKViwIB48eMDyajAYcPbsWTRq1MhiTpLLw/nz5zmqpNDFch7lwSgErM274mqEwN69exEQEAAXFxe0bNkSBw8eVMwDyccuYL5vlZzu7e2Nhg0bon///op70YLu5eWFli1bcg5WeT+IzdXkNoi8H0T7DAYDL1SS90Ny+yG1fhB1J38muQ1jrR/E3C2/q2utn+7evWtB9/f3x/r167mdYm4WOHXqFEaOHMn9lJ5+ELaYvJ1Cl2/cuNGqHSZk+tixY4p2imeEvn/27JnCDkyuq0Ub5PZkfHy8xdiUtwEAexuIIDjOzs4Wsin4Sv69BbZt2wZvb2/kyJEDW7duxecCdQH3CSI+Ph4LFizAuHHjMHbsWBw9ehQvX74EAI7OlxJdGKlxcXHw9PTkkwb5+4D5Lpy40B4WFqYwVgHg3r17GD58ONzd3TkZq8Fg4DQDW7duRVxcHF8wNRgMuH79OtN37dqF169fp0p/+vSpBR0wB5YQLjX58+fnierVq1fYs2cPXrx4weHC8+bNy5dtmzdvjtWrVwMAT0yPHz/mXVF3d3ds3LgRDx8+BGBWGvfu3cPAgQNha2uL0NBQ7Nixg3eeduzYgadPn2LcuHHo2LEjevTogeXLl+PJkyccBWrr1q1M79WrF7Zt24a///6beZQk832XoUOHomPHjujWrRsmT56MW7ducdqFlOjDhg1Lkf7kyRM8e/aMQw7nz5+fL1e3atWKL2eLC9aTJ0/mADNZsmThaFFyeRg2bBhsbW2RM2dO/PXXXxzee9GiRYiLi8OPP/6IPn364Pvvv8eyZcs4JUVK9KdPn8JoNHJes4IFC3IC6Xz58mHw4MGIjY1l4+bFixd8D0Gv16NChQpYs2YNy96LFy8UaTACAgI4IIgw5MQzwgWzRo0abKyJ/ujfvz9sbW3h6OiIqlWrckLo8+fPIy4ujvM3lSpVivPgiJxgJ0+eVNAjIiLYDUeUcffuXX6mZMmSqFKlCubPn4+2bdtCo9GgW7duvBsufnPnzkW7du1AZL7TKaI7it+aNWvYwHv9+jWHxxf01atXs8ECANHR0ejUqZPC7UbkoNuxYwdev37N0TTFLy36mjVr8OzZM5ZbcaojfiNGjFCMzZToQmZFzjwis9uRWPwIHfbq1SvFRXo3NzeFsfbq1SvuM0EX/bJkyRLEx8eje/fuHGbexcUFR48eZdfqlJ45cuQIG+0FCxZE+/btme7k5ITDhw9zwBNrdBHhb8GCBSAyR+gU92FsbGwwYsQI3rgQ+k7uCbB69WpevKVEl59MP3nyhL+1JJlTMxiNRta1W7ZsQefOnSFJZletQ4cO4caNG3jx4gW7cNWtW5dPfkQqjRcvXvBJYkr01N4HYCEvog1t2rRhQ0zooL179/L31uv1OH36tCJCclJSklW6kPmkpCQ+fXd0dOSTyO7du0OSzCHlu3btCkkyB8m5ePEibty4wfRdu3bBZDIpntm9ezd69OgBIkqRLvqJyBxKfdSoUcxnyZIlcfr0aRgMBiQkJCAuLg4TJ07k0ycfHx9uh1wXDx06FDY2NggMDMRPP/3EmzF//vkn6/N27dqhWbNmGDlyJL766iumi3uKcvqVK1cUfObLlw8tWrRA8+bN8eOPP2LZsmUWc9aoUaPQrl07NGnSBL169cKpU6d4Xv7iiy9SpUdGRmLatGkceKV27dq8kZuQkID4+HjMmzeP5+agoCCmy/th0qRJHODp6NGjPL7//PNPPHv2jO2g0aNH4/fff2c5F/2UnP7y5Uv+nhs2bFDYUYcOHcL58+ct+kHckR0xYgRmzZqFe/fuKebmtOhjxozhu8dfffUVb24YDAY8ffoUU6ZM4XkvZ86cePXqlWKh9eLFCwtbTC6zadlh1ugAuIytW7cyXYwjoR/k/SDu3A0cOJDzHwpdbTAYuA1hYWGKNoiN3+joaPaIGjp0KF68eAE5xDOinXLdAIAjn35OUBdwnxh++uknPq4WPz8/P87zkxJdhC0GgKVLl/Jxs9i1nT9/PiuOxYsXK+g6nQ4LFizgRV7y9+vUqcNh9PPlywdXV1f2Y5ckcy6aHj16oEyZMggLC0uRXrZsWeTMmTNFevny5dnV4ocffmC/8JYtW2L37t0cUfDo0aMYP368wm9cbuyK3bJly5bBy8tLYeja2NigQoUKePjwIX755RcLur29PRwdHeHs7Izp06czXbgIaDQalChRArlz54aDgwO3QfjZ63Q6uLm54ciRIzh16hTnjJOXIdxKdu3axX70b0N/+PAhRo0axe2Xu4sSmaNcLVmyhBfycnrevHkRHR0NwLzDLJ6RnwSIKGsjRoxgujBENRoNXFxcUqWXLl0aT548wYMHD6z68hOZ3YyePHnCwTrkPIg2r1ixAvPmzbOgy2XXYDBgwYIFimfEPTY7Ozv8+uuvKdIlSYKHhwemT5/OdFdXVzaybG1tOcqjmMQyZcrE7i8ajQaenp4ICAjAzJkzUyyjf//+3E+SZHZtE2W0adMGmzZtUtDl+QZr1qyJpKQkTJs2jUNgi7yMRMQnOyaTSfEMkfmejSSZE6G7urpi6NChTK9YsSJf/hdh0VOif/311wCA/v3783cuXLiwIvjH0aNH06SvXr2a+0UEUBDPi7D9x48f58AVoo8nTZrEO9gnT55U0MPDw3H79m2ULFkSOp0Oq1atYnrJkiVRvnx5BAUF4fDhwxx4Qv5MqVKlUL58eWTJkgVHjhzBgwcPuH8lyZzYWwSLWbZsmQW9WLFiCjpgXpCITTL5nSgi4ui+kydPVnwrSZIwaNAgDhRhjS42PgDzfWdB12g0CA0N5bDq+fLl4/uPkiShYcOG+OGHH9CtWzeUL18eV69exalTpxS5NcuXL89j9ejRo6nShY5LjS7kRege4Y6a/Hu/fv0aiYmJigh/ERERPGelRJdvXCYmJvLVAK1Wi759+3I/uLi44NKlS3yv2sHBAYULF0bZsmV5zoqKikJiYiI/o9frERAQkCo9MDAQV65cwalTp3iBKuYBwaenpydiYmIwZ84cHt9yup+fH6f8+emnnyx0sSSZ3f1cXFwwZswYpsuD5NjY2KRKz5YtG2JjYzF48GDFvCmvZ+fOnTh16hRvsok2CnpwcDCfLqVGl78vL79AgQJ4+fIlfvrpJ55X5fSyZcvy95w2bRrbOYLu6+vLKTBGjBihsIPkfZAavW7duggPD4ednZ3CJU+SzK6lvr6+/D2Dg4MVdMFH7dq1cfLkyRTbKehCb0qS2bVR0IcMGYJXr17h119/ZR5EgCStVouFCxey3FuzxV6+fJmqHZYeO+vs2bPIly8f7O3t4e7uDkmSMHPmTBw7dsxCP8hzEMrtLTF2xfWKKVOmMH3RokX8LUXQojNnzijsi0WLFnE7xSaNiK5ap06dNzee/4NQF3CfCGJiYjhaYMGCBdGvXz+sX78ezZs3Zx9scWneGl3sjAwcOJATUEuSOUKPTqdDQEAAhgwZoqCLwaTVapE5c2YMGTIE48ePZ8NWKJ/ly5fDZDLh0qVLICIOACDqEBOFiFSXEl1Ew8qdO7fCeBb0SpUq4cyZM8yDXGEIA6BAgQJYv349fH19OcCB/L4Pkfn+1Pjx4+Hp6Ql3d3cUKFCA8wCJegsVKgQ3Nzem29jYYNCgQXzfxM3NDZkyZYKzszNq167Nhoc8ua1IUDl48GD07dtXcRelQoUKGD9+PO/EipQHcoO6UKFCfPlZThcXdEuWLMmGrrX38+fPzzzodDoLf3pxmdvHxwf9+vVDtWrVFN89V65c6N27N/R6Pfz9/ZEvXz7o9Xp069aNQ3yLiJr+/v6IiIjgyVvwLRJ6Z8qUSUEXbfjiiy/Qu3dvNhbki1A5H1qtFkFBQRxdUbRXq9UiPDwcer0e3t7eKFq0KHQ6HRo0aMAuV1mzZsWoUaOg1+u5DI1Ggzlz5uC7776Dra0tcuXKBb1ejyxZsiAgIAAODg5Ys2YNu8+6u7tDr9fDz88PkZGRvHgKCQlhI0Wj0SAwMBCDBw9G//79+Q6a6NNChQpBr9cjODgYkZGRPPGJMgS/kiQpAv0Qme9TiQTy4tvJ6c7Ozmw45s6dGyNHjkTlypW5X7t06YJNmzYhR44ckCRzkvts2bLB3t4e3377LfMovkuBAgVQv359i6T0YjEQGhpqQa9WrRry5MkDSZKQOXNm6HQ61j1CRsWGgzW6l5cXoqKi+I6IVqtV0PV6Pfr164dbt27xiWZYWBjLQUBAAMaNG4ebN29yRM6wsDDY2Nhg/fr1uHfvHlxcXFCmTBm+p1KhQgXmqWjRojh+/DhcXFxQpEgRLkM8Q2TOP3njxg3cvHmTo72JHGBizIpNpnr16lnQCxcuzHerBA96vV6RbFqSzO5b8m8lQmG7urrCxsYGjRo1skoXiXEbNWrE3yIkJAQlSpSATqfD4sWLOSiQ+G5hYWEYNWoUBg8ejFy5crGuXbVqFefeFGULHVOwYEEsXbrUKl3Iz9KlSzmaX3J6wYIFsWTJEqb7+/tbyIP8e+/evRsHDhxQ6H2NRoNOnTrh1q1b2LlzZ4p0wOyyOmPGDNjY2ECj0cDR0RF+fn749ddfIUnm4CkiMEOePHm4naIPK1eujD179uDQoUNwcHBgvSvGfpUqVbBr1y4Luo2NDWbNmsUuZpJkPsVNvpnm4+PDC+zevXujevXqimeKFy+O+vXrQ6PRIE+ePMibNy/0ej2GDx/OEfxEKoY8efKgevXqilQbYl7QaDQIDg5W0MWiV7Tb19eX5ws5D02aNOExkbwdQncJmUyJHh4ezt/X2dnZoh+EDgsNDUWHDh04cIagly9fnvOC5cmTBzlz5oROp0P58uV5cS50VO7cuVGhQgWed8W3EmMtODhYQZfLDhFxSpbWrVsr0h916tSJIx5LkqRoh9DJ4t5qSvQaNWpYbCyLn5eXF2rVqqWwgcS3E7bYsGHDLGw1SZKwbNkyXLlyJcN21qZNm7hM8RMu/ETme4wXLlzgKMrJ26DX69G3b18eu1FRUWjUqBG0Wi10Oh0yZ87Mrpm3bt3C8uXL0ahRI9ja2iIyMhI2NjaszwVEGba2tpxL9XOHuoD7BJCQkIDvv/8ekiShVq1aOH36NNNevXqFhQsX8sCpUqUKTp8+zadtIgS/UB5isdO7d28Os1qqVClF2G1hQIoyixYtyoNUhJ6VK59u3boBMCceFhOnJEmKBUvdunWxdu3aVOlbtmzhwS8WU3J6UlISpk6dChcXF+h0OsWdCFdXV6xevRrPnj3jnW9HR0fmNzAwEBEREfy8UHqinXXq1GG3ArkiEruVjRo1wv379znRp/iJfsicOTNWrlyJ6OhoxYmCi4sL7zgHBATg559/RsOGDRXtE3UGBwdjzZo1uHjxIkqVKqXgVZLMUQfXrFmD6OhoxT0J8RP0mJgYRThm0Q9E5rtj8nt/ROZTRaGY69Spg6+++sqin8QiuHHjxrh//z5+/fVXRRki6qLoB5FwWW5Myfvp6tWryJcvn6IPBY9ZsmRBp06dLCZUsVNYvXp1jB8/XrHYk+9y1q5dG+fOncPkyZMVk5vgT+x43r59G1FRUSyHYgEmSRJ69eqFp0+fYtq0aYo2CJkLCAjAihUrcP/+fXZfE0ZBkSJFWL5nz57NeeSEXAsjJyAgAL/88gu7FQkjS17WwIEDFe2Xy3xwcDC6dOmi6CMRKEYYxuJUWsixra0tnJycOFR/x44dObG9+AbOzs4cWCMgIADff/8908XOtOj75HRPT08EBgbCzc2Nx548IJI1uo+PDy80RAJYGxsbxdht0KABy4cwpoShJsZucrrYyBAye+LECRARR34UshEQEICVK1cCMAcwICJ27RIBBsQzz58/x9KlSxU5isS3Eq644t6k/Lskp4t7bRqNho0sa3IvQrdLkjngyfjx4xWnGtbo8vft7Oz4/4XMnz59mtvu5OQEHx8fvo8nZPLo0aP8jJ2dHets0Q4R4CAluqhDGLDJ+0FOF8mM5d+7Xr16/D3d3d3h6urKC44cOXIo9KObmxtcXV35xCM5PW/evPD29uZ+yJ07NwYMGABJklhX+/v7KzZFhJEvp/v7+yN37twse02bNuU+8PPzQ+bMmXkBLOhy3SiPDhscHIx58+YpXHz1ej3fwxNyLVw+5c8EBQUp5Hry5MmK7y3XLytXrsRPP/2k0C/ysbty5UqcPHmS6eKUXXyL4OBgzJw5k+sWYzP59963b5+Cx+T0ixcvMl2eNkaULz+B0ul0cHJy4o2VWrVqcaJs+TNZsmThfrh+/ToHqBF1iDktICAAS5YswXfffcd0BwcHRT8sWbJEYUfZ2NjA29ubE6f7+/uja9euinlKvjgKDg7G8uXLFX0t+kG0c/ny5Vi0aJFizpK3KSIigu08QRceP0TmBZncRrFmq3Xr1u2d2FmdO3fmOtzc3BQ6d+XKlXj8+DHatWun6ANRRs6cOS3Grlyme/ToweMsPDwc3t7e/C0aN26MM2fOYODAgbz5WL16dbRs2RKFCxdWyL0KdQH3SeDgwYMICAhA1apVFb7P4t9bt27lnRRhiMjpa9asURhRwn/42LFjkCTzqYfchU2r1cLf35/9xR0dHdG6dWvFBOTs7Kygz5o1SxESWywWxS6znZ2dYrc+Od3R0dEiX5RGo1HQZ82axW5I8nIE/eeff+adTjldRCzatm2bRdhuLy8vjBo1CgaDAdu2beOdbEG3t7fHqFGj+BJ88rtHRGZ/bUHfunWrwqVA7JoNGTIEJpMJ27ZtU+TVEjyOHj2aeWjRooVigSxJEkaPHq2oQ5yAiZ+cvm3bNpQpU8ain0Q7tm3bpojoqNFo4Ofnh5EjR8JoNGLHjh2K+yjCCBk5ciQMBgOHJZfTJckcDEHcOZHfrxI/OX3Hjh0WYd71ej1/iz///FOxEBZG1YgRI/jS87BhwxTGrKurK9MBcxAVYYgLuQ4ODuZnFi5cqMjdIwygCRMmcBnWwtkPGTJEUYd8IanT6aDVajmf0ubNmxWLLrH5IcrYvHmzxYJap9NhzJgxHCzF3d1dsVOt0WhSpYtF06hRoxAbGwtvb2+FO5ROp+N2mkwmrF27ViFvtra20Ol0GDZsGNeR/GRUo9Eo6CLBrpwPkTw6PXSRHzE5XQQv+fnnn9mYkn9vkY9px44dit19OR0wu46KXXN5Pw0bNozps2fPZgPcWjt37NiBiIgIRV/JE2iLMuRjQ5IkC3rNmjWttlPQ5WNbp9OxzAoe5ONCThcYMmSIQubc3NwU9G3btrHrpvjeWq2W+0LoIHlfirEp6Mn1rJwunpF7Pmg0Ggt68tNdIfeCLtfFwqDWaDSYO3cu5syZo1gI2NjYwMvLi+nnzp1TRHwkIpYvQRcLK3kbiMz3s6zRxaaCnAe58SsS1ievI7nMjRkzhu8GrV271kIW/P39MXz4cKbLx56gjx49GgkJCdi2bRsvKuU8Dhs2jOtIvtkn5F7OQ/JTFL1ezzKzdu1ai6TNQmYFDyJ4VPJvKeoYM2aM1X4QNoo8YIloh5hzhA0jD8Uv7wcxpwlXXTEuhQ6zlmNVPrZT4kHowSFDhjAP8nEj7wfBgzydQHK6KCP5Bk2XLl0UNo7wqBL9IDbfBF2e5zW5rfau7Cx5GgehH+R0a+NftEPYk/KUPuJbCfTu3RteXl78vgiiJcf8+fPZhd3JycmiDBXqAu6TwNWrV9G5c2fs2LEDgPLiJmD2CxYTxZ49exT0Gzdu8D0SIsLAgQNZqZpMJkWC1Dp16mDixInYv38/nj59CpPJxBd6hUFQtGhR/PHHH3wHQ1x0lStXIsKXX36J+/fvW9RhjS7qkE8iderU4V2W5DyIgd+iRQs8fPiQd6UEXRhYderUwcOHD2EwGHDjxg3eoS1dujQrl59//hkmkwmXL1/mU4m6detyHZkyZcKOHTtgNBpx6dIlixxb7u7u2LlzJwwGA65evcp1yBWwCDAhp8tDwc+cORMAFDzId80yZcqEnTt3WpSRXrqtrS3WrVsHk8mEK1euWNBFJEuTyYSbN2/y4k8YSHq9nt1kb926xTmO5L/g4GBs376dZS6522pwcDD+/PNPizrkd+KEe5eoQ5IkxYQor+PWrVsKwyVnzpyK1AMihQERcRLe3Llz8wXtS5cuWZzweXl54cyZMzx2zp49q6C7ubkp6PI6rMmtNbq7uzuHir9w4YIFDy1atOC7h0ajEXv37rUYNyLKl8FgwKFDh9jwrFSpEnbt2oUdO3YgNjYWJpNJ4VYmSebACt26deOxd+3aNT4hFL+mTZvizp07HOVMRO4Tv3r16nGUMKPRqOBBGB/NmjXDw4cP06QDwKFDh5hH4c5VsWJFPHz4EFevXkW3bt0UC3Xx7+nTp3PqEtEGuQ5p3rw5rly5gqioKHTr1s3iWzRp0gRXr17F1atX0b17dwsdRkSYMmWKRR1yw6Vp06Zch7UymjZtmmodWbNmxZEjR5huLYefCNSzZcsWNlJFXitJMgchMZlMOHXqFAYOHMinuELHiYACJ0+eVNDFr3Llyrh//z5OnTqFQYMGsR6VLxz2798PANi4cSMbsfL+FPeKk/OQXnq1atXw8OFDnDx5EoMGDVLcgRw1ahQ2b96M48ePQ6PRYNy4cQodVqRIEWzbto3p8+fPx40bN3jzxsPDAytXrmT6vHnzFHTx++KLL1KlN2nSBPv377fKgxgXBw8eZB6uXbumKCMyMhJ37tzhADPXrl3jeSAsLAxHjx7FgQMHeOxev36dDemcOXNi7969OHLkCI+r69evKxYtQt6io6N5wXD58mXF2KlXrx6io6PZQycqKop5EIu0Zs2a8V1SOQ+C3qJFC9YP1niQ08Uz8oVPw4YNWYeJdsgXurNmzcLBgwfZBrl+/ToHwCAyJ0E/cuQIHj9+zPTk36Jy5coKHqKiohS6tmzZsgodduPGDYvFtvykJzkPop2iHXIexLirWrWqIiLjtWvXFGWULVuWdaAIeibGXpkyZXDgwAEcOXIET58+VdhZ4iQrua2WUTsrOQ9ymbp//z7bWYIHYaM0bdoUDx8+tOChcePGOHr0KLdByJywxYQnzNatW9meFM8IJCQk4Nq1a1yGCiXUBdxHBJE4WH7KJhAbG2uVLv5ds2ZNSJKEzZs3K/5uNBoxcuRIjqS3efNmGAwGPkG4c+cOuxpt3LgRr1+/5gvfgi5cnMaPHw/gnwSNBoNBQY+MjESJEiWwa9cuRR3R0dE8IYuJSF5HdHQ0uwFkyZIFe/fu5WiaJpPJgofjx4/jxIkTFnQiwvjx49kdY8yYMYrF7MiRI7Fv3z68fPmSd9oLFizIfAwfPhz79u0DAL40TGQOkiJC9NeoUQN58uRBpUqVFHQR+njYsGHo1q2bYgerRYsWXMewYcOwb98+HD16lBW+nAdBP3fuHKpWrZpiHUuXLlWcHgm6KGPu3Lnw9fVlZWyNLoxJSZIU9JkzZ+Lw4cOKhVqVKlX45Ounn37CkCFDUKZMGV7kSZI5H4zYHGjdujUHSbBWx/Tp03H48GG+vyGvAzBf1j906BDu3bunmPxbtWqFCxcuYNy4cfx34d6RLVs2HDhwAJcvX8a4ceNYrgsUKMBtbdWqFV68eIFLly6hdOnSXIagh4aG4uTJk7h48SLGjRvHk74wrENDQ3HixAlcvnwZP/zwg8LlMTkPcrpIZCvn4erVqyhdujRfpg8NDUXevHlRv359XLx4kSOuiWiN3377LR48eIDnz58rJrpcuXJBksxR9O7cuYM7d+4oUiwIerdu3XDkyBEL+siRI/nE9dtvv8Xdu3f5grnQI2J8pcRDWFgY17Fr1643pguXTeHy2aFDB7x69Qp9+vRhN6FatWop3KBDQ0MRHx/Pz4SEhKBt27a8A63VatGoUSM2PNzc3ODt7c3jTqvVon79+pyI1t/fHwUKFFB4K1ir48svv2RDTNQhyvDz80PlypVTpdeuXVvBY3IevvjiC0U7PT09sW3bNhQrVoxdEuXGqKurK7Zs2cJ0kTBXTt+8ebOCLj/1cXFxwW+//cZ0cXdYvlAtXbo0du/ezc84OTlZ1JGcB/kCzxoP8vet8SC/31OqVCls374dWq0Wq1evxogRI7BmzRp2lx84cCD27t0LrVbLXig9evRAwYIFQUQYNGgQdu/ezXQRhVN4IrRp0wabN2+GRqNJkX737l1FHUOHDsVXX33FC5vkPIgyhNfIoEGD8OLFC5hMJkRHR2PMmDHczx07dkSLFi0wadIkxMbG4s6dO6nSxbxZpUoVNsgjIiLQokULzJw5EwkJCVyH+NYRERFo2bIlZsyYgfj4eIs6GjdujIiIiBR5SE4X9kGVKlV4M6F27doKHu7cuYPRo0ezLNSuXVvBg9Axw4cP5xPXVatW4e7duxY6Smz6WaMPHz4c3bp14w2gVatWKegiJ6TQx2vXruVvAZgjK44cOZI3u8eMGWNRx4gRI5iH8ePHK+hiXt2/f7+CTzk9KSkJI0aM4BP2tWvX4tmzZ1ZtGMCcoiEmJgavXr1S2EDiXtjt27dx9+5djtaY3M7atGkToqOjFXS5nbVp0ybcvXvXKg/CltuwYUOKPJw7dw4nTpxQpA9Kbk8C5kVY8oXZyJEjeVMIgCJqror0Q13AfQR48OABvvnmG75LlhrdmpA/evQIWbNmhU6nw1dffcV/F8aXnN66dWtFLjQAfM/H29sbXbp04V0rsQM/bdo0SJL5Av7XX3+teMZoNGLq1KkKeteuXRUhYkXku5TqMBgMCnrXrl05R4+ch7TokmQOIlCiRAloNBp8+eWXHA5ftPnBgwf4+uuvUaBAATbcV61aBcC8+yPoIhKdk5MTAgICsGPHjlTp4gQMAO7fv8+TkZyeUR5EGQ8ePEDbtm15kajRaODu7o4VK1YwvXnz5rxgkNPFAmvHjh1sHGk0Gtja2mLw4MGczPTYsWN810EYmj169MCtW7eQkJCABw8eoE6dOmyAaTQaeHt7448//shwHbdv3+Y6GjZsqDAkhXEnSeaoiV26dFGcVorFEpH5RK1bt27IkSOHYtfRwcGBg3V4eHggPDxccTKi1WphZ2eXLvq74CFTpkwcCU8YtTlz5sTSpUsBAKNGjWIjOCwsDAUKFOAdfgD48ccfQWS+65MtWzbkypULBQoUwJw5c3D79m0eO8npc+fO5RPJlOoQ3yotHjJKFxHKROoABwcHBAQEwM7ODh06dABgDuTUtm1bhWtcwYIF4ejoiD59+jA9+UmZjY0N+vTpg8TERDx48ICDi8hdSr/55pt3UkdMTAzatWv3VjyIdkZHRyvGt5A5V1dXzJgxAzt27ECdOnXYmBLjy8XFhel169a1Sp8+fTpOnjyJHTt2oGLFihbu4iNHjsS5c+ewdetWVK5cWbHIsrGxeSc8zJgxI108bN++HZUrV+axJDZbRDqJ6OhodouztbVl47tWrVpp0kXexzlz5kCSJHbzIjKf6N26dQsGgwGzZs1ietasWdkzQ4RVf1MecuTIgerVq3MEw0yZMqFVq1Z8N1csZoUbvoeHB1q0aKGgZ8qUCZ06dUL58uV5YyJnzpysF4nMaWPEXVw3NzeEhYWlSP9QPJQqVQp//vknz5nCRsmSJYtCR92+fVthwySn37p1CwBw7tw5vrsYGhrKOkbkFJTTQ0JCmH7z5k2YTKYM8SDy4aXEg0jQbY2HefPm4ebNm6ynCxcujLZt2yIkJERRx/Dhw9nGsUa/desWl+Hl5aWgi1Pl1Og3b97E1KlT08VD8eLFeeEmt/WEPSlJEkqUKKHIWSe3SeU2anKPMhXph7qA+8BYvXo1B2cIDw9nFzEh8GnRAWD27Nk8AebJkwfbtm1TPJMW/ddff+UdMn9/f2zfvl2x6ElISOAoi4IPkaNHTISp0dNTR0bpch6IzG5WHh4e7BqYvC+Dg4PZKBfGm5wuEpIKI3vGjBlp0pOXodfrebd7+vTp74SHzZs3IyAgADY2NsiRI4fiHkj27NmxcuVKpgcHByt2wbNnz45Hjx7h999/55M5cdFZfLsKFSpg2bJlTE8esrpChQr49ddfERAQwAsv+W5+vnz53nkdkiTxxX1hzDZo0ABr1qzhtoaEhChcP7y8vLBo0SKmh4eHK9yZdDodSpcuDX9/f9jY2KBw4cIc+EPwWaJECWTOnDlFemRk5DvnoWTJkrzQ8/HxwcKFC9lwzZQpEypWrMiLxwoVKuDJkyfsGqPRaFC4cGGLZ0SC1JToK1euTLWO901P3gZxuiP6ulixYli5ciV/i9y5cytcDXU6HaZMmcL9nDdvXkU4eSJzyozNmzdzGSKqm3yRltE6hg8f/k55KFiwoMIVuXDhworxXbhwYYXrWN68edOkP3nyBJs2beJn5G5j8u8l6GFhYYqNhxIlSnwQHuQLyQIFCrB+sLGxsXB1JCLMmTMnVfqlS5dYl4pvIK+jVKlSzIOgy3Vt3rx5sWLFinTzIBbjcj3Xvn17hf7IkyePwtsgICAACxcuTJEuIvbJ9Ufx4sUVEQ9r1aql0GHJ6R07dvygPAQHByMmJgZLly6FJJmDYRQsWNBCR/z888+p0leuXMnu2U5OTihXrtwb0Z88efLBebh3757iZD65ri5fvjx7SlijV6hQAcuXL2c5DgwMfGN6engoUKAAJEniTYzki7KEhAReuKf0THIbVsXbQV3AfSC8fv0aCxYs4J243LlzQ5IktGrVCs+fP8fr168xf/78FOmAOcLkggULeJIX+TpatWqFZ8+ecRmp0efNm8d1CKXcqFEjPH/+HAkJCXj58iUWLFjAR+vCnaJVq1Z49OgRXr16hfnz56dIT6uOp0+fZoieEo/iucaNG+PSpUtW+1Lspoq0Asnp8kAeRYoUSZVer149i+8pn8zr16+fYR4aNGiAyMhI2NraYurUqYiNjeUw9+InJtKpU6fiyZMn+P777xWLCk9PT97RrlatGnr16qVw8xNGNBGhevXqePLkicXlb7n7lkjMLTd233Udffv2BQD07NmT/1apUiVUrVqV++L58+fYs2ePwv1N3heCHhQUxIaYp6cnbG1tMWXKFB5TCxYsYLqbm1uq9KJFi743HsTC3svLiydcLy8vPH36FGfOnEHVqlUhSeb8QOJSu7OzM+8Ei2eISBFe2xrd398/xTreNz15G5ycnNCjRw+WF2EY+/v7cz8/ffrUIlG5vJ+fPn2KIUOGKO5wlS1bFlWqVIGtrS1+/PFH9O/fX+FC/C7q8PDweKc8PH/+HGfOnIGHhwcvogT9xx9/ZHmRB15Ji96wYUMLmRUuXeL0KDkPIrqf0K8fkofkdfTu3RtVqlSxCEefGl3IXNWqVWFjY8MuxoIuQsALuRV0caqSUR7EPGytH9atW8eR+6zpj99//x1ubm58Qmytr6dMmcLjzdfXN1V6nTp1PggP06ZNU/AgXOlLly7Nz5w+fZp1iFgAp0SX65g2bdqkquOS08W3+Bh4EC6/bm5uuHr1KgDgxIkTXIbQhynR5XVkzpwZ9+7dw5kzZ/gueWr09PAgSeb8pETmBaC44yePqwAAmzZtgiRJVp9R8e6gLuA+EKKiolgJTpgwAadOnULhwoXh7u6OJUuWpEmXlyGMhEGDBlktIy26m5sbxo8fz/dPbG1tLepwc3PjSJUiaXHyMtKiT5gwwaKOtHh4Ux5FaHqRj83Ozg4jR46Eq6srl3H8+HEULlxYkXfL3t6eeRR0Z2dnnsQkyZyDKSW6n58f1zFu3DicPHmSQ+cmp78tD8IdqXbt2uya+ueff/ICXSxcQ0NDcffuXXanFGGg5b+cOXMiJiYGjx8/xrBhw/jvogxPT0+cOnUKgDlwgXwCIzK7Og0fPhwxMTF48OCBRdj0d1XHiBEjEBMTg6SkJA7xLjeUqlevzq60Dx8+5AlOnMaGhobyhfwnT57wvT5RRmhoKF/ATkpKwpMnTxQnJ2nR3wcPz58/V9wNzJYtGwoUKAAfHx++N3Djxg3O50Nkdnny8fHBoUOH2CXlxo0bHHXV1tYWXl5eOHz4MLu1yOkp1fG+6dbaMH/+fJQrV47HhMh/FxoaysbAn3/+aRERLjQ0lAMTHDhwwCISK5E5sAAAizue4s5aRut4VzyI4AZPnz7l+7biJEvQhQuTGBfye5zW6PJIgtWrV2c52b17N7tFicWLvB8OHjwIIrKgfwgeiIjDw7u7u7NOlEe0E3fOUqILA5WI+O6WfNNl//79vOAUC2dBF3fu0qrDGl14GtSpU0ch93L9ERsby3pDlJFcf1ijJ9cf8sBladE/Fh6ICLNnz4bJZOL7cXIdQkSYOHEigH+SQ8vpYqzOnj0bSUlJSEpKShdd/i0+Bh6EvHTr1k2hy8W4EJtb3bt3V+hy+bgRKaK6dOliwWNKdDkPvr6+VnkQd1TFd+/evbviJO3ly5d82iby9XXv3h1yyJ9RkTFoSMUHQbZs2ahmzZr0zTffUIcOHShnzpzUsmVLevbsGS1ZsoR0Oh3VrFmTvv76a6v0W7duUVBQENWoUYP8/PzIycmJqlWrxs8sWrSINm3aRIGBgeTr62uVvmXLFgoMDKSvv/6avv32Wxo9ejRpNBpKSEigWbNm0a1bt2jt2rXk5ORE9evXp9mzZ5NWq6XHjx8zH/PmzSMnJyeqV69emvQOHTpY1KHT6Sg4OJi8vLyofPnyb0xPzuOcOXNIq9XSixcviIgoPj6epk+fTnq9nnkICwujli1bUlxcHGk0GgJAdnZ2VKlSJerQoQPlypWLWrRoQa9evSJnZ2ciItLpdFSlShX65ptvrNJfv35N+fPn577MkSMHderUib/3y5cvqUCBAtS+ffu35uH58+dEROTr60sajYYSExOpcuXKFBkZSUREGo15OMfExNCOHTsoMTGRvLy8KCIigjJlykR58uQhb29vIiLKnDkz+fv7k7OzM2XNmpWIiMaOHUtt27YlIqJXr17RuXPnKCkpiR4/fkxERBMnTqTWrVsTEREACgkJIS8vL/Ly8qIsWbIQEVGrVq3eSx06nY4uXrxIRES2trbk4eFBRERJSUkEgAwGAzk5OVFCQgIREfMTExND27Zto6SkJHJ3dycARESk1+uZvn37dkpMTCSdTkd2dnZkNBr5u6VGd3FxeS88ODk5kb29PfNw584dSkhIoPv373O/BAYG0oABA8jPz4+//YMHDyghIYG0Wi0ZjUYKCgqiqlWrMo8PHz6k+Ph40ul0FvSU6njfdGttCAkJoXr16lGWLFkoPj6e+1nej5UrVyZPT0+SIyYmhv78809KSEigkiVLUv369cnOzo6IiPvTYDCQyWSiggULUsOGDcnW1paIiJycnDJcx7vkYevWrZSUlESurq5ERCRJEhUpUkQhL0lJSaTVaikqKookSeIxlhK9ePHi3NdJSUn835s3bxIAypMnD1WrVk3RjqSkJLpy5QoREeXNm5e/54figYi4P2NjY+nFixc0ffp0OnbsGGXOnJmIzLo2NbrBYCCdTkdERCaTiX766Sc6cOAABQQEEBHR//73P9ZHRqOR2rVrRwcOHKACBQrQvn37SJIksrGxeWMejh8/Tvny5aMtW7ZQiRIlFP0g9EdcXBwZDAYKDAykSpUqWchkfHy8Vbpcf7x48YI0Go1CnqzRPT09ycfH54Py4OHhwTxotVoqUKAAGQwGsrW1pYSEBDp48CCXQWSeZ5OSksjOzo4SEhLo8OHD5OjoSEREz549Izs7O8qbNy/pdDrSarXk5+dHgYGBqdLLlCnDdXwoHsqVK8d1BAUFkVarpZ9++omuX79OAGjVqlUUHx/P8qvRaGjmzJl048YNC3pMTAyVKlWKNBoNzZ49m65cuUJBQUHk7u5ORETR0dFW6bly5WJdlCVLFqs8aLVaCg4OpkePHpFGo6GffvqJoqKiiIhowoQJ1KpVK56nBw0axGVcvXrV6jMqMoh/d72oAvjnmPnZs2eKC5xXrlxBjRo1oNPp8MMPP6RI1+v1GDt2LABzgBJvb2/kyJEDCQkJuHLlCgfQECdvmTJlSpX+999/806OuIsi3EXECdLvv/8OAHzSkjzHTlr0jRs3WtRBRIqcJSJqUXrpKfEoTntEdELxmzVrFvflpUuXeJdV5A8qVKgQh3i/dOkSatSooXCtKVSoEM6fP6+gyxOgFyhQgMPMG41GXLlyReFyI6e/LQ9E5nsoT5484V2swYMHK+iSZE5+eeXKFQD/BIjo168fJ4719PTEsWPHAAANGjSATqfD6dOnOfCIvIyGDRtCq9WmSAeA+vXrQ6/X4/jx4++tDhFdVKvV8i66vI6ff/6Z2y/y9cnLWLJkCfe12FlPXkfy08rkdHmiVnHi/K55WLRoEd9XE3QRJEXkEzSZTEhMTOS+FmNZ5CwSsiEPJENE6Nq1q1V68jreNz2tNty5cwffffcdJElilx/RTxcvXsSKFSug0WgUYbnldPGtRNtFX3t6euLw4cOKb0VEb13HzJkzLdzz3hUPV65cwYoVK6DValGlShVs3LjR6rgQO91du3a1Shc5un788UerY7NJkyYgMqdlsDb20qK/bx4uXbrE9OS6sGrVqrh06RKMRiM6duyooCWnW8tN6eHhgSNHjsBoNHLyYmvj/8KFC4qx/6Y8XLlyBUajkfVLjhw5WD/I+2H48OEgInTs2NFqX6dFB4ChQ4eyp4Y1upgfv/nmG6vf4t/mQSQr12g0OHDgAABzapGOHTta2BHVqlXDlStXUqQ7ODjg3LlzAMwn4J07d1ZcIZDTDx48iE6dOik8Kj4GHjw9PfHtt99CkiSUKlWK3RvlMinsipTo1atX5+i7JUqUUES0Tk4vWbIkqlSpomhDch6qVKnCdbRs2RI2NjbImTMn8yDcKyXJnNZE2HojRozgMpI/oyLjUBdwHwHEIs1oNGLVqlVwdnZGwYIFWaGK0OrW6MK1pFmzZrh37x5GjBjBLnXOzs4YOXIkJElC06ZNrdJF5EJRx59//qmIuFemTBmcP3+eowXt3LlToWxcXFw4gmJiYqJV+urVq9NVR0bpKfEoXPZy586NkSNHYvfu3RgwYAAnds2aNStH0woLC8PYsWOxc+dO1K9fnw2rokWLpoueK1cujB49WlGHJEnsOpQRHoTbgq2tLXr06IHdu3dj4MCBcHV1RUBAgMLg1+l0GDhwIAYMGABXV1dkz54dly5dwuLFiznhbO/evfmez8CBA2EymbBv3z74+PjAyckJ9vb2rNhTok+YMIHLGDBgAAC88zrGjx/PZVSuXBl2dnZwdXXlpLqdO3fGgAED4O7uzuHHPTw84ODgAAcHB9jZ2SEyMhJubm5wd3dHmzZtOPqlXq9nd1BRhgiBnhpdROR7lzyMGDGCy3Bzc8OYMWO4H0SC3o4dOyrCMi9atAi2traQJAkajYbpgHmjaO/evfDx8eE7d1WqVGHXoOR9ba2O901PrQ23bt3CN998w4aVXq+Hra0tatSoAU9PTzg4OGDmzJkcDVXIfa9evdCjRw94eXnB1tZWYbwQmdM4CLqdnR3LX0bqKFq06HvlYeXKlVbHXvv27dktb+vWranSnz17ZjE2Bb1u3bp4+vSpRR3ibmpK9H+DB5EuJTIykoMJeXp6Mn3hwoWcV83R0RGOjo7IlClTinRXV1cMHTqUeRg5ciTTnZyc4OLigtmzZyt4WLJkCW7fvo1ixYrBxcUFuXLleiMeFi1axOkDihcvjqpVq+Knn35S9EO3bt0gSRKKFi2KS5cuWfRD9erVQUQp0idMmMBlFClSBL/88kuq9MuXL1t8iw/Bg3wDcuDAgRg5ciSyZ88OSTKnFRF0nU4He3t7NGrUiDduq1WrhsWLF7PbularxYwZM7gMInMQJGEPWKNXr15dsfD+UDyMHj2av0X79u3h6urKC56IiAgsXbqU+9LW1lZxfzYiIgLLli1T9LWY+8XCq2TJkvDw8EiRXqtWLYwdOzZVHs6fP4+zZ8+iUqVKsLe3V/BQq1YtXLhwQeFSuXPnTr7OIn9GxbuBuoD7yHDv3j20a9cOkiShR48evLgTgyI5fcyYMZAkCVWrVkW1atUgSRKyZMnCux3iDka1atX4b3K6qOP+/fvo168f570RA9caXZLMIXBFfT169MCdO3fQv39/+Pv7W6W/aR3vgi7nIWvWrAplJHaxatWqBUky59iKiIjg3TSxqy7eeVt69uzZuY6M8tCmTRvFbp+g58qVC3v37sX169ctdgOJzPcU9u7dC8Dsxy4iYYldwapVq/Jl5Rs3bvA9QvETO8iAOR+h3FgVd9aSl/E+6qhWrRpOnDiBb7/9VhERTvxy5syJ33//PUV6jhw5sHPnTsTGxqJNmzYWu/nyMtKiv08e5M+ULFmSZUUYAvIJMjY2VpFI1xpdBAghMu+synPupFXH+6Zba0PRokUVOrFjx44WfRkWFsYReWNjY1ne5L9cuXLxMx07drRI5J2cnpE63icPImrwvXv3ONiHJP1z56pMmTI4ffp0mvT4+HicPn2aoz6KTaXkdHGyLH5lypTB33///cF5OH36NGJjYxUypdFo0LJlS97xv3LlSpp08T3FyWrJkiWt0oWniryMBw8evBUPrVq14nH34MEDxMfH49KlS9wP4l5T0aJFcfToUaYn74eiRYviyJEj/C3EXVlJkjghtygjLfrHwANgPq0TZYtFTnBwMHvUyOniFxwcjPXr1/O3kt+nE+NQlJEW/WPhITY2ltOPiNP4HDlyYNOmTUyX340WdOGZlFzXisBiooy06OnhATCnfBBBUZLzIH+mf//+bC+KvHQq3i3UBdw7QlohUdMTMlU8s2vXLgQFBSEwMJAHhnA5Sk4Xl1GJzBeyf/jhBwBm16WgoCCeICXJnMtGuF7Ky1i1ahX69+8PIrMb06RJk9Kky8sICAhAo0aNIEkSbG1tLejJy7C3t09XHe+CR9EPAQEBmDRpEsaOHYt+/fpxji15GWvWrMGhQ4cwZswY9O/fH0uXLs0w/W15GD16NL7//nuuI0uWLAgMDGSjP3PmzOjevTuXkZSUBKPRiB9++IGNBrHTJ+QnKSkJJ06cUJxO9uvXj+lGoxHHjh2zCHV///59AOCcL71791ZMIkOHDv3X6gCAu3fvYv78+Yrca8OHD2f6/fv3MXfuXEUET71ez3UYjUbcv39f4ZYlSRImTJiQLvq/wYPoh0mTJvGpDhHxKafoa5PJxHJPROjZs6eirw0GAyZPnsw6wlpfizqEi5+o403pyXnMaBtE4nqDwYCBAwcyD/J+jI+Pt+BRp9Nh/vz5XIaoo0OHDlyHyCVmrQ6NRgMbG5t01/Fv8GA0GnHhwgUUL17c6rhIiw6Yc1C1bdtWQRdjEwAuXLjA7mwpyeyH4kEEMxEyNWHCBEjSP6la0ksXMpeUlMQyJ0kSu7yKcZOQkMDjRpy6ZJQH0Y+AOfpgav1w7ty5NL/FuXPnUv0WqdE/Bh7Ed5BfmSAyRzYWdKPRiO7duyvoch2WkJAAo9GIcuXKKZ4ZNGhQuugfAw/ywC7yQCL9+/dXyOTTp08VCyf5txAyN2bMGEUdw4YNSxc9PTwA5qBKcl0t11HimefPn6N///68SBS2mIp3D3UB9x6QVoSdtOjPnj3DoEGDIEkSmjRpgidPngAA7/DJ6SEhIZAkCe3bt+fIZYB5J0XumtCyZUvExsZaLaNJkyaYNWsWBg4cyGWkRZfXIUlmP+tevXop6pDTrZWRFv1d82itL9Oiv8m3sEZ/FzzExsZyHY0bN0ZAQIDFgkKc1BoMBsVO38qVK1nmhIJdu3Yt7/S3bt3aKl3slMrLkLv6yiOufag6jEYju3BYq8NoNPJuoigjOd1aX8l5SKsvPwYeDAZDqjxYk4m0eMgo/V234d/6Vu+7nRnlYcmSJXxSYo2HlOiijGXLlqFTp06cpiM5D4Iuj775MfLwMXyLjPKwcuVKDBgwIMV+EHT5ZldyHn799VfFXe/kPKRE/5h4+PrrrxUbPMl5EPTkMiXnoX379oqFSXIeUqJ/TDwA5usH4uTLmkwuXrzYoi+Ty6Q8krM1mbRGB5AqD3IsXboUAwcORIMGDSBJkqKM5M/ExsZa0FS8O6gLuAziyJEjGD58OCZOnIhZs2bh5cuXTDOZTGnSUyrj77//RrFixeDk5IQBAwYwffDgwbhy5QrTHRwc8O2331rQAeDvv/9GeHg4HB0dUyyjaNGicHJyQv/+/d+YDgBHjx5F0aJF4eDggMjISG7D4cOHERMTg7///htFihSBg4MDateunSLd2dn5rXhID4/Hjx9PF4/Ozs5p9tPb0N+Eh9ToIq2AuI/j5OSE9u3bcx1xcXFITEzEyJEjWUF/+eWXePnyJZ/gvn79GkeOHOGQw/b29ti7d68iqERSUlKKZSQmJn7wOgBzDsSU6gDMu54HDhxAw4YNuQxxOp2eOpKSkpCQkPDBeUirH1Lra8B8unPgwAE0atQoRR4ySk/tW76rNvwbdXwM7UyJB4PBgMTERLx69QpDhgyxqMNgMCApKQkvX75MkQ6Yd9D37t1rwYOgv3z5Ert27fpoefgYvkVGeRDtfPHiBfbv359iP8TFxWHv3r2pfov4+HhFipbk3yIuLi5F+ofmQWxQnj59GmvXrrXgQdDPnz+PVatWWeVB9POFCxfQq1cvq3UkJSXh/PnzKdI/NA/yefPQoUOpzpv79+9PUSYTEhKQlJTEQWZSmleXLVvGHhlTpkzh900mE+Li4mA0GtkzxM3NDUePHlV4gIk71uL6jjUvsbQOKVS8G6gLuLfE/fv30axZM8V9JnEStWzZslTpIn9Zas8sXLiQ3eGI/nGxIjJHMVqwYEGq9CVLliA+Pj7VZ95FHbdu3eL7SvJnJMmcxDEt+sfA48fAw5vyKP/Z2tpi2bJliI6OZnmS08PDw7FkyZIU6e7u7hgxYgQApFrGp1KHcE2Ufwci84JX5eEfHjJKV+VF5UHlQeVB5eHT4EEEgQPAXiHFixfH8ePHYTQaFXThOitJEiIjI3H8+HEAsHhGkiSL+28q/j2oC7i3wP3791GzZk1IkoRGjRph1apV2Lx5Mx+zS5KEggULMn3lypXYvHkzR9vSaDSYM2cOihcvzs+sWLECGzduVJQhQsVqNBoOTiFfJIjEjJIksbuJoIs6xKVXjUbDz8hD32ekDnk7vb29odPpFK4WRJQqXZKkD87jx8DD2/RT8hQJkiRx4vDSpUvDxsaGw7In56FAgQKws7NDjhw5FPUMGTKEZbJUqVKwtbVVBFX4VOoICwuDJJmjg/Xt2xe2traK4BEqD++GrsqLyoPKg8qDysPHz4NGo8Ho0aNx+PBhrF+/Hg4ODuwOWrFiRZw+fRqA2Rvsxx9/hIuLC4oVK4b8+fNDkiTFMwCwfv16uLi4oFKlSopFnYp/F+oC7i2wfPlySJI5NP+LFy8UNPll19KlSzNdHC13794dkvRPWPng4GA8fvxYEeSkRYsWXIYYpJIkcaoAeR1ikUBECrq8DvlAbt68Oe7du/dO6hB/0+v1ivtMzZs3x9dff50m/WPg8WPg4W15rFixIof5Fj8bGxt4enrC3t4effr04bDPckVua2sLOzs7LF68GJs3b+Z7GoJ/e3t7eHp6ws7ODt99951FGR97HYLu7+8Pb29vrkPlQclDRumqvKg8qDyoPKg8fBo8SJKETJkywdvbW8GDJJmDouXOnZtT3tjZ2WHJkiXYvHkzP5M1a1Y0a9YMkZGR8PHx4fQacvtWxb8LdQH3hkhKSuKkpVu3bgUA9j0GzGF1xelI0aJFcfLkSabfu3eP82yIQSWiPiUkJCA6OhrTpk3jfF/ilydPHvbHvnfvHgYMGKBYLGTLlk1BHzt2LF9KFr9ixYph796976yOUaNGKRLYEhGKFCmCv/76K130j4HHj4GHjPL4ww8/KCI+it28v/76C0lJSbh48SInvRa/HDlyMP3evXsYPHiwggedToeyZcvir7/++qTq+N///qf4FjY2NihdujT27dun8iDjIaN0VV5UHlQeVB5UHj4NHpKnHipYsCAOHToEALh48SKKFy+u2JwuUKAA0wHgyZMnaNOmDXsHOTo6omzZsopnVHwYqAu4t4DI27Vs2TLF38XFX2H0Ozo6ol+/fnyhU4QVliehrl27NtMnTZoEIuLEh0TmExdrZYj3U6LLj+jz58//XuqQK46IiIg3pn8MPH4MPLwtjxMmTACRObeXt7c3iMw7cIIHg8FgUYecDgDjxo2zkMmyZct+snXI6a1bt1Z5SIGHjNJVeVF5UHlQeVB5+DR4+OKLLyBJElxcXBQ8iNQnmTJlwpgxY/ium4CwaQFz6pyrV69aPKPiw0FdwL0h4uLi0Lx5c0iShOHDh+PVq1dME1F8ypcvzwMqX758+OuvvwCYE3wOHjwYK1asYLqvry8ncr169SoGDRqEly9f8p0qa2UMGDCAIxURmS+p/tt1DBgwQJHX5E3pHwOPHwMP74rH1GTuc6lD5eHfoavyovKg8qDyoPLwafBgMBiwfv16SJLEJ2yijKtXr2Lw4MGKhZo8BZKKjxvqAi4Z1q5di6+++gqHDx8GoPTtNZlMWLt2LSemzJ8/P44ePcp0EV5VGPQODg6cTFmE3DWZTKhdu7bi5KZfv348gOLj47F27Vr4+vqCiODs7AyNRoOZM2fCYDAgPj4+zTqMRmOG60iNnpCQgDVr1qRKN5lMH5zHj4GH991PCQkJafKQUZn8WOpQeUgfD59CGz6GflJ5UHlQeVB5+NR56NWrF+dtIzIn3xY8iAAjPXr0gL29PSIiIqDT6bgOOdSF26cHdQH3/7h79y5HiZQkCSNHjuTFkslkwt27d9GuXTum+/v7g4hQt25d7Nu3DwBw584d1KpViweS+FWtWhUmkwkxMTGIiIjgvwv/6ICAAOzbtw8xMTFcBxEpXOeqVKkCwBwq9n3WIW/n29A/Bh4/Bh4+Bh7/K3V8DH35KfCgyuTH006VB5UHlQeVh/fJg9FoxM6dO+Hn54e6detyQJOAgADs37+fXSnFMw0aNMC0adMgSeYgbMA/+drUICSfJtQFHIALFy6gdevWkCRztD93d3fkzp0bO3fuTJGeJUsWBAcHQ5Ik+Pr6onTp0vDz8wOR+aKoo6MjB5/QaDQoVqyYgu7q6oqAgAAewB4eHrwoJCJotVr4+/vzoLRWxruuQ7TlbekfA48fAw8fA4//lTo+hr78FHhQZfLjaafKg8qDyoPKw/vkoWLFiqhSpQokSULu3Llx8+ZNrF69mu/L+fj4oFq1avxMnjx5cPPmTTx//hyurq7w8/PDkydPPrDlrSKj+OwXcCaTiSP9VK9eHbt378b3338PSZLQqVMnPHz4MEV6gwYNUKVKFTg6OvJg9Pf3x6RJk9CvXz9IkqSIOGmN7uXlBTs7Oz4Cd3BwQLVq1dC0aVOuQ+QVe591yCMVvQ39Y+DxY+DhY+Dxv1LHx9CXnwIPqkx+PO1UeVB5UHlQeXjfPEiSOTdty5YtsWPHDnZ/LFKkCOd4kyQJ2bNnt3imcuXKCA0NRVxcnHry9onjs1/AAcDq1atRvnx57NmzBwCwa9cuhIeHw8/PDytXrrSg79y5k+nLli1DVFQUJk2ahKJFi2Ljxo0wmUzYtWsXcufOzbsrP/zwA9NFGYKeJUsWTJ48GUWKFMHixYvx9OlTrsPHxwfe3t4ICwtTlPE+6vD390ehQoXemv4x8Pgx8PAx8PhfqeNj6MtPgQdVJj+edqo8qDyoPKg8vE8ejh49igsXLuDp06cAzAcRjx49QrZs2RAeHo5z587hzJkzimcA8DP58+fH69ev385gVvHRQF3AwRxZMiYmRvH/Y8aMgV6vR/369XH+/HnExMTwbkVyelRUFOLi4nD79m1FGR06dAARIXv27Dh//jyio6MVZSSnx8TEsE+yqEPk52jXrt17r6N169YZon8MPH4MPHwMPP5X6vgY+vJT4EGVyY+nnSoPKg8qDyoP/9fevQdFdZ5/AP++yy67yxIugjcugohNvaMSI96iJmaSNgVEq44xRpHMaKptvTSxJr94aad0mkajM05aR01MGlvRUSOioDX1glGpieNUawRR64pOq41XiOBynt8fdE9YwSu3c9bvZ4Zpep6z7/s9u+8m++yePduUGUpLS0XTNJ9P0AoLC0UpJbNnz9a33fkJW337kHlZ8RjweDzYtm0bQkJC0KZNG8THxyMoKAiapsFiscBqteLw4cMICQlBREQEOnXqhJdeegl//etfUVBQgGHDhmHq1KnIzc2Fy+VC+/bt8eyzz2LXrl3Iz8/X61999RVOnTqFsLAwFBcXIy8vD3a7HWVlZdixYwc6duyIkpKSeuu7du3C1KlTsXXrVrhcLrRu3RpWqxU2mw0ejwcdOnSA1Wpt0Bzeuvc44+LiEBQUBKvViurqasTFxfncD3fWExIS0KZNmxbNaIQMRrifmmO9GGFNGuHxNkKG+625x2VNmuGxYgZmYAZmaEgGh8OBmJgYAEBlZSUKCgqQl5cHh8OBsrIybNu2DdOnT9df3+bl5WH27Nno0KEDfvrTn+qvfZVS992HTKylO8imtmbNGomNjRWlaq4eabFY5MUXX5Ti4mLRNO2e9VWrVklYWJgkJiZK27Zt6+yTnZ0toaGhdepKKQkMDBSHwyEvv/yyBAUFSWBg4F3r9c3h3cdms4nD4WjwHHfWLRaLREVFSXh4uNhsNrHZbPesO53OFs9ohAxGuJ+aY70YYU0a4fE2Qob7rbnHZU2a4bFiBmZgBmZoSIYBAwbIwYMHxe12y6ZNm2TChAnSunVrCQoKkgkTJkhYWJikpKRIXl6ebN68WV5++WW9vnz5cqmurtavUul2u2Xz5s0+Y9Teh8zN0tINZFMQEWiahvfffx+TJk1CeHg43nzzTaxcuRIDBgzA9u3bMWrUKEyaNKneen5+PkaNGoWKigp06tQJp06dgohg1qxZPmOsWbMGrVq10utdunSBiAAAqqqqEBMTg1u3bqGiogJVVVWIjIysU+/fv7/PHHeOER0djfbt2zdojtr1lJQUPP3009A0DRcuXEBVVRWGDBmC27dv37U+a9YsdO3atUUzGiGDEe6n5lgvRliTRni8jZDhfmvucVmTZnismIEZmIEZHjVDTEwMkpOTcejQIeTm5mLu3LnIyMhATk4OunfvjsLCQmRnZ+Oll15CUVERfvnLXyI9PR0bNmzQ66+//josFguUUrBYLHj77bcxcuRIrF+/Hj169KizD5mbEu8K8jOXLl3CiBEjcOHCBWzZsgX9+/cHALjdbnz44YeYP38+AgICEBoairy8PPTr1w8Wi0WvL1iwAK1atYLT6cSFCxcQFRWFVatW4fnnn8eZM2fwySefYP78+bBYanrgqKgoLFiwAOXl5WjTpg2Ki4v1OYKCguByuWC32/GDH/wAw4cPx7Fjx7Bo0aI6cyxatAg3btzwGaMhcyxcuNCnHhwcjHnz5uHcuXM4c+YM1qxZc8/6xx9/3OIZjZDBCPdTc6wXI6xJIzzeRshwvzX3uKxJMzxWzMAMzMAMDcmwYMEChISEIDg4GEFBQXjzzTdRXl6O7t27Y/jw4fpr288++ww///nPAQAvvvgixo0bhwEDBsBq/e4bUR6PB1arFSUlJdi+fTuSkpIwZMiQh34dTQZ39w/nzG39+vWilJLJkyeLiOgfKXs999xzAkCio6Prrb/22muC/13KtXv37mKxWGTy5Mly8eJFEan5AcSnnnpKAEhwcHCduqZper19+/YyZ84cPc+///3vZpvj2Wef1eu/+MUvHrpuhIxGyGCEjP4yhxHuSzNk4Jo0znEyAzMwAzM0ZYb09HRRSknHjh3FYrFIZmamfntN0+TgwYMiInL16lWZM2dOvfscOnRIvHiKpP/zu1Mo5X8fKNpsNgCAy+WCx+PRP1LWNA0AkJWVBQAoKyvD6tWroZSCUgpSc2VOLFmyBG3btgUAOBwOJCUlITc3F4WFhfjiiy8wduxYfPnllwCAmzdvIjY2Flu3bsW+ffvwxRdfYMyYMXr94sWLsNls6NOnD3Jzc7Fnzx4cOHAAly5d0nM39hy7d+/GwYMHcfPmTb1utVofuG6EjEbIYISM/jKHEe5LM2TgmjTOcTIDMzADMzRlhgMHDmDs2LHIzc1Fu3btcPbsWXTo0EG//cGDBzFmzBgMGjQIO3fuRGhoKNLT09G7d2/k5uZi7969+j4DBw7Ezp07AYCnSD4OWriBbJCysjI5c+aM/i5GbWvXrhUAMmLECLl48aLPp2sej0fWrl0rSikBIHFxcVJaWioiNe9aeDweERGZNm2aABCn0ym/+tWv9C+5en+4OzY2Vn9XJSIiQux2u3Tu3FmCgoL0eu05Fi5cqH+JNSgoSJRS0qpVqwbP4a3Hx8fLkiVLfObw1h6kboSMRsjQkhmbY70YaU0yA9ek2R4rZmAGZmCGR83gcrlk6dKldTL06NFDsrOzRanvfhTcm0EpJT179pSjR4/qr2M/+OADcTqd0rlzZ3G5XPo+//jHP5r4lTcZhSkbuG+++UamT58uiYmJ0qlTJ3E4HJKVlSU7d+7U65MnT9afUHa7XbKysiQ/P19Eapq048eP6y9MAIjNZpMpU6ZIfn6+aJpWZwyllAQEBAgAiYyMlKVLl/rUvft4/wWwcuXKOnNYLBaxWCz6PsuWLWvQHPXVo6OjxWazCQAJDw+XgQMH3rUeEREhixYtatGMRshghPupOdaLEdakER5vI2S435p7XNakGR4rZmAGZmCGhmRYtWqVlJWV6fukpKSIw+HQM6xcuVJ/fTts2DABIFarVZRS+u1rq66ulrfeekscDocopSQyMrLOPuT/TNfArVmzRsLCwkQpJXFxcTJw4ED9HYrIyEhZvHixXg8ODvZ5IkRGRsq5c+f0Me58wtY3RmBgoM9+TqdT3n33Xb3ufRLWrpeWltY7x93GeJQ5ate9x1n7b9iwYfesT506Vf+ZhJbKaIQMRrifmmO9GGFNGuHxNkKG+625x2VNmuGxYgZmYAZmaEiG0tJS/ayuPn36+NTfeOMNqaqqEpGaM8M2bdokvXr10uszZ87U616bNm2SoUOH6q9Za49BjxfTNHC3b9+W1atXS3h4uCQkJMiKFSvk8uXLIiJy+PBh/aIkgYGBkpCQIH/4wx9k7dq1EhISImFhYfqXUAcNGqSPMX36dAkODhar1SpOp1OSk5PrjNG/f3/9ydSxY8d71r3v6tw5h9VqFaDmXZkePXo0aA7vuzp3Hqf33SDv3/3qRshohAwtmbE51ouR1iQzPNia8/c1aabHihmYgRmY4VEy9OzZU5RSkpmZKSI1DdrgwYP1277zzjv6dq+xY8eKUkrCw8MlNDRU9uzZU+e1sHeftLQ0OX36dDO8+iajMk0Dd+zYMWnfvr3ExMTI/v379e3e77Zt3LhRf2Lk5OSIiIjb7ZaJEyeKUkq/ShAAadeunezfv1/cbre88sor+vbs7Gz9n//85z+LiMiuXbv085+7deum19euXSsiInv37tWbx9p/7dq1k71794rb7Za0tLQmmeMvf/mLeDwecbvdkpqa+tB1I2Q0QgYjZPSXOYxwX5ohA9ekcY6TGZiBGZihsTN88skn+qdkJ0+e1DN06NBBAMjQoUNF0zTRNE1u374tIiIlJSVSUFAgM2bMEKWUfuXJ2tdmKCkpkc8//1yITNPAFRUVSXJysnz88cf6Nm/z5vF4ZPfu3RIcHCwOh0Nv8Kqrq6WgoEDi4uIEqDlv2WKxyFtvvaXfbvv27RISEiIAJDU11WcMj8cj1dXVMnr0aP1JabfbxeFwyN69e0VEpKqqSgoKCuSJJ54QoOZ856aew2azNahuhIxGyGCEjP4yhxHuSzNk4Jo0znEyAzMwAzM0VYYpU6bI4MGDJSQkRM8gInoGpZQsX75cv23t//X+VMGWLVuE6G5M08Ddvn1bCgsL9Xcq7nT58mWJj48XAD5X4blx44YsXrzY5x2TI0eO+NR79uypP2GjoqLqjFH7XRXv351zeMdozjkaWjdCRiNkMEJGf5mDGbgmzXaczMAMzMAMjZ3Be/qlUqreDN7v3X355Zdyp1GjRvl8ckdUH9M0cLXV9wOF169fl+9///sSFRWl/ySAV1VVlYwbN05/Ys+fP1+qq6vl5MmT8vbbbwsASUxMFKDmXZnQ0FA5deqUXveex1x7jHfeeUc0TfMZIyEhoVnnaGjdCBmNkMEIGf1lDmbgmvS3x4oZmIEZmKEhGUpLS+tkeOWVV0QpJUlJSbJs2TKfOZRSkpWVxR/jpnsyZQNXn7Nnz4pSSvr16yfffvutvt37BDh69KgA310eNjo6WiIiIgSAJCcny759+2TUqFEC1LyrEhUVJREREaKUkr59+8rRo0fvO8b69etbfI6G1o2Q0QgZjJDRX+ZgBq5Jsx0nMzADMzBDU2a4cuWKvPrqq2K320UppY+hlJLk5GSf33wjqo8VfuLIkSMAgOTkZDgcDmiaBovFAqUUNE3D6dOnoZTCCy+8gODgYPz3v/+Fx+NBeno6fvazn8FiseD8+fPYuHEjEhISEB8fj6qqKowcORIzZ86EpmnYsmXLPce4X7055vCHjEbIYISM/jIHM3BNmu04mYEZmIEZmjKDxWLB8uXLkZGRgT/96U/45ptvcPv2baSnp2PmzJkt/IqazMD0DZz3iXD06FEAQO/evQEA1dXVsFgsdeoZGRnIysrCtWvX4HA4YLfboWkaAODkyZMAgLlz52L8+PHQNA3BwcH3HcNms7X4HA2tGyGjETIYIaO/zMEMXJNmO05mYAZmYIamzAAAFosFAOByuZCamorU1FRUVFQgICAAdrsdRA/C9A2c94mwd+9eBAQEoGvXrgAAm80GALhw4QIqKyv1epcuXQAAISEhUErh/PnzqKysRKdOnXzGcDqdUEo91BhGmMMfMhohgxEy+ssczMA1abbjZAZmYAZmaIoMtfcREQCAUspnH6IH0tLncDaGS5cuSWhoqHTs2FHfVl5eLjk5OTJw4EB58skn71nv1q2bnDlzpkFjGGEOf8hohAxGyOgvczCD/xyDEe4nZmAGZmAGs2fo1q2bXL9+XYgawi8auL/97W9is9lk6tSpIiKyc+dOyczMFKfTKQEBATJy5Mh71mfMmNHgMYwwhz9kNEIGI2T0lzmYwX+OwQj3EzMwAzMwg9kzzJgxQ4gaytQNnPcKk0uXLhWllEycOFHmz58v0dHRopSS1NRUOXfu3F3raWlp8q9//atBYxhhDn/IaIQMRsjoL3Mwg/8cgxHuJ2ZgBmZgBrNnSEtLk3PnzjX0pS+RiJi8gfMaM2aMKKUkLi5OlFLSvXt3+fzzzx+43hhjGGEOf8hohAxGyOgvczCD/xzD43KczMAMzMAMTZmBqDGYvoGrqqqS0aNHi1JKIiIiZPny5Q9Vb4wxjDCHP2Q0QgYjZPSXOZjBf47hcTlOZmAGZmCGpsxA1FhM38CJiKxbt05+/etfy61btx6p3hhjGGEOf8hohAxGyOgvczCD/xxDc8zBDMzADMzg7xmIGoMS+d91TE1MRO55+dX71RtjDCPM4Q8ZjZDBCBn9ZQ5maJ66v8zBDMzADMzg7xmIGoNfNHBERERERESPA0tLByAiIiIiIqIHwwaOiIiIiIjIJNjAERERERERmQQbOCIiIiIiIpNgA0dERERERGQSbOCIiIiIiIhMgg0cERERERGRSbCBIyIiIiIiMgk2cERERERERCbBBo6IiIiIiMgkrC0dgIiIyKyUUj7/32q1IjQ0FO3bt0ffvn3xox/9CGlpabBa+Z9bIiJqHEpEpKVDEBERmZG3gXv11VcBAJqm4dq1ayguLsbJkychIkhMTMSnn36Kfv36NXi+jz76CJMnT8b8+fOxYMGCBo9HRETmw7cEiYiIGuijjz6qs620tBTz5s1DTk4Ohg0bhv379yMpKanZsxERkX/hd+CIiIiaQKdOnbBu3TpMmTIFFRUVyMzMbOlIRETkB9jAERERNaH33nsPLpcLR44cQWFhoU8tLy8PmZmZ6NKlC0JCQuByudCrVy/85je/QWVlpc++Q4cOxeTJkwEACxcuhFJK/7vzE8ATJ05g0qRJiI2Nhd1uR9u2bTFu3DgcP3683ozbtm3DiBEjEB0dDbvdjqioKAwaNAgLFy5svDuCiIgaBb8DR0RE9Ii834G7339Kf/zjH2PDhg1YtGgR/u///k/f3q5dO3z77bfo3r07YmJicO3aNRQVFeHKlSsYPnw4duzYgYCAAADAb3/7W2zduhX79+9Hr169fE7HzMrKwqBBgwAAmzdvxrhx41BZWYmkpCQkJibC7XajqKgITqcT27dvx5AhQ/TbLl++HNOnT0dAQAAGDhyI6OhoXL58GSdOnMD58+fve2xERNS8+B04IiKiJpaUlIQNGzbgxIkTPtv/+Mc/4vnnn4fT6dS33bhxA+PHj8fWrVvx6aefYuLEiQCAuXPnol27dti/fz/S09PrvYjJ2bNnMWHCBNhsNmzduhXPPfecXsvPz0dqaiomTJiAU6dOITAwEADwu9/9DkopHDx4EMnJyfr+IoI9e/Y05t1ARESNgKdQEhERNbHIyEgAwJUrV3y2p6Wl+TRvAPDEE09gyZIlAIDPPvvsoeZ5//33UV5ejuzsbJ/mDQBeeOEFTJs2DW63G3l5efr2S5cuISwszKd5A2o+XRw6dOhDzU9ERE2Pn8ARERE1Me9piHf+bhwAlJSUYNu2bTh16hTKy8uhaZq+f0lJyUPNs2PHDgBARkZGvfXBgwdj2bJlKCoqwsiRIwEAffv2RWFhIaZMmYJZs2ahW7duDzUnERE1LzZwRERETezy5csAgFatWunbRARz5szBkiVL7vo9sxs3bjzUPGfPngUAREdHP1AeoOY7cOnp6Vi9ejVWr16Ntm3b4plnnkFGRgZGjx6tfwePiIiMgQ0cERFREzty5AgAoGvXrvq2devWYfHixYiNjcWSJUuQkpKC1q1bw2azoaqqCna7/aEvIKJpGoDvflj8bp5++mn9n3v27Il//vOfyM/Px7Zt27B7927k5OQgJycHKSkp2L17t/59OSIianls4IiIiJrQtWvXUFBQAAAYNmyYvn3Tpk0AgA8++AA//OEPfW5z+vTpR5orJiYGpaWleO+99xAREfHAt3M4HEhPT0d6ejoA4Pjx4xg/fjwOHDiAlStX4vXXX3+kPERE1Ph4ERMiIqImNHv2bJSXl+Opp55CSkqKvt17QZOYmJg6t8nJyal3LO8nYR6Pp976iBEjAHzXHD6qbt264Sc/+QkA4NixYw0ai4iIGhcbOCIioiZw+vRpjB07FqtWrYLL5cKqVat86t/73vcAACtWrPA5VXLfvn1499136x0zKioKAHDy5Ml667Nnz4bT6cScOXOwcePGOvXKykps2LAB58+fBwBUVFRg2bJluHr1qs9+mqYhPz8fABAbG/sAR0tERM2FP+RNRET0iLxXlfR+50zTNFy/fh3FxcX4+uuvISLo3Lkz1q5dW+cy/cXFxejTpw/Ky8vRtWtX9OzZE2VlZSgsLMTs2bPx+9//HnFxcfqFSQDg1q1biIuLw3/+8x8888wzSEhIgMViQWZmJgYMGACg5qcHxo8fj4qKCiQmJqJLly5wuVwoKyvDV199hfLychw5cgRJSUm4evUqwsPDYbPZ0LdvX8THx6Oqqgp///vf4Xa7ER8fj8OHDz/U6ZhERNS02MARERE9ojt/FsBqtSIkJARRUVHo27cv0tLSkJqaetcrOX799dd44403cOjQIdy8eRNPPvkkpk2bhtdeew1KqToNHAAcPnwY8+bNQ1FREa5fvw4RwYcffohJkybp+5SWlmLx4sXYuXMn3G43bDYboqKi0Lt3b2RkZCAtLQ2BgYHweDxYsWIFdu3ahaNHj+LixYsIDAxEhw4dMGrUKEyfPt3nyplERNTy2MARERERERGZBL8DR0REREREZBJs4IiIiIiIiEyCDRwREREREZFJsIEjIiIiIiIyCTZwREREREREJsEGjoiIiIiIyCTYwBEREREREZkEGzgiIiIiIiKTYANHRERERERkEmzgiIiIiIiITIINHBERERERkUmwgSMiIiIiIjIJNnBEREREREQmwQaOiIiIiIjIJNjAERERERERmQQbOCIiIiIiIpNgA0dERERERGQS/w/ouQYkb72MZQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "stats = [_stats(data, mask) for _, data, mask in results_ndvi]\n", "\n", @@ -1308,7 +652,7 @@ ], "metadata": { "kernelspec": { - "display_name": "py39", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1322,7 +666,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13 (main, May 24 2022, 21:13:51) \n[Clang 13.1.6 (clang-1316.0.21.2)]" + "version": "3.9.17" }, "vscode": { "interpreter": { diff --git a/docs/src/examples/notebooks/Working_with_STAC_simple.ipynb b/docs/src/examples/notebooks/Working_with_STAC_simple.ipynb index e52aeb858..d43f62cb4 100644 --- a/docs/src/examples/notebooks/Working_with_STAC_simple.ipynb +++ b/docs/src/examples/notebooks/Working_with_STAC_simple.ipynb @@ -47,20 +47,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "%pylab is deprecated, use %matplotlib inline and import the required libraries.\n", - "Populating the interactive namespace from numpy and matplotlib\n" - ] - } - ], + "outputs": [], "source": [ "import httpx\n", "\n", @@ -73,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -83,20 +74,12 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'type': 'Feature', 'stac_version': '1.0.0', 'id': 'S2A_30TVT_20221112_0_L2A', 'properties': {'created': '2022-11-14T06:54:49.284Z', 'platform': 'sentinel-2a', 'constellation': 'sentinel-2', 'instruments': ['msi'], 'eo:cloud_cover': 0.005979, 'proj:epsg': 32630, 'mgrs:utm_zone': 30, 'mgrs:latitude_band': 'T', 'mgrs:grid_square': 'VT', 'grid:code': 'MGRS-30TVT', 'view:sun_azimuth': 169.467826196677, 'view:sun_elevation': 24.259740600657594, 's2:degraded_msi_data_percentage': 0, 's2:nodata_pixel_percentage': 0.000226, 's2:saturated_defective_pixel_percentage': 0, 's2:dark_features_percentage': 0, 's2:cloud_shadow_percentage': 0.002296, 's2:vegetation_percentage': 10.348745, 's2:not_vegetated_percentage': 2.478484, 's2:water_percentage': 87.111628, 's2:unclassified_percentage': 0.002548, 's2:medium_proba_clouds_percentage': 0.003716, 's2:high_proba_clouds_percentage': 0.000508, 's2:thin_cirrus_percentage': 0.001755, 's2:snow_ice_percentage': 0.050325, 's2:product_type': 'S2MSI2A', 's2:processing_baseline': '04.00', 's2:product_uri': 'S2A_MSIL2A_20221112T111321_N0400_R137_T30TVT_20221112T145700.SAFE', 's2:generation_time': '2022-11-12T14:57:00.000000Z', 's2:datatake_id': 'GS2A_20221112T111321_038601_N04.00', 's2:datatake_type': 'INS-NOBS', 's2:datastrip_id': 'S2A_OPER_MSI_L2A_DS_ATOS_20221112T145700_S20221112T111315_N04.00', 's2:granule_id': 'S2A_OPER_MSI_L2A_TL_ATOS_20221112T145700_A038601_T30TVT_N04.00', 's2:reflectance_conversion_factor': 1.0193600036007, 'datetime': '2022-11-12T11:18:11.455000Z', 's2:sequence': '0', 'earthsearch:s3_path': 's3://sentinel-cogs/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A', 'earthsearch:payload_id': 'roda-sentinel2/workflow-sentinel2-to-stac/d5f624f4b32b7ca4b39180d6eceea7fd', 'earthsearch:boa_offset_applied': True, 'processing:software': {'sentinel2-to-stac': '0.1.0'}, 'updated': '2022-11-14T06:54:49.284Z'}, 'geometry': {'type': 'Polygon', 'coordinates': [[[-4.337121116089946, 47.8459059875105], [-2.86954302848021, 47.85361872923358], [-2.8719559380291044, 46.865637260938634], [-4.312398603410253, 46.85818510451771], [-4.337121116089946, 47.8459059875105]]]}, 'links': [{'rel': 'self', 'href': 'https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_30TVT_20221112_0_L2A'}, {'rel': 'canonical', 'href': 's3://sentinel-cogs/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/S2A_30TVT_20221112_0_L2A.json', 'type': 'application/json'}, {'rel': 'license', 'href': 'https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice'}, {'rel': 'derived_from', 'href': 'https://earth-search.aws.element84.com/v1/collections/sentinel-2-l1c/items/S2A_30TVT_20221112_0_L1C', 'type': 'application/geo+json'}, {'rel': 'parent', 'href': 'https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a'}, {'rel': 'collection', 'href': 'https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a'}, {'rel': 'root', 'href': 'https://earth-search.aws.element84.com/v1/'}], 'assets': {'aot': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/AOT.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Aerosol optical thickness (AOT)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.001, 'offset': 0}], 'roles': ['data', 'reflectance']}, 'blue': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B02.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Blue (band 2) - 10m', 'eo:bands': [{'name': 'blue', 'common_name': 'blue', 'description': 'Blue (band 2)', 'center_wavelength': 0.49, 'full_width_half_max': 0.098}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'coastal': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B01.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Coastal aerosol (band 1) - 60m', 'eo:bands': [{'name': 'coastal', 'common_name': 'coastal', 'description': 'Coastal aerosol (band 1)', 'center_wavelength': 0.443, 'full_width_half_max': 0.027}], 'gsd': 60, 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 399960, 0, -60, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 60, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'granule_metadata': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/granule_metadata.xml', 'type': 'application/xml', 'roles': ['metadata']}, 'green': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B03.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Green (band 3) - 10m', 'eo:bands': [{'name': 'green', 'common_name': 'green', 'description': 'Green (band 3)', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B08.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'NIR 1 (band 8) - 10m', 'eo:bands': [{'name': 'nir', 'common_name': 'nir', 'description': 'NIR 1 (band 8)', 'center_wavelength': 0.842, 'full_width_half_max': 0.145}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir08': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B8A.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'NIR 2 (band 8A) - 20m', 'eo:bands': [{'name': 'nir08', 'common_name': 'nir08', 'description': 'NIR 2 (band 8A)', 'center_wavelength': 0.865, 'full_width_half_max': 0.033}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir09': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B09.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'NIR 3 (band 9) - 60m', 'eo:bands': [{'name': 'nir09', 'common_name': 'nir09', 'description': 'NIR 3 (band 9)', 'center_wavelength': 0.945, 'full_width_half_max': 0.026}], 'gsd': 60, 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 399960, 0, -60, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 60, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'red': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B04.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Red (band 4) - 10m', 'eo:bands': [{'name': 'red', 'common_name': 'red', 'description': 'Red (band 4)', 'center_wavelength': 0.665, 'full_width_half_max': 0.038}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge1': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B05.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Red edge 1 (band 5) - 20m', 'eo:bands': [{'name': 'rededge1', 'common_name': 'rededge', 'description': 'Red edge 1 (band 5)', 'center_wavelength': 0.704, 'full_width_half_max': 0.019}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge2': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B06.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Red edge 2 (band 6) - 20m', 'eo:bands': [{'name': 'rededge2', 'common_name': 'rededge', 'description': 'Red edge 2 (band 6)', 'center_wavelength': 0.74, 'full_width_half_max': 0.018}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge3': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B07.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Red edge 3 (band 7) - 20m', 'eo:bands': [{'name': 'rededge3', 'common_name': 'rededge', 'description': 'Red edge 3 (band 7)', 'center_wavelength': 0.783, 'full_width_half_max': 0.028}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'scl': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/SCL.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Scene classification map (SCL)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint8', 'spatial_resolution': 20}], 'roles': ['data', 'reflectance']}, 'swir16': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B11.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'SWIR 1 (band 11) - 20m', 'eo:bands': [{'name': 'swir16', 'common_name': 'swir16', 'description': 'SWIR 1 (band 11)', 'center_wavelength': 1.61, 'full_width_half_max': 0.143}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'swir22': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/B12.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'SWIR 2 (band 12) - 20m', 'eo:bands': [{'name': 'swir22', 'common_name': 'swir22', 'description': 'SWIR 2 (band 12)', 'center_wavelength': 2.19, 'full_width_half_max': 0.242}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'thumbnail': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/thumbnail.jpg', 'type': 'image/jpeg', 'title': 'Thumbnail image', 'roles': ['thumbnail']}, 'tileinfo_metadata': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/tileinfo_metadata.json', 'type': 'application/json', 'roles': ['metadata']}, 'visual': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/TCI.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'True color image', 'eo:bands': [{'name': 'red', 'common_name': 'red', 'description': 'Red (band 4)', 'center_wavelength': 0.665, 'full_width_half_max': 0.038}, {'name': 'green', 'common_name': 'green', 'description': 'Green (band 3)', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}, {'name': 'blue', 'common_name': 'blue', 'description': 'Blue (band 2)', 'center_wavelength': 0.49, 'full_width_half_max': 0.098}], 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'roles': ['visual']}, 'wvp': {'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/T/VT/2022/11/S2A_30TVT_20221112_0_L2A/WVP.tif', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'title': 'Water vapour (WVP)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'unit': 'cm', 'scale': 0.001, 'offset': 0}], 'roles': ['data', 'reflectance']}, 'aot-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/AOT.jp2', 'type': 'image/jp2', 'title': 'Aerosol optical thickness (AOT)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.001, 'offset': 0}], 'roles': ['data', 'reflectance']}, 'blue-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B02.jp2', 'type': 'image/jp2', 'title': 'Blue (band 2) - 10m', 'eo:bands': [{'name': 'blue', 'common_name': 'blue', 'description': 'Blue (band 2)', 'center_wavelength': 0.49, 'full_width_half_max': 0.098}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'coastal-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B01.jp2', 'type': 'image/jp2', 'title': 'Coastal aerosol (band 1) - 60m', 'eo:bands': [{'name': 'coastal', 'common_name': 'coastal', 'description': 'Coastal aerosol (band 1)', 'center_wavelength': 0.443, 'full_width_half_max': 0.027}], 'gsd': 60, 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 399960, 0, -60, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 60, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'green-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B03.jp2', 'type': 'image/jp2', 'title': 'Green (band 3) - 10m', 'eo:bands': [{'name': 'green', 'common_name': 'green', 'description': 'Green (band 3)', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B08.jp2', 'type': 'image/jp2', 'title': 'NIR 1 (band 8) - 10m', 'eo:bands': [{'name': 'nir', 'common_name': 'nir', 'description': 'NIR 1 (band 8)', 'center_wavelength': 0.842, 'full_width_half_max': 0.145}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir08-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B8A.jp2', 'type': 'image/jp2', 'title': 'NIR 2 (band 8A) - 20m', 'eo:bands': [{'name': 'nir08', 'common_name': 'nir08', 'description': 'NIR 2 (band 8A)', 'center_wavelength': 0.865, 'full_width_half_max': 0.033}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'nir09-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B09.jp2', 'type': 'image/jp2', 'title': 'NIR 3 (band 9) - 60m', 'eo:bands': [{'name': 'nir09', 'common_name': 'nir09', 'description': 'NIR 3 (band 9)', 'center_wavelength': 0.945, 'full_width_half_max': 0.026}], 'gsd': 60, 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 399960, 0, -60, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 60, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'red-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B04.jp2', 'type': 'image/jp2', 'title': 'Red (band 4) - 10m', 'eo:bands': [{'name': 'red', 'common_name': 'red', 'description': 'Red (band 4)', 'center_wavelength': 0.665, 'full_width_half_max': 0.038}], 'gsd': 10, 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 10, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge1-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B05.jp2', 'type': 'image/jp2', 'title': 'Red edge 1 (band 5) - 20m', 'eo:bands': [{'name': 'rededge1', 'common_name': 'rededge', 'description': 'Red edge 1 (band 5)', 'center_wavelength': 0.704, 'full_width_half_max': 0.019}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge2-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B06.jp2', 'type': 'image/jp2', 'title': 'Red edge 2 (band 6) - 20m', 'eo:bands': [{'name': 'rededge2', 'common_name': 'rededge', 'description': 'Red edge 2 (band 6)', 'center_wavelength': 0.74, 'full_width_half_max': 0.018}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'rededge3-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B07.jp2', 'type': 'image/jp2', 'title': 'Red edge 3 (band 7) - 20m', 'eo:bands': [{'name': 'rededge3', 'common_name': 'rededge', 'description': 'Red edge 3 (band 7)', 'center_wavelength': 0.783, 'full_width_half_max': 0.028}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'scl-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/SCL.jp2', 'type': 'image/jp2', 'title': 'Scene classification map (SCL)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint8', 'spatial_resolution': 20}], 'roles': ['data', 'reflectance']}, 'swir16-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B11.jp2', 'type': 'image/jp2', 'title': 'SWIR 1 (band 11) - 20m', 'eo:bands': [{'name': 'swir16', 'common_name': 'swir16', 'description': 'SWIR 1 (band 11)', 'center_wavelength': 1.61, 'full_width_half_max': 0.143}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'swir22-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/B12.jp2', 'type': 'image/jp2', 'title': 'SWIR 2 (band 12) - 20m', 'eo:bands': [{'name': 'swir22', 'common_name': 'swir22', 'description': 'SWIR 2 (band 12)', 'center_wavelength': 2.19, 'full_width_half_max': 0.242}], 'gsd': 20, 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'scale': 0.0001, 'offset': -0.1}], 'roles': ['data', 'reflectance']}, 'visual-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/TCI.jp2', 'type': 'image/jp2', 'title': 'True color image', 'eo:bands': [{'name': 'red', 'common_name': 'red', 'description': 'Red (band 4)', 'center_wavelength': 0.665, 'full_width_half_max': 0.038}, {'name': 'green', 'common_name': 'green', 'description': 'Green (band 3)', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}, {'name': 'blue', 'common_name': 'blue', 'description': 'Blue (band 2)', 'center_wavelength': 0.49, 'full_width_half_max': 0.098}], 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 399960, 0, -10, 5300040], 'roles': ['visual']}, 'wvp-jp2': {'href': 's3://sentinel-s2-l2a/tiles/30/T/VT/2022/11/12/0/WVP.jp2', 'type': 'image/jp2', 'title': 'Water vapour (WVP)', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 399960, 0, -20, 5300040], 'raster:bands': [{'nodata': 0, 'data_type': 'uint16', 'bits_per_sample': 15, 'spatial_resolution': 20, 'unit': 'cm', 'scale': 0.001, 'offset': 0}], 'roles': ['data', 'reflectance']}}, 'bbox': [-4.337121116089946, 46.85818510451771, -2.86954302848021, 47.85361872923358], 'stac_extensions': ['https://stac-extensions.github.io/grid/v1.0.0/schema.json', 'https://stac-extensions.github.io/eo/v1.0.0/schema.json', 'https://stac-extensions.github.io/mgrs/v1.0.0/schema.json', 'https://stac-extensions.github.io/projection/v1.0.0/schema.json', 'https://stac-extensions.github.io/processing/v1.1.0/schema.json', 'https://stac-extensions.github.io/view/v1.0.0/schema.json', 'https://stac-extensions.github.io/raster/v1.1.0/schema.json'], 'collection': 'sentinel-2-l2a'}\n" - ] - } - ], + "outputs": [], "source": [ "item = httpx.get(stac_item).json()\n", "print(item)" @@ -104,53 +87,11 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Name: aot | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: blue | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: coastal | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: granule_metadata | Format: application/xml\n", - "Name: green | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: nir | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: nir08 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: nir09 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: red | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: rededge1 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: rededge2 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: rededge3 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: scl | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: swir16 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: swir22 | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: thumbnail | Format: image/jpeg\n", - "Name: tileinfo_metadata | Format: application/json\n", - "Name: visual | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: wvp | Format: image/tiff; application=geotiff; profile=cloud-optimized\n", - "Name: aot-jp2 | Format: image/jp2\n", - "Name: blue-jp2 | Format: image/jp2\n", - "Name: coastal-jp2 | Format: image/jp2\n", - "Name: green-jp2 | Format: image/jp2\n", - "Name: nir-jp2 | Format: image/jp2\n", - "Name: nir08-jp2 | Format: image/jp2\n", - "Name: nir09-jp2 | Format: image/jp2\n", - "Name: red-jp2 | Format: image/jp2\n", - "Name: rededge1-jp2 | Format: image/jp2\n", - "Name: rededge2-jp2 | Format: image/jp2\n", - "Name: rededge3-jp2 | Format: image/jp2\n", - "Name: scl-jp2 | Format: image/jp2\n", - "Name: swir16-jp2 | Format: image/jp2\n", - "Name: swir22-jp2 | Format: image/jp2\n", - "Name: visual-jp2 | Format: image/jp2\n", - "Name: wvp-jp2 | Format: image/jp2\n" - ] - } - ], + "outputs": [], "source": [ "for it, asset in item[\"assets\"].items():\n", " print(\"Name:\", it, \"| Format:\", asset[\"type\"])" @@ -158,104 +99,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "bounds = featureBounds(item)\n", "\n", @@ -272,19 +118,11 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'visual': {'bounds': [-4.337134709547373, 46.85817595750231, -2.869529638083867, 47.85370180403547], 'minzoom': 7, 'maxzoom': 13, 'band_metadata': [['b1', {}], ['b2', {}], ['b3', {}]], 'band_descriptions': [['b1', ''], ['b2', ''], ['b3', '']], 'dtype': 'uint8', 'nodata_type': 'Nodata', 'colorinterp': ['red', 'green', 'blue'], 'height': 10980, 'driver': 'GTiff', 'overviews': [2, 4, 8, 16], 'count': 3, 'width': 10980, 'nodata_value': 0.0}, 'red': {'bounds': [-4.337134709547373, 46.85817595750231, -2.869529638083867, 47.85370180403547], 'minzoom': 7, 'maxzoom': 13, 'band_metadata': [['b1', {}]], 'band_descriptions': [['b1', '']], 'dtype': 'uint16', 'nodata_type': 'Nodata', 'colorinterp': ['gray'], 'height': 10980, 'driver': 'GTiff', 'overviews': [2, 4, 8, 16], 'count': 1, 'width': 10980, 'nodata_value': 0.0}, 'blue': {'bounds': [-4.337134709547373, 46.85817595750231, -2.869529638083867, 47.85370180403547], 'minzoom': 7, 'maxzoom': 13, 'band_metadata': [['b1', {}]], 'band_descriptions': [['b1', '']], 'dtype': 'uint16', 'nodata_type': 'Nodata', 'colorinterp': ['gray'], 'height': 10980, 'driver': 'GTiff', 'overviews': [2, 4, 8, 16], 'count': 1, 'width': 10980, 'nodata_value': 0.0}, 'green': {'bounds': [-4.337134709547373, 46.85817595750231, -2.869529638083867, 47.85370180403547], 'minzoom': 7, 'maxzoom': 13, 'band_metadata': [['b1', {}]], 'band_descriptions': [['b1', '']], 'dtype': 'uint16', 'nodata_type': 'Nodata', 'colorinterp': ['gray'], 'height': 10980, 'driver': 'GTiff', 'overviews': [2, 4, 8, 16], 'count': 1, 'width': 10980, 'nodata_value': 0.0}}\n" - ] - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "r = httpx.get(\n", @@ -307,94 +145,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": stac_item,\n", " \"assets\": \"visual\",\n", @@ -428,95 +184,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = {\n", " \"url\": stac_item,\n", " \"assets\": \"visual\",\n", @@ -544,95 +218,13 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = (\n", " (\"url\", stac_item),\n", " (\"assets\", \"red\"),\n", @@ -669,104 +261,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Apply Expression between assets" + "Use an expression to calculate a band index (NDVI) based on information contained in multiple assets." ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = (\n", " (\"url\", stac_item),\n", " (\"expression\", \"(nir-red)/(nir+red)\"), # NDVI\n", - " # We need to tell rio-tiler that each asset is a Band \n", + " # We need to tell rio-tiler that each asset is a Band\n", " # (so it will select the first band within each asset automatically)\n", " (\"asset_as_band\", True),\n", " (\"rescale\", \"-1,1\"),\n", @@ -792,100 +302,24 @@ "m" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you don't use the `asset_as_band=True` option, you need to append the band to the asset name within the expression. For example, `nir` becomes `nir_b1`." + ] + }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get Tile URL\n", "r = httpx.get(\n", - " f\"{titiler_endpoint}/stac/tilejson.json\",\n", + " f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n", " params = (\n", " (\"url\", stac_item),\n", - " # if you don't use `asset_as_band=True` option you need to pass the band indexes within the expression\n", " (\"expression\", \"(nir_b1-red_b1)/(nir_b1+red_b1)\"), # NDVI\n", " (\"rescale\", \"-1,1\"),\n", " (\"minzoom\", 8),\n", diff --git a/docs/src/examples/notebooks/Working_with_Statistics.ipynb b/docs/src/examples/notebooks/Working_with_Statistics.ipynb index d9f2ac5e4..947d91eec 100644 --- a/docs/src/examples/notebooks/Working_with_Statistics.ipynb +++ b/docs/src/examples/notebooks/Working_with_Statistics.ipynb @@ -2,31 +2,38 @@ "cells": [ { "cell_type": "markdown", - "source": [ - "# Working with Statistics" - ], "metadata": { "collapsed": false - } + }, + "source": [ + "# Working with Statistics" + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ "## Intro\n", "\n", "Titiler allows you to get statistics and summaries of your data without having to load the entire dataset yourself. These statistics can be summaries of entire COG files, STAC items, or individual parts of the file, specified using GeoJSON.\n", "\n", - "Below, we will go over some of the statistical endpoints in Titiler - `/bounds`, `/info`, and `/statistics`.\n", + "Below, we will go over some of the statistical endpoints in Titiler - `/info` and `/statistics`.\n", "\n", "(Note: these examples will be using the `/cog` endpoint, but everything is also available for `/stac` and `/mosaicjson` unless otherwise noted)" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:40.161502Z", + "start_time": "2023-04-06T14:25:40.153667Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "# setup\n", @@ -35,86 +42,37 @@ "\n", "titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n", "cog_url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:40.153667Z", - "end_time": "2023-04-06T14:25:40.161502Z" - } - } + ] }, { "cell_type": "markdown", - "source": [ - "## Bounds\n", - "\n", - "The `/bounds` endpoint returns the bounding box of the image/asset. These bounds are returned in the projection EPSG:4326 (WGS84), in the format `(minx, miny, maxx, maxy)`." - ], "metadata": { "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 10, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'bounds': [57.664053823239804, -20.55473177712791, 57.84021477996238, -20.25261582755764]}\n" - ] - } - ], + }, "source": [ - "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/bounds\",\n", - " params = {\n", - " \"url\": cog_url,\n", - " }\n", - ").json()\n", + "## Info\n", "\n", - "bounds = r[\"bounds\"]\n", - "print(r)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:40.781598Z", - "end_time": "2023-04-06T14:25:40.921234Z" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "For a bit more information, you can get summary statistics from the `/info` endpoint. This includes info such as:\n", - "- Bounds (identical to the `/bounds` endpoint)\n", - "- Min and max zoom\n", + "The `/info` endpoint returns general metadata about the image/asset.\n", + "\n", + "- Bounds\n", + "- CRS \n", "- Band metadata, such as names of the bands and their descriptions\n", "- Number of bands in the image\n", "- Overview levels\n", - "- Image width and height\n", - "\n", - "These are statistics available in the metadata of the image, so should be fast to read.\n" - ], - "metadata": { - "collapsed": false - } + "- Image width and height" + ] }, { "cell_type": "code", - "execution_count": 11, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"bounds\": [57.664053823239804, -20.55473177712791, 57.84021477996238, -20.25261582755764], \"minzoom\": 10, \"maxzoom\": 18, \"band_metadata\": [[\"b1\", {}], [\"b2\", {}], [\"b3\", {}]], \"band_descriptions\": [[\"b1\", \"\"], [\"b2\", \"\"], [\"b3\", \"\"]], \"dtype\": \"uint8\", \"nodata_type\": \"Mask\", \"colorinterp\": [\"red\", \"green\", \"blue\"], \"count\": 3, \"width\": 38628, \"driver\": \"GTiff\", \"overviews\": [2, 4, 8, 16, 32, 64, 128], \"height\": 66247}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:42.410135Z", + "start_time": "2023-04-06T14:25:42.355858Z" + }, + "collapsed": false + }, + "outputs": [], "source": [ "r = httpx.get(\n", " f\"{titiler_endpoint}/cog/info\",\n", @@ -124,17 +82,13 @@ ").json()\n", "\n", "print(json.dumps(r))" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:42.355858Z", - "end_time": "2023-04-06T14:25:42.410135Z" - } - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ "## Statistics\n", "\n", @@ -144,23 +98,19 @@ "- Percentiles\n", "\n", "Statistics are generated both for the image as a whole and for each band individually." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", - "execution_count": 12, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"b1\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 36.94901407469342, \"count\": 574080.0, \"sum\": 21211690.0, \"std\": 48.282133573955264, \"median\": 3.0, \"majority\": 1.0, \"minority\": 246.0, \"unique\": 256.0, \"histogram\": [[330584.0, 54820.0, 67683.0, 57434.0, 30305.0, 14648.0, 9606.0, 5653.0, 2296.0, 1051.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 0.0, \"percentile_98\": 171.0}, \"b2\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 57.1494356187291, \"count\": 574080.0, \"sum\": 32808348.0, \"std\": 56.300819175100656, \"median\": 37.0, \"majority\": 5.0, \"minority\": 0.0, \"unique\": 256.0, \"histogram\": [[271018.0, 34938.0, 54030.0, 69429.0, 70260.0, 32107.0, 29375.0, 9697.0, 2001.0, 1225.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 5.0, \"percentile_98\": 180.0}, \"b3\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 51.251764562430324, \"count\": 574080.0, \"sum\": 29422613.0, \"std\": 39.65505035854822, \"median\": 36.0, \"majority\": 16.0, \"minority\": 252.0, \"unique\": 254.0, \"histogram\": [[203263.0, 150865.0, 104882.0, 42645.0, 30652.0, 25382.0, 12434.0, 2397.0, 1097.0, 463.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 14.0, \"percentile_98\": 158.0}}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:42.866905Z", + "start_time": "2023-04-06T14:25:42.816337Z" + }, + "collapsed": false + }, + "outputs": [], "source": [ "r = httpx.get(\n", " f\"{titiler_endpoint}/cog/statistics\",\n", @@ -170,38 +120,30 @@ ").json()\n", "\n", "print(json.dumps(r))" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:42.816337Z", - "end_time": "2023-04-06T14:25:42.866905Z" - } - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ - "This endpoint is far more configurable than `/bounds` and `info`. You can specify which bands to analyse, how to generate the histogram, and pre-process the image.\n", + "This endpoint is far more configurable than `/info`. You can specify which bands to analyse, how to generate the histogram, and pre-process the image.\n", "\n", "For example, if you wanted to get the statistics of the [VARI](https://www.space4water.org/space/visible-atmospherically-resistant-index-vari) of the image you can use the `expression` parameter to conduct simple band math:" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", - "execution_count": 13, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"(b2-b1)/(b1+b2-b3)\": {\"min\": -1.7976931348623157e+308, \"max\": 1.7976931348623157e+308, \"mean\": null, \"count\": 574080.0, \"sum\": null, \"std\": null, \"median\": -0.15384615384615385, \"majority\": -0.4, \"minority\": -149.0, \"unique\": 18718.0, \"histogram\": [[5646.0, 10176.0, 130905.0, 97746.0, 50184.0, 95842.0, 60322.0, 21478.0, 13552.0, 12204.0], [-1.0, -0.8, -0.6, -0.3999999999999999, -0.19999999999999996, 0.0, 0.20000000000000018, 0.40000000000000013, 0.6000000000000001, 0.8, 1.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": -3.5, \"percentile_98\": 3.3870967741935485}}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:43.393610Z", + "start_time": "2023-04-06T14:25:43.304442Z" + }, + "collapsed": false + }, + "outputs": [], "source": [ "r = httpx.get(\n", " f\"{titiler_endpoint}/cog/statistics\",\n", @@ -213,29 +155,29 @@ ").json()\n", "\n", "print(json.dumps(r))" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:43.304442Z", - "end_time": "2023-04-06T14:25:43.393610Z" - } - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false + }, "source": [ "Alternatively, if you would like to get statistics for only a certain area, you can specify an area via a feature or a feature collection.\n", "\n", "(Note: this endpoint is not available in the mosaicjson endpoint, only `/cog` and `/stac`)" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:43.877434Z", + "start_time": "2023-04-06T14:25:43.867923Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "mahebourg = \"\"\"\n", @@ -284,27 +226,19 @@ " ]\n", "}\n", "\"\"\"" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:43.867923Z", - "end_time": "2023-04-06T14:25:43.877434Z" - } - } + ] }, { "cell_type": "code", - "execution_count": 15, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"geometry\": {\"type\": \"Polygon\", \"coordinates\": [[[57.70358910197049, -20.384114558699935], [57.68564920588395, -20.384114558699935], [57.68209507552771, -20.39855066753664], [57.68666467170024, -20.421074640746554], [57.70341985766697, -20.434397129770545], [57.72999121319131, -20.42392955694521], [57.70358910197049, -20.384114558699935]]]}, \"properties\": {\"statistics\": {\"b1\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 88.5634794986129, \"count\": 619641.0, \"sum\": 54877563.0, \"std\": 55.18714964714274, \"median\": 77.0, \"majority\": 52.0, \"minority\": 253.0, \"unique\": 256.0, \"histogram\": [[67233.0, 110049.0, 129122.0, 90849.0, 77108.0, 44091.0, 44606.0, 37790.0, 18033.0, 760.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 4.0, \"percentile_98\": 208.0}, \"b2\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 112.07155594933195, \"count\": 619641.0, \"sum\": 69444131.0, \"std\": 42.64508357271268, \"median\": 107.0, \"majority\": 103.0, \"minority\": 1.0, \"unique\": 256.0, \"histogram\": [[6004.0, 31108.0, 107187.0, 126848.0, 130731.0, 73650.0, 107827.0, 33264.0, 2403.0, 619.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 34.0, \"percentile_98\": 189.0}, \"b3\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 84.54690377170006, \"count\": 619641.0, \"sum\": 52388728.0, \"std\": 44.64862735915829, \"median\": 77.0, \"majority\": 53.0, \"minority\": 254.0, \"unique\": 256.0, \"histogram\": [[40704.0, 130299.0, 138014.0, 85866.0, 86381.0, 91182.0, 41872.0, 4116.0, 993.0, 214.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 11.0, \"percentile_98\": 170.0}}}}]}\n" - ] - } - ], + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-06T14:25:45.709013Z", + "start_time": "2023-04-06T14:25:44.592051Z" + }, + "collapsed": false + }, + "outputs": [], "source": [ "# NOTE: This is a POST request, unlike all other requests in this example\n", "r = httpx.post(\n", @@ -312,27 +246,20 @@ " data=mahebourg,\n", " params = {\n", " \"url\": cog_url,\n", - " }\n", + " \"max_size\": 1024,\n", + " },\n", + " timeout=20,\n", ").json()\n", "\n", "print(json.dumps(r))\n" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "start_time": "2023-04-06T14:25:44.592051Z", - "end_time": "2023-04-06T14:25:45.709013Z" - } - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } + "source": [] } ], "metadata": { @@ -344,14 +271,14 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.9.19" } }, "nbformat": 4, diff --git a/docs/src/examples/notebooks/Working_with_nonWebMercatorTMS.ipynb b/docs/src/examples/notebooks/Working_with_nonWebMercatorTMS.ipynb index c9e728f6b..21a4daedc 100644 --- a/docs/src/examples/notebooks/Working_with_nonWebMercatorTMS.ipynb +++ b/docs/src/examples/notebooks/Working_with_nonWebMercatorTMS.ipynb @@ -36,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -57,11 +57,11 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "titiler_endpoint = \"http://127.0.0.1:8081\" # \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n", + "titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n", "url = \"https://s3.amazonaws.com/opendata.remotepixel.ca/cogs/natural_earth/world.tif\" # Natural Earth WORLD tif" ] }, @@ -74,31 +74,11 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Supported TMS:\n", - "- LINZAntarticaMapTilegrid\n", - "- EuropeanETRS89_LAEAQuad\n", - "- CanadianNAD83_LCC\n", - "- UPSArcticWGS84Quad\n", - "- NZTM2000\n", - "- NZTM2000Quad\n", - "- UTM31WGS84Quad\n", - "- UPSAntarcticWGS84Quad\n", - "- WorldMercatorWGS84Quad\n", - "- WGS1984Quad\n", - "- WorldCRS84Quad\n", - "- WebMercatorQuad\n" - ] - } - ], + "outputs": [], "source": [ "r = httpx.get(f\"{titiler_endpoint}/tileMatrixSets\").json()\n", "\n", @@ -119,28 +99,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7d78a0adf5954e65b3f46db3cf943f7a", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Map(center=[0, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_text'…" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", - " f\"{titiler_endpoint}/cog/tilejson.json\", params = {\"url\": url}\n", + " f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = {\"url\": url}\n", ").json()\n", "\n", "m = Map(center=(0, 0), zoom=2, basemap={}, crs=projections.EPSG3857)\n", @@ -161,25 +125,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "8c5d6ab05bce4aef9e29ff7ebd0ac02f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Map(center=[0, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_text'…" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", " f\"{titiler_endpoint}/cog/WorldCRS84Quad/tilejson.json\", params = {\"url\": url}\n", @@ -203,25 +151,9 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0615b0bb04ad46198d05f6eb95ed8e6b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Map(center=[50, 65], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_tex…" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r = httpx.get(\n", " f\"{titiler_endpoint}/cog/EuropeanETRS89_LAEAQuad/tilejson.json\", params = {\"url\": url}\n", @@ -273,7 +205,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.18" }, "vscode": { "interpreter": { diff --git a/docs/src/external_links.md b/docs/src/external_links.md index 9cd60516a..68629b6f4 100644 --- a/docs/src/external_links.md +++ b/docs/src/external_links.md @@ -13,24 +13,38 @@ * David McCracken's Blog on [Plotly Dash Interactive Mapping - Dash Leaflet & TiTiler](https://www.pywram.com/t/blog-plotly-dash-interactive-mapping-dash-leaflet-titiler/287) +## TiTiler extensions/plugins + +* [stac-utils/titiler-pgstac](https://github.com/stac-utils/titiler-pgstac): TiTiler extension which connects to a PgSTAC database to create dynamic mosaics based on search queries. + +* [developmentseed/titiler-xarray](https://github.com/developmentseed/titiler-xarray): TiTiler extension for xarray + +* [developmentseed/titiler-images](https://github.com/developmentseed/titiler-images): TiTiler demo application for Sentinel-2 Digital Twin dataset + + ## Projects / Demo using TiTiler * ESA Charter Mapper [geobrowser](https://docs.charter.uat.esaportal.eu/webPortal/geobrowser/titiler/) * [developmentseed/titiler-digitaltwin](https://github.com/developmentseed/titiler-digitaltwin): TiTiler demo application for Sentinel-2 Digital Twin dataset -* [developmentseed/titiler-pds](https://github.com/developmentseed/titiler-pds): TiTiler demo application for Sentinel-2 and Landsat-8 AWS Public Datasets - * [developmentseed/titiler-mvt](https://github.com/developmentseed/titiler-mvt): TiTiler demo application to create Mapbox Vector Tiles from COG +* [developmentseed/titiler-pds](https://github.com/developmentseed/titiler-pds): TiTiler demo application for Sentinel-2 and Landsat-8 AWS Public Datasets + * [stac-utils/stac-fastapi](https://github.com/stac-utils/stac-fastapi): STAC API implementation with FastAPI. * [c-core-labs/stac-api](https://github.com/c-core-labs/stac-api): STAC compliant API implementation (built from stac-fastapi) -* [lambgeo/titiler-layer](https://github.com/lambgeo/titiler-layer): TiTiler Lambda layers for easy deployment on AWS +* [developmentseed/titiler-lambda-layer](https://github.com/developmentseed/titiler-lambda-layer): TiTiler Lambda layers for easy deployment on AWS * [Terradue/Stars](https://github.com/Terradue/Stars): Spatio Temporal Asset Runtime Services +* [developmentseed/rio-viz](https://github.com/developmentseed/rio-viz): Visualize Cloud Optimized GeoTIFF in browser + +* [developmentseed/pearl-backend](https://github.com/developmentseed/pearl-backend): PEARL (Planetary Computer Land Cover Mapping) Platform API and Infrastructure + +* [microsoft/planetary-computer-apis](https://github.com/microsoft/planetary-computer-apis): Microsoft Planetary Computer APIs ## Conferences / presentations / videos diff --git a/docs/src/intro.md b/docs/src/intro.md index 8941f6aa0..685f18acd 100644 --- a/docs/src/intro.md +++ b/docs/src/intro.md @@ -27,7 +27,9 @@ See default endpoints documentation pages: * [`/cog` - Cloud Optimized GeoTIFF](endpoints/cog.md) * [`/mosaicjson` - MosaicJSON](endpoints/mosaic.md) * [`/stac` - Spatio Temporal Asset Catalog](endpoints/stac.md) -* [`/tms` - TileMatrixSets](endpoints/tms.md) +* [`/tileMatrixSets` - Tiling Schemes](endpoints/tms.md) +* [`/algorithms` - Algorithms](endpoints/algorithms.md) +* [`/colorMaps` - ColorMaps](endpoints/colormaps.md) #### Settings @@ -35,6 +37,7 @@ The default application can be customized using environment variables defined in - `NAME` (str): name of the application. Defaults to `titiler`. - `CORS_ORIGINS` (str, `,` delimited origins): allowed CORS origin. Defaults to `*`. +- `CORS_ALLOW_METHODS` (str, `,` delimited methods): allowed CORS methods. Defaults to `GET`. - `CACHECONTROL` (str): Cache control header to add to responses. Defaults to `"public, max-age=3600"`. - `ROOT_PATH` (str): path behind proxy. - `DEBUG` (str): adds `LoggerMiddleware` and `TotalTimeMiddleware` in the middleware stack. @@ -42,10 +45,11 @@ The default application can be customized using environment variables defined in - `DISABLE_STAC` (bool): disable `/stac` endpoints. - `DISABLE_MOSAIC` (bool): disable `/mosaic` endpoints. - `LOWER_CASE_QUERY_PARAMETERS` (bool): transform all query-parameters to lower case (see https://github.com/developmentseed/titiler/pull/321). +- `GLOBAL_ACCESS_TOKEN` (str | None): a string which is required in the `?access_token=` query param with every request. ## Customized, minimal app -`TiTiler` has been developed so users can build their own app using only the portions they need. Using [TilerFactories](advanced/tiler_factories.md), users can create a fully customized application with only the endpoints needed. +`TiTiler` has been developed so users can build their own application with only the endpoints they need. Using [Factories](advanced/endpoints_factories.md), users can create a fully customized application with only a defined set of endpoints. When building a custom application, you may wish to only install the `core` and/or `mosaic` modules. To install these from PyPI: @@ -99,6 +103,7 @@ from fastapi.security.api_key import APIKeyQuery from titiler.application.main import app from titiler.core.factory import TilerFactory +import uvicorn api_key_query = APIKeyQuery(name="access_token", auto_error=False) @@ -106,11 +111,11 @@ api_key_query = APIKeyQuery(name="access_token", auto_error=False) def token_validation(access_token: str = Security(api_key_query)): """stupid token validation.""" if not access_token: - raise HTTPException(status_code=403, detail="Missing `access_token`") + raise HTTPException(status_code=401, detail="Missing `access_token`") # if access_token == `token` then OK if not access_token == "token": - raise HTTPException(status_code=403, detail="Invalid `access_token`") + raise HTTPException(status_code=401, detail="Invalid `access_token`") return True diff --git a/docs/src/overrides/partials/integrations/analytics/plausible.html b/docs/src/overrides/partials/integrations/analytics/plausible.html new file mode 100644 index 000000000..828f7d9ab --- /dev/null +++ b/docs/src/overrides/partials/integrations/analytics/plausible.html @@ -0,0 +1,53 @@ + + + + diff --git a/pyproject.toml b/pyproject.toml index b691b6110..dad9ffb60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,31 +21,20 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] -version="0.11.7" +version="0.19.0.dev" dependencies = [ - "titiler.core==0.11.7", - "titiler.extensions==0.11.7", - "titiler.mosaic==0.11.7", - "titiler.application==0.11.7", -] - -[project.optional-dependencies] -dev = [ - "pre-commit", -] -docs = [ - "nbconvert", - "mkdocs", - "mkdocs-jupyter", - "mkdocs-material", - "pygments", - "pdocs", + "titiler.core==0.19.0.dev", + "titiler.extensions==0.19.0.dev", + "titiler.mosaic==0.19.0.dev", + "titiler.application==0.19.0.dev", ] [project.urls] @@ -80,7 +69,7 @@ exclude = [ ] [build-system] -requires = ["hatchling"] +requires = ["hatchling>=1.12.0"] build-backend = "hatchling.build" [tool.coverage.run] @@ -127,3 +116,138 @@ no_implicit_optional = true strict_optional = true namespace_packages = true explicit_package_bases = true + +[tool.pytest.ini_options] +filterwarnings = [ + "ignore::rasterio.errors.NotGeoreferencedWarning", +] + +[tool.hatch.build.targets.wheel] +bypass-selection = true + +[tool.bumpversion] +current_version = "0.19.0.dev" +parse = """(?x) + (?P\\d+)\\. + (?P\\d+)\\. + (?P\\d+) + (?: + (?Pa|b|rc) # pre-release label + (?P\\d+) # pre-release version number + )? # pre-release section is optional + (?: + \\.post + (?P\\d+) # post-release version number + )? # post-release section is optional + (?: + \\.dev + )? +""" +serialize = [ + "{major}.{minor}.{patch}.post{post_n}", + "{major}.{minor}.{patch}{pre_l}{pre_n}", + "{major}.{minor}.{patch}.dev", + "{major}.{minor}.{patch}", +] + +search = "{current_version}" +replace = "{new_version}" +regex = false +tag = true +commit = true +tag_name = "{new_version}" + +############################################################################### +# update titiler meta package +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'version="{current_version}"' +replace = 'version="{new_version}"' + +# core +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'titiler.core=={current_version}' +replace = 'titiler.core=={new_version}' + +# extensions +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'titiler.extensions=={current_version}' +replace = 'titiler.extensions=={new_version}' + +# mosaic +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'titiler.mosaic=={current_version}' +replace = 'titiler.mosaic=={new_version}' + +# application +[[tool.bumpversion.files]] +filename = "pyproject.toml" +search = 'titiler.application=={current_version}' +replace = 'titiler.application=={new_version}' + +############################################################################### +# Update sub modules version +# titiler.core +[[tool.bumpversion.files]] +filename = "src/titiler/core/titiler/core/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +# titiler.extensions +[[tool.bumpversion.files]] +filename = "src/titiler/extensions/titiler/extensions/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +# titiler.mosaic +[[tool.bumpversion.files]] +filename = "src/titiler/mosaic/titiler/mosaic/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +# titiler.application +[[tool.bumpversion.files]] +filename = "src/titiler/application/titiler/application/__init__.py" +search = '__version__ = "{current_version}"' +replace = '__version__ = "{new_version}"' + +############################################################################### +# Update sub modules dependencies +[[tool.bumpversion.files]] +filename = "src/titiler/extensions/pyproject.toml" +search = 'titiler.core=={current_version}' +replace = 'titiler.core=={new_version}' + +[[tool.bumpversion.files]] +filename = "src/titiler/mosaic/pyproject.toml" +search = 'titiler.core=={current_version}' +replace = 'titiler.core=={new_version}' + +[[tool.bumpversion.files]] +filename = "src/titiler/application/pyproject.toml" +search = 'titiler.core=={current_version}' +replace = 'titiler.core=={new_version}' + +[[tool.bumpversion.files]] +filename = "src/titiler/application/pyproject.toml" +search = 'titiler.extensions[cogeo,stac]=={current_version}' +replace = 'titiler.extensions[cogeo,stac]=={new_version}' + +[[tool.bumpversion.files]] +filename = "src/titiler/application/pyproject.toml" +search = 'titiler.mosaic=={current_version}' +replace = 'titiler.mosaic=={new_version}' + +# deployment files +[[tool.bumpversion.files]] +filename = "deployment/aws/lambda/Dockerfile" +search = 'titiler.application=={current_version}' +replace = 'titiler.application=={new_version}' + +[[tool.bumpversion.files]] +filename = "deployment/k8s/charts/Chart.yaml" +search = 'appVersion: {current_version}' +replace = 'appVersion: {new_version}' diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt new file mode 100644 index 000000000..06136a824 --- /dev/null +++ b/requirements/requirements-dev.txt @@ -0,0 +1,2 @@ +pre-commit +bump-my-version diff --git a/requirements/requirements-docs.txt b/requirements/requirements-docs.txt new file mode 100644 index 000000000..8fcfa9aa1 --- /dev/null +++ b/requirements/requirements-docs.txt @@ -0,0 +1,6 @@ +black>=23.10.1 +mkdocs>=1.4.3 +mkdocs-jupyter>=0.24.5 +mkdocs-material[imaging]>=9.5 +griffe-inherited-docstrings>=1.0.0 +mkdocstrings[python]>=0.25.1 diff --git a/src/titiler/application/README.md b/src/titiler/application/README.md index d8ca172eb..ec06a22cd 100644 --- a/src/titiler/application/README.md +++ b/src/titiler/application/README.md @@ -6,19 +6,19 @@ ## Installation ```bash -$ pip install -U pip +$ python -m pip install -U pip # From Pypi -$ pip install titiler.application +$ python -m pip install titiler.application # Or from sources $ git clone https://github.com/developmentseed/titiler.git -$ cd titiler && pip install -e titiler/application +$ cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application ``` Launch Application ```bash -$ pip install uvicorn +$ python -m pip install uvicorn $ uvicorn titiler.application.main:app --reload ``` @@ -30,9 +30,7 @@ titiler/ ├── tests/ - Tests suite └── titiler/application/ - `application` namespace package ├── templates/ - | ├── index.html - demo landing page - | ├── cog_index.html - demo viewer for `/cog` - | └── stac_index.html - demo viewer for `/stac` + | └── index.html - Landing page ├── main.py - Main FastAPI application └── settings.py - demo settings (cache, cors...) ``` diff --git a/src/titiler/application/pyproject.toml b/src/titiler/application/pyproject.toml index f88aa0750..c11b2bc37 100644 --- a/src/titiler/application/pyproject.toml +++ b/src/titiler/application/pyproject.toml @@ -21,19 +21,21 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] dynamic = ["version"] dependencies = [ - "titiler.core==0.11.7", - "titiler.extensions[cogeo,stac]==0.11.7", - "titiler.mosaic==0.11.7", - "starlette-cramjam>=0.3,<0.4", - "python-dotenv", + "titiler.core==0.19.0.dev", + "titiler.extensions[cogeo,stac]==0.19.0.dev", + "titiler.mosaic==0.19.0.dev", + "starlette-cramjam>=0.4,<0.5", + "pydantic-settings~=2.0", ] [project.optional-dependencies] @@ -43,6 +45,7 @@ test = [ "pytest-asyncio", "httpx", "brotlipy", + "boto3", ] server = [ "uvicorn[standard]>=0.12.0,<0.19.0", diff --git a/src/titiler/application/tests/fixtures/item.json b/src/titiler/application/tests/fixtures/item.json index f8e447265..8a20a9ed5 100644 --- a/src/titiler/application/tests/fixtures/item.json +++ b/src/titiler/application/tests/fixtures/item.json @@ -1,18 +1,10 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.1", - "stac_extensions": [ - "eo", - "view", - "proj" - ], + "stac_version": "1.0.0", "id": "S2A_34SGA_20200318_0_L2A", - "bbox": [ - 23.293255090449595, - 31.505183020453355, - 24.296453548295318, - 32.51147809805106 - ], + "properties": { + "datetime": "2020-03-18T09:11:33Z" + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -40,91 +32,31 @@ ] ] }, - "properties": { - "datetime": "2020-03-18T09:11:33Z", - "platform": "sentinel-2a", - "constellation": "sentinel-2", - "instruments": [ - "msi" - ], - "gsd": 10, - "data_coverage": 73.85, - "view:off_nadir": 0, - "eo:cloud_cover": 89.84, - "proj:epsg": 32634, - "sentinel:latitude_band": "S", - "sentinel:grid_square": "GA", - "sentinel:sequence": "0", - "sentinel:product_id": "S2A_MSIL2A_20200318T085701_N0214_R007_T34SGA_20200318T115254", - "created": "2020-05-12T21:03:26.671Z", - "updated": "2020-05-12T21:03:26.671Z" - }, - "collection": "sentinel-s2-l2a-cogs", + "links": [ + { + "rel": "self", + "href": "https://myurl.com/item.json", + "type": "application/json" + } + ], "assets": { "B01": { - "title": "Band 1 (coastal)", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", "href": "https://myurl.com/B01.tif", - "proj:shape": [ - 1830, - 1830 - ], - "proj:transform": [ - 60, - 0, - 699960, - 0, - -60, - 3600000, - 0, - 0, - 1 - ] + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 1 (coastal)" }, "B09": { - "title": "Band 9", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", "href": "https://myurl.com/B09.tif", - "proj:shape": [ - 1830, - 1830 - ], - "proj:transform": [ - 60, - 0, - 699960, - 0, - -60, - 3600000, - 0, - 0, - 1 - ] + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 9" } }, - "links": [ - { - "rel": "self", - "href": "s3://sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_34SGA_20200318_0_L2A/S2A_34SGA_20200318_0_L2A.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "https://myurl.com/v0/collections/sentinel-s2-l2a" - }, - { - "rel": "collection", - "href": "https://myurl.com/v0/collections/sentinel-s2-l2a" - }, - { - "rel": "root", - "href": "https://myurl.com/v0/" - }, - { - "title": "Source STAC Item", - "rel": "derived_from", - "href": "https://myurl.com/collections/sentinel-s2-l2a/items/S2A_34SGA_20200318_0_L2A", - "type": "application/json" - } - ] + "bbox": [ + 23.293255090449595, + 31.505183020453355, + 24.296453548295318, + 32.51147809805106 + ], + "stac_extensions": [], + "collection": "sentinel-s2-l2a-cogs" } diff --git a/src/titiler/application/tests/routes/test_cog.py b/src/titiler/application/tests/routes/test_cog.py index bacd1cae2..75c30a022 100644 --- a/src/titiler/application/tests/routes/test_cog.py +++ b/src/titiler/application/tests/routes/test_cog.py @@ -4,7 +4,7 @@ import os from io import BytesIO from unittest.mock import patch -from urllib.parse import parse_qsl, urlencode, urlparse +from urllib.parse import parse_qsl, urlparse import numpy import pytest @@ -54,22 +54,28 @@ def test_wmts(rio, app): """test wmts endpoints.""" rio.open = mock_rasterio_open - response = app.get("/cog/WMTSCapabilities.xml?url=https://myurl.com/cog.tif") + response = app.get( + "/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/xml" assert response.headers["Cache-Control"] == "private, max-age=3600" assert ( - "http://testserver/cog/WMTSCapabilities.xml?url=https" + "http://testserver/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https" in response.content.decode() ) - assert "cogeo" in response.content.decode() + assert "default" in response.content.decode() assert ( "http://testserver/cog/tiles/WebMercatorQuad/{TileMatrix}/{TileCol}/{TileRow}@1x.png?url=https" in response.content.decode() ) + assert ( + "http://www.opengis.net/def/crs/EPSG/0/3857" + in response.content.decode() + ) response = app.get( - "/cog/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&tile_scale=2&tile_format=jpg" + "/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&tile_scale=2&tile_format=jpg" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/xml" @@ -78,6 +84,13 @@ def test_wmts(rio, app): in response.content.decode() ) + response = app.get( + "/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&use_epsg=true" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/xml" + assert "EPSG:3857" in response.content.decode() + @patch("rio_tiler.io.rasterio.rasterio") def test_tile(rio, app): @@ -86,7 +99,7 @@ def test_tile(rio, app): # full tile response = app.get( - "/cog/tiles/8/87/48?url=https://myurl.com/cog.tif&rescale=0,1000" + "/cog/tiles/WebMercatorQuad/8/87/48?url=https://myurl.com/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" @@ -96,7 +109,7 @@ def test_tile(rio, app): assert meta["height"] == 256 response = app.get( - "/cog/tiles/8/87/48@2x?url=https://myurl.com/cog.tif&rescale=0,1000&color_formula=Gamma R 3" + "/cog/tiles/WebMercatorQuad/8/87/48@2x?url=https://myurl.com/cog.tif&rescale=0,1000&color_formula=Gamma R 3" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" @@ -105,25 +118,25 @@ def test_tile(rio, app): assert meta["height"] == 512 response = app.get( - "/cog/tiles/8/87/48.jpg?url=https://myurl.com/cog.tif&rescale=0,1000" + "/cog/tiles/WebMercatorQuad/8/87/48.jpg?url=https://myurl.com/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpg" response = app.get( - "/cog/tiles/8/87/48.jpeg?url=https://myurl.com/cog.tif&rescale=0,1000" + "/cog/tiles/WebMercatorQuad/8/87/48.jpeg?url=https://myurl.com/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" response = app.get( - "/cog/tiles/8/87/48@2x.jpg?url=https://myurl.com/cog.tif&rescale=0,1000" + "/cog/tiles/WebMercatorQuad/8/87/48@2x.jpg?url=https://myurl.com/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpg" response = app.get( - "/cog/tiles/8/87/48@2x.tif?url=https://myurl.com/cog.tif&nodata=0&bidx=1" + "/cog/tiles/WebMercatorQuad/8/87/48@2x.tif?url=https://myurl.com/cog.tif&nodata=0&bidx=1" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" @@ -133,14 +146,16 @@ def test_tile(rio, app): assert meta["width"] == 512 assert meta["height"] == 512 - response = app.get("/cog/tiles/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0") + response = app.get( + "/cog/tiles/WebMercatorQuad/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" data = numpy.load(BytesIO(response.content)) assert data.shape == (2, 256, 256) response = app.get( - "/cog/tiles/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false" + "/cog/tiles/WebMercatorQuad/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" @@ -150,7 +165,7 @@ def test_tile(rio, app): # Test brotli compression headers = {"Accept-Encoding": "br"} response = app.get( - "/cog/tiles/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", + "/cog/tiles/WebMercatorQuad/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", headers=headers, ) assert response.status_code == 200 @@ -159,7 +174,7 @@ def test_tile(rio, app): # Exclude png from compression middleware headers = {"Accept-Encoding": "br"} response = app.get( - "/cog/tiles/8/87/48.png?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", + "/cog/tiles/WebMercatorQuad/8/87/48.png?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", headers=headers, ) assert response.status_code == 200 @@ -168,7 +183,7 @@ def test_tile(rio, app): # Test gzip fallback headers = {"Accept-Encoding": "gzip"} response = app.get( - "/cog/tiles/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", + "/cog/tiles/WebMercatorQuad/8/87/48.npy?url=https://myurl.com/cog.tif&nodata=0&return_mask=false", headers=headers, ) assert response.status_code == 200 @@ -176,19 +191,23 @@ def test_tile(rio, app): # partial response = app.get( - "/cog/tiles/8/84/47?url=https://myurl.com/cog.tif&rescale=0,1000" + "/cog/tiles/WebMercatorQuad/8/84/47?url=https://myurl.com/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" response = app.get( - "/cog/tiles/8/84/47?url=https://myurl.com/cog.tif&nodata=0&rescale=0,1000&colormap_name=viridis" + "/cog/tiles/WebMercatorQuad/8/84/47?url=https://myurl.com/cog.tif&nodata=0&rescale=0,1000&colormap_name=viridis" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" - cmap = urlencode( - { + # valid colormap + response = app.get( + "/cog/tiles/WebMercatorQuad/8/53/50.png", + params={ + "url": "https://myurl.com/above_cog.tif", + "bidx": 1, "colormap": json.dumps( { "1": [58, 102, 24, 255], @@ -196,34 +215,31 @@ def test_tile(rio, app): "3": "#b1b129", "4": "#ddcb9aFF", } - ) - } - ) - response = app.get( - f"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}" + ), + }, ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" - cmap = urlencode({"colormap": json.dumps({"1": [58, 102]})}) + # invalid colormap shape response = app.get( - f"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}" + "/cog/tiles/WebMercatorQuad/8/53/50.png", + params={ + "url": "https://myurl.com/above_cog.tif", + "bidx": 1, + "colormap": json.dumps({"1": [58, 102]}), + }, ) assert response.status_code == 400 - cmap = urlencode({"colormap": {"1": "#ddcb9aFF"}}) + # bad resampling response = app.get( - f"/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}" - ) - assert response.status_code == 400 - - response = app.get( - "/cog/tiles/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&resampling=somethingwrong" + "/cog/tiles/WebMercatorQuad/8/53/50.png?url=https://myurl.com/above_cog.tif&bidx=1&resampling=somethingwrong" ) assert response.status_code == 422 response = app.get( - "/cog/tiles/8/87/48@2x.tif?url=https://myurl.com/cog.tif&nodata=0&bidx=1&return_mask=false" + "/cog/tiles/WebMercatorQuad/8/87/48@2x.tif?url=https://myurl.com/cog.tif&nodata=0&bidx=1&return_mask=false" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" @@ -239,7 +255,9 @@ def test_tilejson(rio, app): """test /tilejson endpoint.""" rio.open = mock_rasterio_open - response = app.get("/cog/tilejson.json?url=https://myurl.com/cog.tif") + response = app.get( + "/cog/WebMercatorQuad/tilejson.json?url=https://myurl.com/cog.tif" + ) assert response.status_code == 200 body = response.json() assert body["tilejson"] == "2.2.0" @@ -255,7 +273,7 @@ def test_tilejson(rio, app): assert body["center"] response = app.get( - "/cog/tilejson.json?url=https://myurl.com/cog.tif&tile_format=png&tile_scale=2" + "/cog/WebMercatorQuad/tilejson.json?url=https://myurl.com/cog.tif&tile_format=png&tile_scale=2" ) assert response.status_code == 200 body = response.json() @@ -269,9 +287,13 @@ def test_tilejson(rio, app): "3": "#b1b129", "4": "#ddcb9aFF", } - cmap = urlencode({"colormap": json.dumps(cmap_dict)}) response = app.get( - f"/cog/tilejson.json?url=https://myurl.com/above_cog.tif&bidx=1&{cmap}" + "/cog/WebMercatorQuad/tilejson.json", + params={ + "url": "https://myurl.com/above_cog.tif", + "bidx": 1, + "colormap": json.dumps(cmap_dict), + }, ) assert response.status_code == 200 body = response.json() @@ -340,11 +362,11 @@ def test_preview(rio, app): @patch("rio_tiler.io.rasterio.rasterio") def test_part(rio, app): - """test /crop endpoint.""" + """test /bbox endpoint.""" rio.open = mock_rasterio_open response = app.get( - "/cog/crop/-56.228,72.715,-54.547,73.188.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" + "/cog/bbox/-56.228,72.715,-54.547,73.188.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -355,7 +377,7 @@ def test_part(rio, app): assert meta["driver"] == "PNG" response = app.get( - "/cog/crop/-56.228,72.715,-54.547,73.188.jpg?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256&return_mask=false" + "/cog/bbox/-56.228,72.715,-54.547,73.188.jpg?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256&return_mask=false" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpg" @@ -366,7 +388,7 @@ def test_part(rio, app): assert meta["driver"] == "JPEG" response = app.get( - "/cog/crop/-56.228,72.715,-54.547,73.188/128x128.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" + "/cog/bbox/-56.228,72.715,-54.547,73.188/128x128.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -376,7 +398,7 @@ def test_part(rio, app): assert meta["driver"] == "PNG" response = app.get( - "/cog/crop/-56.228,72.715,-54.547,73.188.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256&width=512&height=512" + "/cog/bbox/-56.228,72.715,-54.547,73.188.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256&width=512&height=512" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -386,7 +408,7 @@ def test_part(rio, app): assert meta["driver"] == "PNG" response = app.get( - "/cog/crop/-56.228,72.715,-54.547,73.188.npy?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" + "/cog/bbox/-56.228,72.715,-54.547,73.188.npy?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" @@ -417,7 +439,9 @@ def test_tile_outside_bounds_error(rio, app): """raise 404 when tile is not found.""" rio.open = mock_rasterio_open - response = app.get("/cog/tiles/15/0/0?url=https://myurl.com/cog.tif&rescale=0,1000") + response = app.get( + "/cog/tiles/WebMercatorQuad/15/0/0?url=https://myurl.com/cog.tif&rescale=0,1000" + ) assert response.status_code == 404 assert response.headers["Cache-Control"] == "private, max-age=3600" diff --git a/src/titiler/application/tests/routes/test_mosaic.py b/src/titiler/application/tests/routes/test_mosaic.py index 3e092329d..d9acea550 100644 --- a/src/titiler/application/tests/routes/test_mosaic.py +++ b/src/titiler/application/tests/routes/test_mosaic.py @@ -4,7 +4,7 @@ from typing import Any, Callable from unittest.mock import patch -import mercantile +import morecantile from cogeo_mosaic.backends import FileBackend from cogeo_mosaic.mosaic import MosaicJSON @@ -57,26 +57,30 @@ def test_info(app): response = app.get("/mosaicjson/info", params={"url": MOSAICJSON_FILE}) assert response.status_code == 200 body = response.json() - assert body["minzoom"] == 7 - assert body["maxzoom"] == 9 assert body["name"] == "mosaic" # mosaic.name is not set assert body["quadkeys"] == [] + assert body["mosaic_minzoom"] == 7 + assert body["mosaic_maxzoom"] == 9 + assert body["mosaic_tilematrixset"] response = app.get("/mosaicjson/info.geojson", params={"url": MOSAICJSON_FILE}) assert response.status_code == 200 assert response.headers["content-type"] == "application/geo+json" body = response.json() assert body["geometry"] - assert body["properties"]["minzoom"] == 7 - assert body["properties"]["maxzoom"] == 9 assert body["properties"]["name"] == "mosaic" # mosaic.name is not set assert body["properties"]["quadkeys"] == [] + assert body["properties"]["mosaic_minzoom"] == 7 + assert body["properties"]["mosaic_maxzoom"] == 9 + assert body["properties"]["mosaic_tilematrixset"] def test_tilejson(app): - """test GET /mosaicjson/tilejson.json endpoint""" + """test GET /mosaicjson/WebMercatorQuad/tilejson.json endpoint""" mosaicjson = read_json_fixture(MOSAICJSON_FILE) - response = app.get("/mosaicjson/tilejson.json", params={"url": MOSAICJSON_FILE}) + response = app.get( + "/mosaicjson/WebMercatorQuad/tilejson.json", params={"url": MOSAICJSON_FILE} + ) assert response.status_code == 200 body = response.json() TileJSON(**body) @@ -111,13 +115,14 @@ def test_tile(app): """Test GET /mosaicjson/tiles endpoint""" mosaicjson = read_json_fixture(MOSAICJSON_FILE) bounds = mosaicjson["bounds"] - tile = mercantile.tile(*mosaicjson["center"]) - partial_tile = mercantile.tile(bounds[0], bounds[1], mosaicjson["minzoom"]) + tms = morecantile.tms.get("WebMercatorQuad") + tile = tms.tile(*mosaicjson["center"]) + partial_tile = tms.tile(bounds[0], bounds[1], mosaicjson["minzoom"]) with patch.object(FileBackend, "_read", mosaic_read_factory(MOSAICJSON_FILE)): # full tile response = app.get( - f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}", + f"/mosaicjson/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}", params={"url": MOSAICJSON_FILE}, ) assert response.status_code == 200 @@ -126,7 +131,7 @@ def test_tile(app): assert meta["width"] == meta["height"] == 256 response = app.get( - f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}@2x", + f"/mosaicjson/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}@2x", params={"url": MOSAICJSON_FILE}, ) assert response.status_code == 200 @@ -135,7 +140,7 @@ def test_tile(app): assert meta["width"] == meta["height"] == 512 response = app.get( - f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}.tif", + f"/mosaicjson/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.tif", params={"url": MOSAICJSON_FILE}, ) assert response.status_code == 200 @@ -145,7 +150,7 @@ def test_tile(app): assert meta["crs"] == 3857 response = app.get( - f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}@2x.tif", + f"/mosaicjson/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}@2x.tif", params={"url": MOSAICJSON_FILE, "nodata": 0, "bidx": 1}, ) assert response.status_code == 200 @@ -157,7 +162,7 @@ def test_tile(app): assert meta["height"] == 512 response = app.get( - f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}@2x.jpg", + f"/mosaicjson/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}@2x.jpg", params={ "url": MOSAICJSON_FILE, "rescale": "0,1000", @@ -170,14 +175,14 @@ def test_tile(app): # partial tile response = app.get( - f"/mosaicjson/tiles/{partial_tile.z}/{partial_tile.x}/{partial_tile.y}", + f"/mosaicjson/tiles/WebMercatorQuad/{partial_tile.z}/{partial_tile.x}/{partial_tile.y}", params={"url": MOSAICJSON_FILE}, ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" response = app.get( - f"/mosaicjson/tiles/{partial_tile.z}/{partial_tile.x}/{partial_tile.y}.tif", + f"/mosaicjson/tiles/WebMercatorQuad/{partial_tile.z}/{partial_tile.x}/{partial_tile.y}.tif", params={"url": MOSAICJSON_FILE, "resampling": "bilinear"}, ) assert response.status_code == 200 @@ -185,10 +190,11 @@ def test_tile(app): def test_wmts(app): - """test GET /mosaicjson/WMTSCapabilities.xml endpoint""" + """test GET /mosaicjson/WebMercatorQuad/WMTSCapabilities.xml endpoint""" with patch.object(FileBackend, "_read", mosaic_read_factory(MOSAICJSON_FILE)): response = app.get( - "/mosaicjson/WMTSCapabilities.xml", params={"url": MOSAICJSON_FILE} + "/mosaicjson/WebMercatorQuad/WMTSCapabilities.xml", + params={"url": MOSAICJSON_FILE}, ) assert response.status_code == 200 assert response.headers["content-type"] == "application/xml" @@ -198,7 +204,7 @@ def test_wmts(app): ) response = app.get( - "/mosaicjson/WMTSCapabilities.xml", + "/mosaicjson/WebMercatorQuad/WMTSCapabilities.xml", params={"url": MOSAICJSON_FILE, "tile_scale": 2}, ) assert response.status_code == 200 diff --git a/src/titiler/application/tests/routes/test_stac.py b/src/titiler/application/tests/routes/test_stac.py index 2ced6f0dd..395daff53 100644 --- a/src/titiler/application/tests/routes/test_stac.py +++ b/src/titiler/application/tests/routes/test_stac.py @@ -75,11 +75,13 @@ def test_tile(httpx, rio, app): rio.open = mock_rasterio_open # Missing assets - response = app.get("/stac/tiles/9/289/207?url=https://myurl.com/item.json") + response = app.get( + "/stac/tiles/WebMercatorQuad/9/289/207?url=https://myurl.com/item.json" + ) assert response.status_code == 400 response = app.get( - "/stac/tiles/9/289/207?url=https://myurl.com/item.json&assets=B01&rescale=0,1000" + "/stac/tiles/WebMercatorQuad/9/289/207?url=https://myurl.com/item.json&assets=B01&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -88,7 +90,7 @@ def test_tile(httpx, rio, app): assert meta["height"] == 256 response = app.get( - "/stac/tiles/9/289/207?url=https://myurl.com/item.json&expression=B01_b1&rescale=0,1000" + "/stac/tiles/WebMercatorQuad/9/289/207?url=https://myurl.com/item.json&expression=B01_b1&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -104,11 +106,13 @@ def test_tilejson(httpx, rio, app): httpx.get = mock_RequestGet rio.open = mock_rasterio_open - response = app.get("/stac/tilejson.json?url=https://myurl.com/item.json") + response = app.get( + "/stac/WebMercatorQuad/tilejson.json?url=https://myurl.com/item.json" + ) assert response.status_code == 400 response = app.get( - "/stac/tilejson.json?url=https://myurl.com/item.json&assets=B01&minzoom=5&maxzoom=10" + "/stac/WebMercatorQuad/tilejson.json?url=https://myurl.com/item.json&assets=B01&minzoom=5&maxzoom=10" ) assert response.status_code == 200 body = response.json() @@ -125,7 +129,7 @@ def test_tilejson(httpx, rio, app): assert body["center"] response = app.get( - "/stac/tilejson.json?url=https://myurl.com/item.json&assets=B01&tile_format=png&tile_scale=2" + "/stac/WebMercatorQuad/tilejson.json?url=https://myurl.com/item.json&assets=B01&tile_format=png&tile_scale=2" ) assert response.status_code == 200 body = response.json() @@ -182,12 +186,12 @@ def test_part(httpx, rio, app): # Missing Assets or Expression response = app.get( - "/stac/crop/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json" + "/stac/bbox/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json" ) assert response.status_code == 400 response = app.get( - "/stac/crop/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&assets=B01&rescale=0,1000&max_size=64" + "/stac/bbox/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&assets=B01&rescale=0,1000&max_size=64" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -196,7 +200,7 @@ def test_part(httpx, rio, app): assert meta["height"] == 14 response = app.get( - "/stac/crop/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&assets=B01&rescale=0,1000&max_size=64&width=128&height=128" + "/stac/bbox/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&assets=B01&rescale=0,1000&max_size=64&width=128&height=128" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -205,7 +209,7 @@ def test_part(httpx, rio, app): assert meta["height"] == 128 response = app.get( - "/stac/crop/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&expression=B01_b1&rescale=0,1000&max_size=64" + "/stac/bbox/23.878,32.063,23.966,32.145.png?url=https://myurl.com/item.json&expression=B01_b1&rescale=0,1000&max_size=64" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -217,7 +221,7 @@ def test_part(httpx, rio, app): @patch("rio_tiler.io.rasterio.rasterio") @patch("rio_tiler.io.stac.httpx") def test_point(httpx, rio, app): - """test crop endpoints.""" + """test point endpoints.""" httpx.get = mock_RequestGet rio.open = mock_rasterio_open diff --git a/src/titiler/application/tests/test_main.py b/src/titiler/application/tests/test_main.py index 81ddba727..f573f4ca0 100644 --- a/src/titiler/application/tests/test_main.py +++ b/src/titiler/application/tests/test_main.py @@ -6,3 +6,9 @@ def test_health(app): response = app.get("/healthz") assert response.status_code == 200 assert response.json() == {"ping": "pong!"} + + response = app.get("/api") + assert response.status_code == 200 + + response = app.get("/api.html") + assert response.status_code == 200 diff --git a/src/titiler/application/titiler/application/__init__.py b/src/titiler/application/titiler/application/__init__.py index f59030372..9aebeaf6b 100644 --- a/src/titiler/application/titiler/application/__init__.py +++ b/src/titiler/application/titiler/application/__init__.py @@ -1,3 +1,3 @@ """titiler.application""" -__version__ = "0.11.7" +__version__ = "0.19.0.dev" diff --git a/src/titiler/application/titiler/application/main.py b/src/titiler/application/titiler/application/main.py index d6366d8bd..03bafbe79 100644 --- a/src/titiler/application/titiler/application/main.py +++ b/src/titiler/application/titiler/application/main.py @@ -1,10 +1,12 @@ """titiler app.""" import logging +import re import jinja2 -from fastapi import FastAPI -from rio_tiler.io import STACReader +from fastapi import Depends, FastAPI, HTTPException, Security +from fastapi.security.api_key import APIKeyQuery +from rio_tiler.io import Reader, STACReader from starlette.middleware.cors import CORSMiddleware from starlette.requests import Request from starlette.responses import HTMLResponse @@ -16,6 +18,7 @@ from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers from titiler.core.factory import ( AlgorithmFactory, + ColorMapFactory, MultiBaseTilerFactory, TilerFactory, TMSFactory, @@ -35,26 +38,47 @@ from titiler.mosaic.errors import MOSAIC_STATUS_CODES from titiler.mosaic.factory import MosaicTilerFactory -try: - pass # type: ignore -except ImportError: - # Try backported to PY<39 `importlib_resources`. - pass # type: ignore - logging.getLogger("botocore.credentials").disabled = True logging.getLogger("botocore.utils").disabled = True logging.getLogger("rio-tiler").setLevel(logging.ERROR) -templates = Jinja2Templates( - directory="", - loader=jinja2.ChoiceLoader([jinja2.PackageLoader(__package__, "templates")]), -) # type:ignore +jinja2_env = jinja2.Environment( + loader=jinja2.ChoiceLoader([jinja2.PackageLoader(__package__, "templates")]) +) +templates = Jinja2Templates(env=jinja2_env) api_settings = ApiSettings() +############################################################################### +# Setup a global API access key, if configured +api_key_query = APIKeyQuery(name="access_token", auto_error=False) + + +def validate_access_token(access_token: str = Security(api_key_query)): + """Validates API key access token, set as the `api_settings.global_access_token` value. + Returns True if no access token is required, or if the access token is valid. + Raises an HTTPException (401) if the access token is required but invalid/missing. + """ + if api_settings.global_access_token is None: + return True + + if not access_token: + raise HTTPException(status_code=401, detail="Missing `access_token`") + + # if access_token == `token` then OK + if access_token != api_settings.global_access_token: + raise HTTPException(status_code=401, detail="Invalid `access_token`") + + return True + + +############################################################################### + app = FastAPI( title=api_settings.name, + openapi_url="/api", + docs_url="/api.html", description="""A modern dynamic tile server built on top of FastAPI and Rasterio/GDAL. --- @@ -67,12 +91,14 @@ """, version=titiler_version, root_path=api_settings.root_path, + dependencies=[Depends(validate_access_token)], ) ############################################################################### # Simple Dataset endpoints (e.g Cloud Optimized GeoTIFF) if not api_settings.disable_cog: cog = TilerFactory( + reader=Reader, router_prefix="/cog", extensions=[ cogValidateExtension(), @@ -81,7 +107,11 @@ ], ) - app.include_router(cog.router, prefix="/cog", tags=["Cloud Optimized GeoTIFF"]) + app.include_router( + cog.router, + prefix="/cog", + tags=["Cloud Optimized GeoTIFF"], + ) ############################################################################### @@ -96,24 +126,45 @@ ) app.include_router( - stac.router, prefix="/stac", tags=["SpatioTemporal Asset Catalog"] + stac.router, + prefix="/stac", + tags=["SpatioTemporal Asset Catalog"], ) ############################################################################### # Mosaic endpoints if not api_settings.disable_mosaic: mosaic = MosaicTilerFactory(router_prefix="/mosaicjson") - app.include_router(mosaic.router, prefix="/mosaicjson", tags=["MosaicJSON"]) + app.include_router( + mosaic.router, + prefix="/mosaicjson", + tags=["MosaicJSON"], + ) ############################################################################### # TileMatrixSets endpoints tms = TMSFactory() -app.include_router(tms.router, tags=["Tiling Schemes"]) +app.include_router( + tms.router, + tags=["Tiling Schemes"], +) ############################################################################### # Algorithms endpoints algorithms = AlgorithmFactory() -app.include_router(algorithms.router, tags=["Algorithms"]) +app.include_router( + algorithms.router, + tags=["Algorithms"], +) + +############################################################################### +# Colormaps endpoints +cmaps = ColorMapFactory() +app.include_router( + cmaps.router, + tags=["ColorMaps"], +) + add_exception_handlers(app, DEFAULT_STATUS_CODES) add_exception_handlers(app, MOSAIC_STATUS_CODES) @@ -124,7 +175,7 @@ CORSMiddleware, allow_origins=api_settings.cors_origins, allow_credentials=True, - allow_methods=["GET"], + allow_methods=api_settings.cors_allow_methods, allow_headers=["*"], ) @@ -138,6 +189,7 @@ "image/jp2", "image/webp", }, + compression_level=6, ) app.add_middleware( @@ -168,9 +220,72 @@ def ping(): @app.get("/", response_class=HTMLResponse, include_in_schema=False) def landing(request: Request): - """TiTiler Landing page""" + """TiTiler landing page.""" + data = { + "title": "titiler", + "links": [ + { + "title": "Landing page", + "href": str(request.url_for("landing")), + "type": "text/html", + "rel": "self", + }, + { + "title": "the API definition (JSON)", + "href": str(request.url_for("openapi")), + "type": "application/vnd.oai.openapi+json;version=3.0", + "rel": "service-desc", + }, + { + "title": "the API documentation", + "href": str(request.url_for("swagger_ui_html")), + "type": "text/html", + "rel": "service-doc", + }, + { + "title": "TiTiler Documentation (external link)", + "href": "https://developmentseed.org/titiler/", + "type": "text/html", + "rel": "doc", + }, + { + "title": "TiTiler source code (external link)", + "href": "https://github.com/developmentseed/titiler", + "type": "text/html", + "rel": "doc", + }, + ], + } + + urlpath = request.url.path + if root_path := request.app.root_path: + urlpath = re.sub(r"^" + root_path, "", urlpath) + crumbs = [] + baseurl = str(request.base_url).rstrip("/") + + crumbpath = str(baseurl) + for crumb in urlpath.split("/"): + crumbpath = crumbpath.rstrip("/") + part = crumb + if part is None or part == "": + part = "Home" + crumbpath += f"/{crumb}" + crumbs.append({"url": crumbpath.rstrip("/"), "part": part.capitalize()}) + return templates.TemplateResponse( - name="index.html", - context={"request": request}, - media_type="text/html", + "index.html", + { + "request": request, + "response": data, + "template": { + "api_root": baseurl, + "params": request.query_params, + "title": "TiTiler", + }, + "crumbs": crumbs, + "url": str(request.url), + "baseurl": baseurl, + "urlpath": str(request.url.path), + "urlparams": str(request.url.query), + }, ) diff --git a/src/titiler/application/titiler/application/settings.py b/src/titiler/application/titiler/application/settings.py index 09fe63175..f914c7ea4 100644 --- a/src/titiler/application/titiler/application/settings.py +++ b/src/titiler/application/titiler/application/settings.py @@ -1,13 +1,17 @@ """Titiler API settings.""" -import pydantic +from typing import Optional +from pydantic import field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict -class ApiSettings(pydantic.BaseSettings): + +class ApiSettings(BaseSettings): """FASTAPI application settings.""" name: str = "TiTiler" cors_origins: str = "*" + cors_allow_methods: str = "GET" cachecontrol: str = "public, max-age=3600" root_path: str = "" debug: bool = False @@ -18,13 +22,19 @@ class ApiSettings(pydantic.BaseSettings): lower_case_query_parameters: bool = False - @pydantic.validator("cors_origins") + # an API key required to access any endpoint, passed via the ?access_token= query parameter + global_access_token: Optional[str] = None + + model_config = SettingsConfigDict( + env_prefix="TITILER_API_", env_file=".env", extra="ignore" + ) + + @field_validator("cors_origins") def parse_cors_origin(cls, v): """Parse CORS origins.""" return [origin.strip() for origin in v.split(",")] - class Config: - """model config""" - - env_file = ".env" - env_prefix = "TITILER_API_" + @field_validator("cors_allow_methods") + def parse_cors_allow_methods(cls, v): + """Parse CORS allowed methods.""" + return [method.strip().upper() for method in v.split(",")] diff --git a/src/titiler/application/titiler/application/templates/index.html b/src/titiler/application/titiler/application/templates/index.html index 70f9c7ea3..d6c013a30 100644 --- a/src/titiler/application/titiler/application/templates/index.html +++ b/src/titiler/application/titiler/application/templates/index.html @@ -1,56 +1,94 @@ - - + + + {{ template.title }} + + + + + + + + + - - - TiTiler - - - - -
-
- ______   __     ______   __     __         ______     ______
-/\__  _\ /\ \   /\__  _\ /\ \   /\ \       /\  ___\   /\  == \
-\/_/\ \/ \ \ \  \/_/\ \/ \ \ \  \ \ \____  \ \  __\   \ \  __<
-   \ \_\  \ \_\    \ \_\  \ \_\  \ \_____\  \ \_____\  \ \_\ \_\
-    \/_/   \/_/     \/_/   \/_/   \/_____/   \/_____/   \/_/ /_/
-            
+
+  ______   __     ______   __     __         ______     ______
+ /\__  _\ /\ \   /\__  _\ /\ \   /\ \       /\  ___\   /\  == \
+ \/_/\ \/ \ \ \  \/_/\ \/ \ \ \  \ \ \____  \ \  __\   \ \  __<
+    \ \_\  \ \_\    \ \_\  \ \_\  \ \_____\  \ \_____\  \ \_\ \_\
+     \/_/   \/_/     \/_/   \/_/   \/_____/   \/_____/   \/_/ /_/
+             
+

+ {{ response.description }} +

-

API documentations: /docs -

-

TiTiler Online documentations: https://developmentseed.org/titiler/ -

-

+

Links

+ -
- Created by - - Development Seed - -
- + + +
+ Created by + + Development Seed + +
+ + diff --git a/src/titiler/core/README.md b/src/titiler/core/README.md index 4a8fa87c1..1ce51547d 100644 --- a/src/titiler/core/README.md +++ b/src/titiler/core/README.md @@ -5,14 +5,14 @@ Core of Titiler's application. Contains blocks to create dynamic tile servers. ## Installation ```bash -$ pip install -U pip +$ python -m pip install -U pip # From Pypi -$ pip install titiler.core +$ python -m pip install titiler.core # Or from sources $ git clone https://github.com/developmentseed/titiler.git -$ cd titiler && pip install -e titiler/core +$ cd titiler && python -m pip install -e src/titiler/core ``` ## How To @@ -42,6 +42,10 @@ titiler/ └── core/ ├── tests/ - Tests suite └── titiler/core/ - `core` namespace package + ├── algorithm/ + | ├── base.py - ABC Base Class for custom algorithms + | ├── dem.py - Elevation data related algorithms + | └── index.py - Simple band index algorithms ├── models/ | ├── response.py - Titiler's response models | ├── mapbox.py - Mapbox TileJSON pydantic model @@ -50,9 +54,11 @@ titiler/ | ├── enums.py - Titiler's enumerations (e.g MediaType) | └── responses.py - Custom Starlette's responses ├── templates/ - | └── wmts.xml - OGC WMTS template + | ├── map.html - Simple Map viewer (built with leaflet) + | └── wmts.xml - OGC WMTS document template ├── dependencies.py - Titiler FastAPI's dependencies ├── errors.py - Errors handler factory + ├── middleware.py - Starlette middlewares ├── factory.py - Dynamic tiler endpoints factories ├── routing.py - Custom APIRoute class └── utils.py - Titiler utility functions diff --git a/src/titiler/core/pyproject.toml b/src/titiler/core/pyproject.toml index f964c014a..028b6bf63 100644 --- a/src/titiler/core/pyproject.toml +++ b/src/titiler/core/pyproject.toml @@ -21,23 +21,26 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] dynamic = ["version"] dependencies = [ - "fastapi>=0.87.0,<0.95", - "geojson-pydantic", + "fastapi>=0.108.0", + "geojson-pydantic>=1.1.2,<2.0", "jinja2>=2.11.2,<4.0.0", "numpy", - "pydantic", + "pydantic~=2.0", "rasterio", - "rio-tiler>=4.1.6,<4.2", + "rio-tiler>=7.0,<8.0", + "morecantile", "simplejson", - "typing_extensions;python_version<'3.8'", + "typing_extensions>=4.6.1", ] [project.optional-dependencies] diff --git a/src/titiler/core/tests/fixtures/cog_dateline.tif b/src/titiler/core/tests/fixtures/cog_dateline.tif new file mode 100644 index 000000000..279183018 Binary files /dev/null and b/src/titiler/core/tests/fixtures/cog_dateline.tif differ diff --git a/src/titiler/core/tests/fixtures/item.json b/src/titiler/core/tests/fixtures/item.json index f8e447265..8a20a9ed5 100644 --- a/src/titiler/core/tests/fixtures/item.json +++ b/src/titiler/core/tests/fixtures/item.json @@ -1,18 +1,10 @@ { "type": "Feature", - "stac_version": "1.0.0-beta.1", - "stac_extensions": [ - "eo", - "view", - "proj" - ], + "stac_version": "1.0.0", "id": "S2A_34SGA_20200318_0_L2A", - "bbox": [ - 23.293255090449595, - 31.505183020453355, - 24.296453548295318, - 32.51147809805106 - ], + "properties": { + "datetime": "2020-03-18T09:11:33Z" + }, "geometry": { "type": "Polygon", "coordinates": [ @@ -40,91 +32,31 @@ ] ] }, - "properties": { - "datetime": "2020-03-18T09:11:33Z", - "platform": "sentinel-2a", - "constellation": "sentinel-2", - "instruments": [ - "msi" - ], - "gsd": 10, - "data_coverage": 73.85, - "view:off_nadir": 0, - "eo:cloud_cover": 89.84, - "proj:epsg": 32634, - "sentinel:latitude_band": "S", - "sentinel:grid_square": "GA", - "sentinel:sequence": "0", - "sentinel:product_id": "S2A_MSIL2A_20200318T085701_N0214_R007_T34SGA_20200318T115254", - "created": "2020-05-12T21:03:26.671Z", - "updated": "2020-05-12T21:03:26.671Z" - }, - "collection": "sentinel-s2-l2a-cogs", + "links": [ + { + "rel": "self", + "href": "https://myurl.com/item.json", + "type": "application/json" + } + ], "assets": { "B01": { - "title": "Band 1 (coastal)", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", "href": "https://myurl.com/B01.tif", - "proj:shape": [ - 1830, - 1830 - ], - "proj:transform": [ - 60, - 0, - 699960, - 0, - -60, - 3600000, - 0, - 0, - 1 - ] + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 1 (coastal)" }, "B09": { - "title": "Band 9", - "type": "image/tiff; application=geotiff; profile=cloud-optimized", "href": "https://myurl.com/B09.tif", - "proj:shape": [ - 1830, - 1830 - ], - "proj:transform": [ - 60, - 0, - 699960, - 0, - -60, - 3600000, - 0, - 0, - 1 - ] + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 9" } }, - "links": [ - { - "rel": "self", - "href": "s3://sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_34SGA_20200318_0_L2A/S2A_34SGA_20200318_0_L2A.json", - "type": "application/json" - }, - { - "rel": "parent", - "href": "https://myurl.com/v0/collections/sentinel-s2-l2a" - }, - { - "rel": "collection", - "href": "https://myurl.com/v0/collections/sentinel-s2-l2a" - }, - { - "rel": "root", - "href": "https://myurl.com/v0/" - }, - { - "title": "Source STAC Item", - "rel": "derived_from", - "href": "https://myurl.com/collections/sentinel-s2-l2a/items/S2A_34SGA_20200318_0_L2A", - "type": "application/json" - } - ] + "bbox": [ + 23.293255090449595, + 31.505183020453355, + 24.296453548295318, + 32.51147809805106 + ], + "stac_extensions": [], + "collection": "sentinel-s2-l2a-cogs" } diff --git a/src/titiler/core/tests/test_CustomCmap.py b/src/titiler/core/tests/test_CustomCmap.py index 0c189247a..8b396aaf7 100644 --- a/src/titiler/core/tests/test_CustomCmap.py +++ b/src/titiler/core/tests/test_CustomCmap.py @@ -1,14 +1,13 @@ """Test TiTiler Custom Colormap Params.""" -from enum import Enum from io import BytesIO -from typing import Dict, Optional import numpy -from fastapi import FastAPI, Query +from fastapi import FastAPI from rio_tiler.colormap import ColorMaps from starlette.testclient import TestClient +from titiler.core.dependencies import create_colormap_dependency from titiler.core.factory import TilerFactory from .conftest import DATA_DIR @@ -17,19 +16,8 @@ "cmap1": {6: (4, 5, 6, 255)}, } cmap = ColorMaps(data=cmap_values) -ColorMapName = Enum( # type: ignore - "ColorMapName", [(a, a) for a in sorted(cmap.list())] -) - -def ColorMapParams( - colormap_name: ColorMapName = Query(None, description="Colormap name"), -) -> Optional[Dict]: - """Colormap Dependency.""" - if colormap_name: - return cmap.get(colormap_name.value) - - return None +ColorMapParams = create_colormap_dependency(cmap) def test_CustomCmap(): diff --git a/src/titiler/core/tests/test_CustomPath.py b/src/titiler/core/tests/test_CustomPath.py index cb1bd3298..dd308d630 100644 --- a/src/titiler/core/tests/test_CustomPath.py +++ b/src/titiler/core/tests/test_CustomPath.py @@ -5,6 +5,7 @@ from fastapi import FastAPI, HTTPException, Query from starlette.testclient import TestClient +from typing_extensions import Annotated from titiler.core.factory import TilerFactory @@ -12,11 +13,13 @@ def CustomPathParams( - name: str = Query( - ..., - alias="file", - description="Give me a url.", - ) + name: Annotated[ + str, + Query( + alias="file", + description="Give me a url.", + ), + ], ) -> str: """Custom path Dependency.""" if not re.match(".+tif$", name): diff --git a/src/titiler/core/tests/test_CustomRender.py b/src/titiler/core/tests/test_CustomRender.py index 2d9661e2a..bf039ce40 100644 --- a/src/titiler/core/tests/test_CustomRender.py +++ b/src/titiler/core/tests/test_CustomRender.py @@ -41,7 +41,7 @@ def test_CustomRender(): app.include_router(cog.router) client = TestClient(app) - response = client.get(f"/tiles/8/87/48.tif?url={DATA_DIR}/cog.tif") + response = client.get(f"/tiles/WebMercatorQuad/8/87/48.tif?url={DATA_DIR}/cog.tif") assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" meta = parse_img(response.content) @@ -51,7 +51,7 @@ def test_CustomRender(): assert not meta.get("compress") response = client.get( - f"/tiles/8/87/48.tif?url={DATA_DIR}/cog.tif&return_mask=false&output_nodata=0&output_compression=deflate" + f"/tiles/WebMercatorQuad/8/87/48.tif?url={DATA_DIR}/cog.tif&return_mask=false&output_nodata=0&output_compression=deflate" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" @@ -62,7 +62,7 @@ def test_CustomRender(): assert meta["compress"] == "deflate" response = client.get( - f"/tiles/9/289/207?url={DATA_DIR}/TCI.tif&rescale=0,1000&rescale=0,2000&rescale=0,3000" + f"/tiles/WebMercatorQuad/9/289/207?url={DATA_DIR}/TCI.tif&rescale=0,1000&rescale=0,2000&rescale=0,3000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" diff --git a/src/titiler/core/tests/test_algorithms.py b/src/titiler/core/tests/test_algorithms.py index ca4e95a59..35c8de484 100644 --- a/src/titiler/core/tests/test_algorithms.py +++ b/src/titiler/core/tests/test_algorithms.py @@ -22,12 +22,11 @@ class Multiply(BaseAlgorithm): def __call__(self, img: ImageData) -> ImageData: """Apply Multiplication factor.""" # Multiply image data bcy factor - data = img.data * self.factor + data = img.array * self.factor # Create output ImageData return ImageData( data, - img.mask, assets=img.assets, crs=img.crs, bounds=img.bounds, @@ -88,7 +87,6 @@ def main(algorithm=Depends(default_algorithms.dependency)): # MAPBOX Terrain RGB response = client.get("/", params={"algorithm": "terrainrgb"}) assert response.status_code == 200 - with MemoryFile(response.content) as mem: with mem.open() as dst: data = dst.read().astype(numpy.float64) @@ -100,7 +98,6 @@ def main(algorithm=Depends(default_algorithms.dependency)): # TILEZEN Terrarium response = client.get("/", params={"algorithm": "terrarium"}) assert response.status_code == 200 - with MemoryFile(response.content) as mem: with mem.open() as dst: data = dst.read().astype(numpy.float64) @@ -108,3 +105,133 @@ def main(algorithm=Depends(default_algorithms.dependency)): # https://github.com/tilezen/joerd/blob/master/docs/formats.md#terrarium elevation = (data[0] * 256 + data[1] + data[2] / 256) - 32768 numpy.testing.assert_array_equal(elevation, arr[0]) + + +def test_normalized_index(): + """test ndi.""" + algo = default_algorithms.get("normalizedIndex")() + + arr = numpy.zeros((2, 256, 256), dtype="uint16") + arr[0, :, :] = 1 + arr[1, :, :] = 2 + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (1, 256, 256) + assert out.array.dtype == "float32" + assert len(numpy.unique(out.array).tolist()) == 1 + numpy.testing.assert_almost_equal(out.array[0, 0, 0], 0.3333, decimal=3) + + # with mixed 0 and masked + arr = numpy.ma.MaskedArray( + numpy.zeros((2, 256, 256), dtype="uint16"), + mask=numpy.zeros((2, 256, 256), dtype="bool"), + ) + arr.data[0, :, :] = 1 + arr.data[0, 0:10, 0:10] = 0 + arr.mask[0, 0:5, 0:5] = True + + arr.data[1, :, :] = 2 + arr.data[1, 0:10, 0:10] = 0 + arr.mask[1, 0:5, 0:5] = True + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (1, 256, 256) + assert out.array.dtype == "float32" + assert len(numpy.unique(out.array).tolist()) == 2 # 0.33 and None + assert out.array[0, 0, 0] is numpy.ma.masked + assert out.array[0, 6, 6] is numpy.ma.masked + numpy.testing.assert_almost_equal(out.array[0, 10, 10], 0.3333, decimal=3) + + +def test_hillshade(): + """test hillshade.""" + algo = default_algorithms.get("hillshade")() + + arr = numpy.random.randint(0, 5000, (1, 262, 262), dtype="uint16") + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (1, 256, 256) + assert out.array.dtype == "uint8" + + arr = numpy.ma.MaskedArray( + numpy.random.randint(0, 5000, (1, 262, 262), dtype="uint16"), + mask=numpy.zeros((1, 262, 262), dtype="bool"), + ) + arr.mask[0, 0:100, 0:100] = True + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (1, 256, 256) + assert out.array.dtype == "uint8" + assert out.array[0, 0, 0] is numpy.ma.masked + + +def test_contours(): + """test contours.""" + algo = default_algorithms.get("contours")() + + arr = numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16") + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + + arr = numpy.ma.MaskedArray( + numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16"), + mask=numpy.zeros((1, 256, 256), dtype="bool"), + ) + arr.mask[0, 0:100, 0:100] = True + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + assert out.array[0, 0, 0] is numpy.ma.masked + + +def test_terrarium(): + """test terrarium.""" + algo = default_algorithms.get("terrarium")() + + arr = numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16") + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + + arr = numpy.ma.MaskedArray( + numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16"), + mask=numpy.zeros((1, 256, 256), dtype="bool"), + ) + arr.mask[0, 0:100, 0:100] = True + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + assert out.array[0, 0, 0] is numpy.ma.masked + + +def test_terrainrgb(): + """test terrainrgb.""" + algo = default_algorithms.get("terrainrgb")() + + arr = numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16") + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + + arr = numpy.ma.MaskedArray( + numpy.random.randint(0, 5000, (1, 256, 256), dtype="uint16"), + mask=numpy.zeros((1, 256, 256), dtype="bool"), + ) + arr.mask[0, 0:100, 0:100] = True + + img = ImageData(arr) + out = algo(img) + assert out.array.shape == (3, 256, 256) + assert out.array.dtype == "uint8" + assert out.array[0, 0, 0] is numpy.ma.masked diff --git a/src/titiler/core/tests/test_cache_middleware.py b/src/titiler/core/tests/test_cache_middleware.py index b1331edd9..f5489da26 100644 --- a/src/titiler/core/tests/test_cache_middleware.py +++ b/src/titiler/core/tests/test_cache_middleware.py @@ -4,6 +4,7 @@ from fastapi import FastAPI, Path from starlette.responses import Response from starlette.testclient import TestClient +from typing_extensions import Annotated from titiler.core.middleware import CacheControlMiddleware @@ -29,18 +30,18 @@ async def route3(): @app.get("/tiles/{z}/{x}/{y}") async def tiles( - z: int = Path(..., ge=0, le=30, description="Mercator tiles's zoom level"), - x: int = Path(..., description="Mercator tiles's column"), - y: int = Path(..., description="Mercator tiles's row"), + z: Annotated[int, Path(ge=0, le=30, description="Mercator tiles's zoom level")], + x: Annotated[int, Path(description="Mercator tiles's column")], + y: Annotated[int, Path(description="Mercator tiles's row")], ): """tiles.""" return "yeah" @app.get("/emptytiles/{z}/{x}/{y}") async def emptytiles( - z: int = Path(..., ge=0, le=30, description="Mercator tiles's zoom level"), - x: int = Path(..., description="Mercator tiles's column"), - y: int = Path(..., description="Mercator tiles's row"), + z: Annotated[int, Path(ge=0, le=30, description="Mercator tiles's zoom level")], + x: Annotated[int, Path(description="Mercator tiles's column")], + y: Annotated[int, Path(description="Mercator tiles's row")], ): """tiles.""" return Response(status_code=404) diff --git a/src/titiler/core/tests/test_case_middleware.py b/src/titiler/core/tests/test_case_middleware.py index dc46ab5ae..0a13abe7f 100644 --- a/src/titiler/core/tests/test_case_middleware.py +++ b/src/titiler/core/tests/test_case_middleware.py @@ -4,6 +4,7 @@ from fastapi import FastAPI, Query from starlette.testclient import TestClient +from typing_extensions import Annotated from titiler.core.middleware import LowerCaseQueryStringMiddleware @@ -13,7 +14,7 @@ def test_lowercase_middleware(): app = FastAPI() @app.get("/route1") - async def route1(value: str = Query(...)): + async def route1(value: Annotated[str, Query()]): """route1.""" return {"value": value} @@ -33,7 +34,7 @@ def test_lowercase_middleware_multiple_values(): app = FastAPI() @app.get("/route1") - async def route1(value: List[str] = Query(...)): + async def route1(value: Annotated[List[str], Query()]): """route1.""" return {"value": value} @@ -46,3 +47,26 @@ async def route1(value: List[str] = Query(...)): response = client.get("/route1?VALUE=lorenzori&VALUE=dogs&value=trucks") assert response.json() == {"value": ["lorenzori", "dogs", "trucks"]} + + +def test_lowercase_middleware_url_with_query_parameters(): + """Make sure all query parameters return.""" + app = FastAPI() + + @app.get("/route1") + async def route1(url: List[str] = Query(...)): + """route1.""" + return {"url": url} + + app.add_middleware(LowerCaseQueryStringMiddleware) + + client = TestClient(app) + + url = "https://developmentseed.org?solutions=geospatial&planet=better" + url_encoded = ( + "https%3A%2F%2Fdevelopmentseed.org%3Fsolutions%3Dgeospatial%26planet%3Dbetter" + ) + + response = client.get(f"/route1?url={url_encoded}") + + assert response.json() == {"url": [url]} diff --git a/src/titiler/core/tests/test_dependencies.py b/src/titiler/core/tests/test_dependencies.py index 6254aa471..bfcd9069a 100644 --- a/src/titiler/core/tests/test_dependencies.py +++ b/src/titiler/core/tests/test_dependencies.py @@ -5,10 +5,11 @@ from typing import Literal import pytest -from fastapi import Depends, FastAPI, Query +from fastapi import Depends, FastAPI, Path from morecantile import tms from rio_tiler.types import ColorMapType from starlette.testclient import TestClient +from typing_extensions import Annotated from titiler.core import dependencies, errors from titiler.core.resources.responses import JSONResponse @@ -18,15 +19,20 @@ def test_tms(): """Create App.""" app = FastAPI() - @app.get("/web/{TileMatrixSetId}") - def web(TileMatrixSetId: Literal["WebMercatorQuad"] = Query(...)): + @app.get("/web/{tileMatrixSetId}") + def web( + tileMatrixSetId: Annotated[ + Literal["WebMercatorQuad"], + Path(), + ], + ): """return tms id.""" - return TileMatrixSetId + return tileMatrixSetId - @app.get("/all/{TileMatrixSetId}") - def all(TileMatrixSetId: Literal[tuple(tms.list())] = Query(...)): + @app.get("/all/{tileMatrixSetId}") + def all(tileMatrixSetId: Annotated[Literal[tuple(tms.list())], Path()]): """return tms id.""" - return TileMatrixSetId + return tileMatrixSetId client = TestClient(app) response = client.get("/web/WebMercatorQuad") @@ -34,7 +40,7 @@ def all(TileMatrixSetId: Literal[tuple(tms.list())] = Query(...)): response = client.get("/web/WorldCRS84Quad") assert response.status_code == 422 - assert "permitted: 'WebMercatorQuad'" in response.json()["detail"][0]["msg"] + assert "Input should be 'WebMercatorQuad'" in response.json()["detail"][0]["msg"] response = client.get("/all/WebMercatorQuad") assert response.json() == "WebMercatorQuad" @@ -60,7 +66,7 @@ def main(cm=Depends(dependencies.ColorMapParams)): assert response.json()["1"] == [68, 2, 85, 255] cmap = json.dumps({1: [68, 1, 84, 255]}) - response = client.get(f"/?colormap={cmap}") + response = client.get("/", params={"colormap": cmap}) assert response.json()["1"] == [68, 1, 84, 255] cmap = json.dumps({0: "#000000", 255: "#ffffff"}) @@ -75,7 +81,7 @@ def main(cm=Depends(dependencies.ColorMapParams)): ([3, 1000], [255, 0, 0, 255]), ] cmap = json.dumps(intervals) - response = client.get(f"/?colormap={cmap}") + response = client.get("/", params={"colormap": cmap}) assert response.json()[0] == [[1, 2], [0, 0, 0, 255]] assert response.json()[1] == [[2, 3], [255, 255, 255, 255]] assert response.json()[2] == [[3, 1000], [255, 0, 0, 255]] @@ -240,6 +246,18 @@ def _assets_bidx(params=Depends(dependencies.AssetsBidxParams)): response = client.get("/third?assets=data&assets=image") assert response.json()["assets"] == ["data", "image"] + response = client.get( + "/third", + params=( + ("assets", "data"), + ("assets", "image"), + ), + ) + assert response.json()["assets"] == ["data", "image"] + + response = client.get("/third", params={"assets": ["data", "image"]}) + assert response.json()["assets"] == ["data", "image"] + response = client.get("/third") assert not response.json()["assets"] @@ -249,12 +267,37 @@ def _assets_bidx(params=Depends(dependencies.AssetsBidxParams)): assert response.json()["assets"] == ["data", "image"] assert response.json()["asset_indexes"] == {"data": [1, 2, 3], "image": [1]} + response = client.get( + "/third", + params=( + ("assets", "data"), + ("assets", "image"), + ("asset_bidx", "data|1,2,3"), + ("asset_bidx", "image|1"), + ), + ) + + assert response.json()["assets"] == ["data", "image"] + assert response.json()["asset_indexes"] == {"data": [1, 2, 3], "image": [1]} + response = client.get( "/third?assets=data&assets=image&asset_expression=data|b1/b2&asset_expression=image|b1*b2" ) assert response.json()["assets"] == ["data", "image"] assert response.json()["asset_expression"] == {"data": "b1/b2", "image": "b1*b2"} + response = client.get( + "/third", + params=( + ("assets", "data"), + ("assets", "image"), + ("asset_expression", "data|b1/b2"), + ("asset_expression", "image|b1*b2"), + ), + ) + assert response.json()["assets"] == ["data", "image"] + assert response.json()["asset_expression"] == {"data": "b1/b2", "image": "b1*b2"} + def test_bands(): """test bands deps.""" @@ -302,33 +345,58 @@ def _bands_expr_opt(params=Depends(dependencies.BandsExprParamsOptional)): assert not response.json()["bands"] -def test_image(): - """test image deps.""" +def test_preview_part_params(): + """test preview/part deps.""" app = FastAPI() - @app.get("/") - def _endpoint(params=Depends(dependencies.ImageParams)): + @app.get("/preview") + def _endpoint(params=Depends(dependencies.PreviewParams)): + """return params.""" + return params + + @app.get("/part") + def _endpoint(params=Depends(dependencies.PartFeatureParams)): """return params.""" return params client = TestClient(app) - response = client.get("/") + response = client.get("/preview") assert response.json()["max_size"] == 1024 assert not response.json()["height"] assert not response.json()["width"] - response = client.get("/?max_size=2048") + response = client.get("/preview?max_size=2048") assert response.json()["max_size"] == 2048 assert not response.json()["height"] assert not response.json()["width"] - response = client.get("/?width=128") + response = client.get("/preview?width=128") assert response.json()["max_size"] == 1024 assert not response.json()["height"] assert response.json()["width"] == 128 - response = client.get("/?width=128&height=128") + response = client.get("/preview?width=128&height=128") + assert not response.json()["max_size"] + assert response.json()["height"] == 128 + assert response.json()["width"] == 128 + + response = client.get("/part") + assert not response.json()["max_size"] + assert not response.json()["height"] + assert not response.json()["width"] + + response = client.get("/part?max_size=2048") + assert response.json()["max_size"] == 2048 + assert not response.json()["height"] + assert not response.json()["width"] + + response = client.get("/part?width=128") + assert not response.json()["max_size"] + assert not response.json()["height"] + assert response.json()["width"] == 128 + + response = client.get("/part?width=128&height=128") assert not response.json()["max_size"] assert response.json()["height"] == 128 assert response.json()["width"] == 128 @@ -353,7 +421,7 @@ def is_nan(params=Depends(dependencies.DatasetParams)): response = client.get("/") assert not response.json()["nodata"] assert not response.json()["unscale"] - assert response.json()["resampling_method"] == "nearest" + assert not response.json()["resampling_method"] response = client.get("/?resampling=cubic") assert not response.json()["nodata"] @@ -379,7 +447,7 @@ def _endpoint(params=Depends(dependencies.ImageRenderingParams)): client = TestClient(app) response = client.get("/") - assert response.json()["add_mask"] is True + assert not response.json()["add_mask"] response = client.get("/?return_mask=False") assert response.json()["add_mask"] is False @@ -401,7 +469,7 @@ def test_algo(): def _endpoint(algorithm=Depends(PostProcessParams)): """return params.""" if algorithm: - return algorithm.dict() + return algorithm.model_dump() return {} client = TestClient(app) @@ -412,7 +480,7 @@ def _endpoint(algorithm=Depends(PostProcessParams)): assert response.status_code == 422 response = client.get("/?algorithm=hillshade") - assert response.json()["azimuth"] == 90 + assert response.json()["azimuth"] == 45 assert response.json()["buffer"] == 3 assert response.json()["input_nbands"] == 1 @@ -426,3 +494,103 @@ def _endpoint(algorithm=Depends(PostProcessParams)): assert response.json()["azimuth"] == 30 assert response.json()["buffer"] == 4 assert response.json()["input_nbands"] == 1 + + +def test_rescale_params(): + """test RescalingParams dependency.""" + app = FastAPI() + + @app.get("/") + def main(rescale=Depends(dependencies.RescalingParams)): + """return rescale.""" + return rescale + + client = TestClient(app) + + response = client.get("/", params={"rescale": "0,1"}) + assert response.status_code == 200 + assert response.json() == [[0, 1]] + + response = client.get("/?rescale=0,1") + assert response.status_code == 200 + assert response.json() == [[0, 1]] + + response = client.get("/?rescale=0,1&rescale=2,3") + assert response.status_code == 200 + assert response.json() == [[0, 1], [2, 3]] + + with pytest.raises(AssertionError): + client.get("/", params={"rescale": [0, 1]}) + + response = client.get("/", params={"rescale": [[0, 1]]}) + assert response.status_code == 200 + assert response.json() == [[0, 1]] + + response = client.get( + "/", + params=( + ("rescale", [0, 1]), + ("rescale", [0, 1]), + ), + ) + assert response.status_code == 200 + assert response.json() == [[0, 1], [0, 1]] + + response = client.get( + "/", + params=( + ("rescale", "0,1"), + ("rescale", "0,1"), + ), + ) + assert response.status_code == 200 + assert response.json() == [[0, 1], [0, 1]] + + response = client.get("/", params={"rescale": [[0, 1], [2, 3]]}) + assert response.status_code == 200 + assert response.json() == [[0, 1], [2, 3]] + + +def test_histogram_params(): + """Test HistogramParams dependency.""" + app = FastAPI() + + @app.get("/") + def main(params=Depends(dependencies.HistogramParams)): + """return rescale.""" + return params + + client = TestClient(app) + + response = client.get( + "/", + params={"histogram_bins": "8"}, + ) + assert response.status_code == 200 + assert response.json()["bins"] == 8 + + response = client.get( + "/", + params={"histogram_bins": "8,9"}, + ) + assert response.status_code == 200 + assert response.json()["bins"] == [8.0, 9.0] + + response = client.get( + "/", + ) + assert response.status_code == 200 + assert response.json()["bins"] == 10 + + response = client.get( + "/", + params={"histogram_range": "8,9"}, + ) + assert response.status_code == 200 + assert response.json()["range"] == [8.0, 9.0] + + with pytest.raises(AssertionError): + client.get( + "/", + params={"histogram_range": "8"}, + ) diff --git a/src/titiler/core/tests/test_factories.py b/src/titiler/core/tests/test_factories.py index 408cf96a9..e40cad4fe 100644 --- a/src/titiler/core/tests/test_factories.py +++ b/src/titiler/core/tests/test_factories.py @@ -3,10 +3,11 @@ import json import os import pathlib -from dataclasses import dataclass +import warnings +import xml.etree.ElementTree as ET from enum import Enum from io import BytesIO -from typing import Dict, Optional, Type +from typing import Dict, Optional, Sequence, Type from unittest.mock import patch from urllib.parse import urlencode @@ -14,10 +15,14 @@ import httpx import morecantile import numpy +import pytest +from attrs import define from fastapi import Depends, FastAPI, HTTPException, Path, Query, security, status from morecantile.defaults import TileMatrixSets from rasterio.crs import CRS from rasterio.io import MemoryFile +from rio_tiler.colormap import cmap as default_cmap +from rio_tiler.errors import NoOverviewWarning from rio_tiler.io import BaseReader, MultiBandReader, Reader, STACReader from starlette.requests import Request from starlette.testclient import TestClient @@ -26,7 +31,8 @@ from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers from titiler.core.factory import ( AlgorithmFactory, - BaseTilerFactory, + BaseFactory, + ColorMapFactory, MultiBandTilerFactory, MultiBaseTilerFactory, TilerFactory, @@ -43,7 +49,7 @@ def test_TilerFactory(): """Test TilerFactory class.""" cog = TilerFactory() - assert len(cog.router.routes) == 27 + assert len(cog.router.routes) == 22 assert len(cog.supported_tms.list()) == NB_DEFAULT_TMS cog = TilerFactory(router_prefix="something", supported_tms=WEB_TMS) @@ -53,7 +59,15 @@ def test_TilerFactory(): app.include_router(cog.router, prefix="/something") client = TestClient(app) - response = client.get(f"/something/tilejson.json?url={DATA_DIR}/cog.tif") + response = client.get("/openapi.json") + assert response.status_code == 200 + + response = client.get("/docs") + assert response.status_code == 200 + + response = client.get( + f"/something/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] @@ -62,7 +76,7 @@ def test_TilerFactory(): assert response.status_code == 422 cog = TilerFactory(add_preview=False, add_part=False, add_viewer=False) - assert len(cog.router.routes) == 18 + assert len(cog.router.routes) == 14 app = FastAPI() cog = TilerFactory() @@ -72,17 +86,19 @@ def test_TilerFactory(): client = TestClient(app) - response = client.get(f"/tiles/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000") + response = client.get( + f"/tiles/WebMercatorQuad/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000" + ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" response = client.get( - f"/tiles/8/87/48?url={DATA_DIR}/cog.tif&rescale=-3.4028235e+38,3.4028235e+38" + f"/tiles/WebMercatorQuad/8/87/48?url={DATA_DIR}/cog.tif&rescale=-3.4028235e+38,3.4028235e+38" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" response = client.get( - f"/tiles/8/87/48.tif?url={DATA_DIR}/cog.tif&bidx=1&bidx=1&bidx=1&return_mask=false" + f"/tiles/WebMercatorQuad/8/87/48.tif?url={DATA_DIR}/cog.tif&bidx=1&bidx=1&bidx=1&return_mask=false" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" @@ -93,7 +109,7 @@ def test_TilerFactory(): assert meta["height"] == 256 response = client.get( - "/tiles/8/87/48.tif", + "/tiles/WebMercatorQuad/8/87/48.tif", params={ "url": f"{DATA_DIR}/cog.tif", "expression": "b1;b1;b1", @@ -109,14 +125,17 @@ def test_TilerFactory(): assert meta["height"] == 256 response = client.get( - f"/tiles/8/84/47?url={DATA_DIR}/cog.tif&bidx=1&rescale=0,1000&colormap_name=viridis" + f"/tiles/WebMercatorQuad/8/84/47?url={DATA_DIR}/cog.tif&bidx=1&rescale=0,1000&colormap_name=viridis" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" # Dict - cmap = urlencode( - { + response = client.get( + "/tiles/WebMercatorQuad/8/84/47.png", + params={ + "url": f"{DATA_DIR}/cog.tif", + "bidx": 1, "colormap": json.dumps( { "1": [58, 102, 24, 255], @@ -124,16 +143,18 @@ def test_TilerFactory(): "3": "#b1b129", "4": "#ddcb9aFF", } - ) - } + ), + }, ) - response = client.get(f"/tiles/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}") assert response.status_code == 200 assert response.headers["content-type"] == "image/png" # Intervals - cmap = urlencode( - { + response = client.get( + "/tiles/WebMercatorQuad/8/84/47.png", + params={ + "url": f"{DATA_DIR}/cog.tif", + "bidx": 1, "colormap": json.dumps( [ # ([min, max], [r, g, b, a]) @@ -141,32 +162,37 @@ def test_TilerFactory(): ([2, 3], [255, 255, 255, 255]), ([3, 1000], [255, 0, 0, 255]), ] - ) - } + ), + }, ) - response = client.get(f"/tiles/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}") assert response.status_code == 200 assert response.headers["content-type"] == "image/png" # Bad colormap format cmap = urlencode({"colormap": json.dumps({"1": [58, 102]})}) - response = client.get(f"/tiles/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}") + response = client.get( + f"/tiles/WebMercatorQuad/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}" + ) assert response.status_code == 400 # no json encoding cmap = urlencode({"colormap": {"1": [58, 102]}}) - response = client.get(f"/tiles/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}") + response = client.get( + f"/tiles/WebMercatorQuad/8/84/47.png?url={DATA_DIR}/cog.tif&bidx=1&{cmap}" + ) assert response.status_code == 400 # Test NumpyTile - response = client.get(f"/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif") + response = client.get(f"/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif") assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" npy_tile = numpy.load(BytesIO(response.content)) assert npy_tile.shape == (2, 256, 256) # mask + data # Test Buffer - response = client.get(f"/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif&buffer=10") + response = client.get( + f"/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif&buffer=10" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" npy_tile = numpy.load(BytesIO(response.content)) @@ -179,7 +205,7 @@ def test_TilerFactory(): assert response.headers["content-type"] == "image/jpeg" response = client.get( - f"/crop/-56.228,72.715,-54.547,73.188.png?url={DATA_DIR}/cog.tif&rescale=0,1000&max_size=256" + f"/bbox/-56.228,72.715,-54.547,73.188.png?url={DATA_DIR}/cog.tif&rescale=0,1000&max_size=256" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" @@ -191,7 +217,7 @@ def test_TilerFactory(): assert response.json()["band_names"] == ["b1"] response = client.get( - f"/point/-6259272.328324187,12015838.020930404?url={DATA_DIR}/cog.tif&coord-crs=EPSG:3857" + f"/point/-6259272.328324187,12015838.020930404?url={DATA_DIR}/cog.tif&coord_crs=EPSG:3857" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/json" @@ -208,7 +234,7 @@ def test_TilerFactory(): assert len(response.json()["values"]) == 1 assert response.json()["band_names"] == ["b1*2"] - response = client.get(f"/tilejson.json?url={DATA_DIR}/cog.tif") + response = client.get(f"/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif") assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] @@ -218,18 +244,17 @@ def test_TilerFactory(): assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] - response_qs = client.get( - f"/tilejson.json?url={DATA_DIR}/cog.tif&TileMatrixSetId=WorldCRS84Quad" + response = client.get( + f"/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif&tile_format=png" ) - assert response.json()["tiles"] == response_qs.json()["tiles"] - - response = client.get(f"/tilejson.json?url={DATA_DIR}/cog.tif&tile_format=png") assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] assert "png" in response.json()["tiles"][0] - response = client.get(f"/tilejson.json?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12") + response = client.get( + f"/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] @@ -237,13 +262,26 @@ def test_TilerFactory(): assert response.json()["maxzoom"] == 12 response = client.get( - f"/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12" + f"/WebMercatorQuad/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/xml" + meta = parse_img(response.content) + assert meta["driver"] == "WMTS" + assert meta["crs"] == "EPSG:3857" + root = ET.fromstring(response.content) + assert root + + response = client.get( + f"/WebMercatorQuad/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&bdix=1&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/xml" meta = parse_img(response.content) assert meta["driver"] == "WMTS" assert meta["crs"] == "EPSG:3857" + root = ET.fromstring(response.content) + assert root response = client.get( f"/WorldCRS84Quad/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12" @@ -253,11 +291,14 @@ def test_TilerFactory(): meta = parse_img(response.content) assert meta["driver"] == "WMTS" assert str(meta["crs"]) == "OGC:CRS84" + root = ET.fromstring(response.content) + assert root response = client.get(f"/bounds?url={DATA_DIR}/cog.tif") assert response.status_code == 200 assert response.headers["content-type"] == "application/json" - assert response.json()["bounds"] + assert len(response.json()["bounds"]) == 4 + assert response.json()["crs"] response = client.get(f"/info?url={DATA_DIR}/cog.tif") assert response.status_code == 200 @@ -268,6 +309,15 @@ def test_TilerFactory(): assert response.status_code == 200 assert response.headers["content-type"] == "application/geo+json" assert response.json()["type"] == "Feature" + assert "bbox" in response.json() + assert response.json()["geometry"]["type"] == "Polygon" + + response = client.get(f"/info.geojson?url={DATA_DIR}/cog_dateline.tif") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + assert response.json()["type"] == "Feature" + assert "bbox" in response.json() + assert response.json()["geometry"]["type"] == "MultiPolygon" response = client.get( f"/preview.png?url={DATA_DIR}/cog.tif&rescale=0,1000&max_size=256" @@ -345,18 +395,20 @@ def test_TilerFactory(): feature_collection = {"type": "FeatureCollection", "features": [feature]} - response = client.post(f"/crop?url={DATA_DIR}/cog.tif", json=feature) + response = client.post(f"/feature?url={DATA_DIR}/cog.tif", json=feature) assert response.status_code == 200 assert response.headers["content-type"] == "image/png" - response = client.post(f"/crop.tif?url={DATA_DIR}/cog.tif", json=feature) + response = client.post(f"/feature.tif?url={DATA_DIR}/cog.tif", json=feature) assert response.status_code == 200 assert response.headers["content-type"] == "image/tiff; application=geotiff" meta = parse_img(response.content) assert meta["dtype"] == "uint16" assert meta["count"] == 2 - response = client.post(f"/crop/100x100.jpeg?url={DATA_DIR}/cog.tif", json=feature) + response = client.post( + f"/feature/100x100.jpeg?url={DATA_DIR}/cog.tif", json=feature + ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" meta = parse_img(response.content) @@ -510,6 +562,16 @@ def test_TilerFactory(): assert min(resp["b1"]["histogram"][1]) == 5.0 assert max(resp["b1"]["histogram"][1]) == 10.0 + # Stats with Algorithm + response = client.get( + f"/statistics?url={DATA_DIR}/cog.tif&bidx=1&bidx=1&algorithm=normalizedIndex" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert len(resp) == 1 + assert "(b1 - b1) / (b1 + b1)" in resp + # POST - statistics response = client.post( f"/statistics?url={DATA_DIR}/cog.tif&bidx=1&bidx=1&bidx=1", json=feature @@ -567,7 +629,9 @@ def test_TilerFactory(): } response = client.post( - f"/statistics?url={DATA_DIR}/cog.tif&categorical=true", json=feature + "/statistics", + json=feature, + params={"categorical": True, "max_size": 1024, "url": f"{DATA_DIR}/cog.tif"}, ) assert response.status_code == 200 assert response.headers["content-type"] == "application/geo+json" @@ -595,8 +659,17 @@ def test_TilerFactory(): assert len(resp["properties"]["statistics"]["b1"]["histogram"][1]) == 12 response = client.post( - f"/statistics?url={DATA_DIR}/cog.tif&categorical=true&c=1&c=2&c=3&c=4", + "/statistics", json=feature, + params=( + ("categorical", True), + ("c", 1), + ("c", 2), + ("c", 3), + ("c", 4), + ("max_size", 1024), + ("url", f"{DATA_DIR}/cog.tif"), + ), ) assert response.status_code == 200 assert response.headers["content-type"] == "application/geo+json" @@ -624,6 +697,18 @@ def test_TilerFactory(): assert len(resp["properties"]["statistics"]["b1"]["histogram"][0]) == 4 assert resp["properties"]["statistics"]["b1"]["histogram"][0][3] == 0 + # Stats with Algorithm + response = client.post( + f"/statistics?url={DATA_DIR}/cog.tif&bidx=1&bidx=1&algorithm=normalizedIndex", + json=feature, + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + resp = response.json() + assert resp["type"] == "Feature" + assert len(resp["properties"]["statistics"]) == 1 + assert "(b1 - b1) / (b1 + b1)" in resp["properties"]["statistics"] + # Test with Algorithm response = client.get(f"/preview.tif?url={DATA_DIR}/dem.tif&return_mask=False") assert response.status_code == 200 @@ -641,6 +726,25 @@ def test_TilerFactory(): assert meta["dtype"] == "uint8" assert meta["count"] == 3 + # OGC Tileset + response = client.get(f"/tiles?url={DATA_DIR}/cog.tif") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert len(resp["tilesets"]) == NB_DEFAULT_TMS + + first_tms = resp["tilesets"][0] + first_id = DEFAULT_TMS.list()[0] + assert first_id in first_tms["title"] + assert len(first_tms["links"]) == 2 # no link to the tms definition + + response = client.get(f"/tiles/WebMercatorQuad?url={DATA_DIR}/cog.tif") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + # covers only 5 zoom levels + assert len(resp["tileMatrixSetLimits"]) == 5 + @patch("rio_tiler.io.rasterio.rasterio") def test_MultiBaseTilerFactory(rio): @@ -648,7 +752,7 @@ def test_MultiBaseTilerFactory(rio): rio.open = mock_rasterio_open stac = MultiBaseTilerFactory(reader=STACReader) - assert len(stac.router.routes) == 29 + assert len(stac.router.routes) == 24 app = FastAPI() app.include_router(stac.router) @@ -657,6 +761,12 @@ def test_MultiBaseTilerFactory(rio): client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + + response = client.get("/docs") + assert response.status_code == 200 + response = client.get(f"/assets?url={DATA_DIR}/item.json") assert response.status_code == 200 assert len(response.json()) == 2 @@ -664,6 +774,7 @@ def test_MultiBaseTilerFactory(rio): response = client.get(f"/bounds?url={DATA_DIR}/item.json") assert response.status_code == 200 assert len(response.json()["bounds"]) == 4 + assert response.json()["crs"] response = client.get(f"/info?url={DATA_DIR}/item.json") assert response.status_code == 200 @@ -714,6 +825,25 @@ def test_MultiBaseTilerFactory(rio): assert meta["dtype"] == "uint16" assert meta["count"] == 3 + response = client.get( + f"/preview.tif?url={DATA_DIR}/item.json&assets=B01&bidx=1&bidx=1&return_mask=false" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "image/tiff; application=geotiff" + meta = parse_img(response.content) + assert meta["dtype"] == "uint16" + assert meta["count"] == 2 + + with pytest.warns(UserWarning): + response = client.get( + f"/preview.tif?url={DATA_DIR}/item.json&assets=B01&asset_bidx=B01|1,1,1&bidx=1&return_mask=false" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "image/tiff; application=geotiff" + meta = parse_img(response.content) + assert meta["dtype"] == "uint16" + assert meta["count"] == 3 + response = client.get( "/preview.tif", params={ @@ -814,6 +944,15 @@ def test_MultiBaseTilerFactory(rio): assert resp["B01_b1"] assert resp["B09_b1"] + # with Algorithm + response = client.get( + f"/statistics?url={DATA_DIR}/item.json&assets=B01&assets=B09&algorithm=normalizedIndex&asset_as_band=True" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert "(B09 - B01) / (B09 + B01)" in resp + stac_feature = { "type": "FeatureCollection", "features": [ @@ -923,6 +1062,37 @@ def test_MultiBaseTilerFactory(rio): } assert props["B09_b1"] + # with Algorithm + response = client.post( + f"/statistics?url={DATA_DIR}/item.json&assets=B01&assets=B09&algorithm=normalizedIndex&asset_as_band=True", + json=stac_feature["features"][0], + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + resp = response.json() + props = resp["properties"]["statistics"] + assert len(props) == 1 + assert "(B09 - B01) / (B09 + B01)" in props + + # OGC Tileset + response = client.get(f"/tiles?url={DATA_DIR}/item.json") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert len(resp["tilesets"]) == NB_DEFAULT_TMS + + first_tms = resp["tilesets"][0] + first_id = DEFAULT_TMS.list()[0] + assert first_id in first_tms["title"] + assert len(first_tms["links"]) == 2 # no link to the tms definition + + response = client.get(f"/tiles/WebMercatorQuad?url={DATA_DIR}/item.json") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + # default minzoom/maxzoom are 0->24 + assert len(resp["tileMatrixSetLimits"]) == 25 + @attr.s class BandFileReader(MultiBandReader): @@ -932,20 +1102,15 @@ class BandFileReader(MultiBandReader): tms: morecantile.TileMatrixSet = attr.ib( default=morecantile.tms.get("WebMercatorQuad") ) - reader_options: Dict = attr.ib(factory=dict) reader: Type[BaseReader] = attr.ib(default=Reader) + reader_options: Dict = attr.ib(factory=dict) - minzoom: int = attr.ib() - maxzoom: int = attr.ib() - - @minzoom.default - def _minzoom(self): - return self.tms.minzoom + bands: Sequence[str] = attr.ib(init=False) + default_bands: Optional[Sequence[str]] = attr.ib(init=False, default=None) - @maxzoom.default - def _maxzoom(self): - return self.tms.maxzoom + minzoom: int = attr.ib(init=False) + maxzoom: int = attr.ib(init=False) def __attrs_post_init__(self): """Parse Sceneid and get grid bounds.""" @@ -955,6 +1120,9 @@ def __attrs_post_init__(self): self.crs = cog.crs self.minzoom = cog.minzoom self.maxzoom = cog.maxzoom + self.width = cog.width + self.height = cog.height + self.transform = cog.transform def _get_band_url(self, band: str) -> str: """Validate band's name and return band's url.""" @@ -972,7 +1140,7 @@ def test_MultiBandTilerFactory(): bands = MultiBandTilerFactory( reader=BandFileReader, path_dependency=CustomPathParams ) - assert len(bands.router.routes) == 28 + assert len(bands.router.routes) == 23 app = FastAPI() app.include_router(bands.router) @@ -981,6 +1149,12 @@ def test_MultiBandTilerFactory(): client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + + response = client.get("/docs") + assert response.status_code == 200 + response = client.get(f"/bands?directory={DATA_DIR}") assert response.status_code == 200 assert response.json() == ["B01", "B09"] @@ -1085,6 +1259,15 @@ def test_MultiBandTilerFactory(): "percentile_98", } + response = client.get( + f"/statistics?directory={DATA_DIR}&bands=B01&bands=B09&algorithm=normalizedIndex" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert len(resp) == 1 + assert "(B09 - B01) / (B09 + B01)" in resp + # POST - statistics band_feature = { "type": "FeatureCollection", @@ -1220,6 +1403,17 @@ def test_MultiBandTilerFactory(): "percentile_98", } + response = client.post( + f"/statistics?directory={DATA_DIR}&bands=B01&bands=B09&algorithm=normalizedIndex", + json=band_feature, + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/geo+json" + resp = response.json() + props = resp["features"][0]["properties"]["statistics"] + assert len(props) == 1 + assert "(B09 - B01) / (B09 + B01)" in props + # default bands response = client.post(f"/statistics?directory={DATA_DIR}", json=band_feature) assert response.status_code == 200 @@ -1240,6 +1434,25 @@ def test_MultiBandTilerFactory(): assert props["B01"] assert props["B09"] + # OGC Tileset + response = client.get(f"/tiles?directory={DATA_DIR}") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + assert len(resp["tilesets"]) == NB_DEFAULT_TMS + + first_tms = resp["tilesets"][0] + first_id = DEFAULT_TMS.list()[0] + assert first_id in first_tms["title"] + assert len(first_tms["links"]) == 2 # no link to the tms definition + + response = client.get(f"/tiles/WebMercatorQuad?directory={DATA_DIR}") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + resp = response.json() + # 1 Zoom level (8) + assert len(resp["tileMatrixSetLimits"]) == 1 + def test_TMSFactory(): """test TMSFactory.""" @@ -1267,8 +1480,7 @@ def test_TMSFactory(): response = client.get("/tms/tileMatrixSets/WebMercatorQuad") assert response.status_code == 200 body = response.json() - assert body["type"] == "TileMatrixSetType" - assert body["identifier"] == "WebMercatorQuad" + assert body["id"] == "WebMercatorQuad" response = client.get("/tms/tileMatrixSets/WebMercatorQua") assert response.status_code == 422 @@ -1312,14 +1524,14 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic) ( [ {"path": "/bounds", "method": "GET"}, - {"path": "/tiles/{z}/{x}/{y}", "method": "GET"}, + {"path": "/tiles/{tileMatrixSetId}/{z}/{x}/{y}", "method": "GET"}, ], [Depends(must_be_bob)], ), ], router_prefix="something", ) - assert len(cog.router.routes) == 27 + assert len(cog.router.routes) == 22 app = FastAPI() app.include_router(cog.router, prefix="/something") @@ -1328,7 +1540,9 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic) auth_bob = httpx.BasicAuth(username="bob", password="ILoveSponge") auth_notbob = httpx.BasicAuth(username="notbob", password="IHateSponge") - response = client.get(f"/something/tilejson.json?url={DATA_DIR}/cog.tif") + response = client.get( + f"/something/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] @@ -1345,20 +1559,21 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic) assert response.json()["detail"] == "You're not Bob" response = client.get( - f"/something/tiles/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_bob + f"/something/tiles/WebMercatorQuad/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000", + auth=auth_bob, ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" response = client.get( - f"/something/tiles/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000", + f"/something/tiles/WebMercatorQuad/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_notbob, ) assert response.status_code == 401 assert response.json()["detail"] == "You're not Bob" response = client.get( - f"/something/tiles/8/87/48.jpeg?url={DATA_DIR}/cog.tif&rescale=0,1000" + f"/something/tiles/WebMercatorQuad/8/87/48.jpeg?url={DATA_DIR}/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "image/jpeg" @@ -1373,7 +1588,9 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic) app.include_router(cog.router, prefix="/something") client = TestClient(app) - response = client.get(f"/something/tilejson.json?url={DATA_DIR}/cog.tif") + response = client.get( + f"/something/WebMercatorQuad/tilejson.json?url={DATA_DIR}/cog.tif" + ) assert response.status_code == 200 assert response.headers["content-type"] == "application/json" assert response.json()["tilejson"] @@ -1427,14 +1644,21 @@ def gdal_env(disable_read: ReaddirType = Query(ReaddirType.false)): app.include_router(router) client = TestClient(app) - response = client.get(f"/info?url={DATA_DIR}/non_cog.tif") - assert response.json()["overviews"] + with warnings.catch_warnings(): + warnings.simplefilter("error") + response = client.get(f"/info?url={DATA_DIR}/non_cog.tif") + assert response.json()["overviews"] - response = client.get(f"/info?url={DATA_DIR}/non_cog.tif&disable_read=false") - assert response.json()["overviews"] + with warnings.catch_warnings(): + warnings.simplefilter("error") + response = client.get(f"/info?url={DATA_DIR}/non_cog.tif&disable_read=false") + assert response.json()["overviews"] - response = client.get(f"/info?url={DATA_DIR}/non_cog.tif&disable_read=empty_dir") - assert not response.json()["overviews"] + with pytest.warns(NoOverviewWarning): + response = client.get( + f"/info?url={DATA_DIR}/non_cog.tif&disable_read=empty_dir" + ) + assert not response.json()["overviews"] def test_algorithm(): @@ -1456,8 +1680,8 @@ def test_algorithm(): def test_path_param_in_prefix(): """Test path params in prefix.""" - @dataclass - class EndpointFactory(BaseTilerFactory): + @define + class EndpointFactory(BaseFactory): def register_routes(self): """register endpoints.""" @@ -1474,7 +1698,7 @@ def route1(param1: int = Path(...), param2: str = Path(...)): return {"value": param2} app = FastAPI() - endpoints = EndpointFactory(reader=Reader, router_prefix="/prefixed/{param1}") + endpoints = EndpointFactory(router_prefix="/prefixed/{param1}") app.include_router(endpoints.router, prefix="/prefixed/{param1}") client = TestClient(app) @@ -1495,9 +1719,11 @@ def test_AutoFormat_Colormap(): app.include_router(cog.router) with TestClient(app) as client: - - cmap = urlencode( - { + response = client.get( + "/preview", + params={ + "url": f"{DATA_DIR}/cog.tif", + "bidx": 1, "colormap": json.dumps( [ # ([min, max], [r, g, b, a]) @@ -1505,14 +1731,11 @@ def test_AutoFormat_Colormap(): ([2, 6000], [255, 0, 0, 255]), ([6001, 300000], [0, 255, 0, 255]), ] - ) - } + ), + }, ) - - response = client.get(f"/preview?url={DATA_DIR}/cog.tif&bidx=1&{cmap}") assert response.status_code == 200 assert response.headers["content-type"] == "image/png" - with MemoryFile(response.content) as mem: with mem.open() as dst: img = dst.read() @@ -1540,7 +1763,7 @@ def custom_rescale_params() -> Optional[RescaleType]: with TestClient(app) as client: response = client.get( - f"/cog/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" + f"/cog/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" @@ -1548,7 +1771,7 @@ def custom_rescale_params() -> Optional[RescaleType]: assert npy_tile.shape == (2, 256, 256) # mask + data response = client.get( - f"/cog_custom/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" + f"/cog_custom/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" ) assert response.status_code == 200 assert response.headers["content-type"] == "application/x-binary" @@ -1557,7 +1780,7 @@ def custom_rescale_params() -> Optional[RescaleType]: def test_dst_crs_option(): - """test dst-crs parameter.""" + """test dst_crs parameter.""" app = FastAPI() app.include_router(TilerFactory().router) @@ -1571,14 +1794,14 @@ def test_dst_crs_option(): 32621 ) # return the image in the original CRS - response = client.get(f"/preview.tif?url={DATA_DIR}/cog.tif&dst-crs=epsg:4326") + response = client.get(f"/preview.tif?url={DATA_DIR}/cog.tif&dst_crs=epsg:4326") meta = parse_img(response.content) assert meta["crs"] == CRS.from_epsg(4326) assert not meta["crs"] == CRS.from_epsg(32621) - # /crop endpoints + # /bbox endpoints response = client.get( - f"/crop/-56.228,72.715,-54.547,73.188.tif?url={DATA_DIR}/cog.tif" + f"/bbox/-56.228,72.715,-54.547,73.188.tif?url={DATA_DIR}/cog.tif" ) meta = parse_img(response.content) assert meta["crs"] == CRS.from_epsg( @@ -1588,20 +1811,182 @@ def test_dst_crs_option(): # Force output in epsg:32621 response = client.get( - f"/crop/-56.228,72.715,-54.547,73.188.tif?url={DATA_DIR}/cog.tif&dst-crs=epsg:32621" + f"/bbox/-56.228,72.715,-54.547,73.188.tif?url={DATA_DIR}/cog.tif&dst_crs=epsg:32621" ) meta = parse_img(response.content) assert meta["crs"] == CRS.from_epsg(32621) - # coord-crs + dst-crs + # coord_crs + dst_crs response = client.get( - f"/crop/-6259272.328324187,12015838.020930404,-6072144.264300693,12195445.265479913.tif?url={DATA_DIR}/cog.tif&coord-crs=epsg:3857" + f"/bbox/-6259272.328324187,12015838.020930404,-6072144.264300693,12195445.265479913.tif?url={DATA_DIR}/cog.tif&coord_crs=epsg:3857" ) meta = parse_img(response.content) assert meta["crs"] == CRS.from_epsg(3857) response = client.get( - f"/crop/-6259272.328324187,12015838.020930404,-6072144.264300693,12195445.265479913.tif?url={DATA_DIR}/cog.tif&coord-crs=epsg:3857&dst-crs=epsg:32621" + f"/bbox/-6259272.328324187,12015838.020930404,-6072144.264300693,12195445.265479913.tif?url={DATA_DIR}/cog.tif&coord_crs=epsg:3857&dst_crs=epsg:32621" ) meta = parse_img(response.content) assert meta["crs"] == CRS.from_epsg(32621) + + +def test_color_formula_dependency(): + """Ensure that we can set default color formulae via the color_formula_dependency""" + + def custom_color_formula_params() -> Optional[str]: + return "sigmoidal R 7 0.4" + + cog = TilerFactory() + cog_custom_color_formula = TilerFactory( + color_formula_dependency=custom_color_formula_params + ) + + app = FastAPI() + app.include_router(cog.router, prefix="/cog") + app.include_router(cog_custom_color_formula.router, prefix="/cog_custom") + + with TestClient(app) as client: + response = client.get( + f"/cog/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif&color_formula=sigmoidal R 10 0.1" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/x-binary" + npy_tile = numpy.load(BytesIO(response.content)) + assert npy_tile.shape == (2, 256, 256) # mask + data + + response = client.get( + f"/cog_custom/tiles/WebMercatorQuad/8/87/48.npy?url={DATA_DIR}/cog.tif" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/x-binary" + numpy.load(BytesIO(response.content)) + assert npy_tile.shape == (2, 256, 256) # mask + data + + +def test_colormap_factory(): + """Test ColorMapFactory endpoint.""" + # Register custom colormaps + cmaps = default_cmap.register( + { + "cust": {0: (0, 0, 0, 255), 1: (255, 0, 0, 255), 255: (255, 255, 0, 255)}, + "negative": { + -100: (0, 0, 0, 255), + 1: (255, 0, 0, 255), + 255: (255, 255, 0, 255), + }, + "seq": [ + ((1, 2), (255, 0, 0, 255)), + ((2, 3), (255, 240, 255, 255)), + ], + } + ) + + cmaps = ColorMapFactory(supported_colormaps=cmaps) + + app = FastAPI() + app.include_router(cmaps.router) + client = TestClient(app) + + response = client.get("/colorMaps") + assert response.status_code == 200 + assert "cust" in response.json()["colorMaps"] + assert "negative" in response.json()["colorMaps"] + assert "seq" in response.json()["colorMaps"] + assert "viridis" in response.json()["colorMaps"] + + response = client.get("/colorMaps/viridis") + assert response.status_code == 200 + + response = client.get("/colorMaps/cust") + assert response.status_code == 200 + + response = client.get("/colorMaps/negative") + assert response.status_code == 200 + + response = client.get("/colorMaps/seq") + assert response.status_code == 200 + + response = client.get("/colorMaps/yo") + assert response.status_code == 422 + + response = client.get("/colorMaps/viridis", params={"format": "png"}) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 256 + assert meta["height"] == 20 + + response = client.get( + "/colorMaps/viridis", params={"format": "png", "orientation": "vertical"} + ) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 20 + assert meta["height"] == 256 + + response = client.get( + "/colorMaps/viridis", params={"format": "png", "width": 1000, "height": 100} + ) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 1000 + assert meta["height"] == 100 + + response = client.get("/colorMaps/cust", params={"format": "png"}) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 256 + assert meta["height"] == 20 + + response = client.get( + "/colorMaps/cust", params={"format": "png", "orientation": "vertical"} + ) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 20 + assert meta["height"] == 256 + + response = client.get("/colorMaps/negative", params={"format": "png"}) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 256 + assert meta["height"] == 20 + + response = client.get( + "/colorMaps/negative", params={"format": "png", "orientation": "vertical"} + ) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 20 + assert meta["height"] == 256 + + response = client.get("/colorMaps/seq", params={"format": "png"}) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 256 + assert meta["height"] == 20 + + response = client.get( + "/colorMaps/seq", params={"format": "png", "orientation": "vertical"} + ) + assert response.status_code == 200 + meta = parse_img(response.content) + assert meta["dtype"] == "uint8" + assert meta["count"] == 4 + assert meta["width"] == 20 + assert meta["height"] == 256 diff --git a/src/titiler/core/tests/test_rendering.py b/src/titiler/core/tests/test_rendering.py new file mode 100644 index 000000000..1241a5eae --- /dev/null +++ b/src/titiler/core/tests/test_rendering.py @@ -0,0 +1,105 @@ +"""test titiler rendering function.""" + +import warnings + +import numpy +import pytest +from rasterio.io import MemoryFile +from rio_tiler.errors import InvalidDatatypeWarning +from rio_tiler.models import ImageData + +from titiler.core.resources.enums import ImageType +from titiler.core.utils import render_image + + +def test_rendering(): + """test rendering.""" + im = ImageData(numpy.zeros((1, 256, 256), dtype="uint8")) + + # Should render as JPEG + content, media = render_image(im) + assert media == "image/jpeg" + with MemoryFile(content) as mem: + with mem.open() as dst: + assert dst.profile["driver"] == "JPEG" + assert dst.count == 1 + assert dst.width == 256 + assert dst.height == 256 + arr = dst.read() + assert numpy.unique(arr).tolist() == [0] + + # Should render as PNG + content, media = render_image(im, output_format=ImageType.png) + assert media == "image/png" + with MemoryFile(content) as mem: + with mem.open() as dst: + assert dst.profile["driver"] == "PNG" + assert dst.count == 2 + arr = dst.read() + assert numpy.unique(arr[0]).tolist() == [0] + + with pytest.warns(InvalidDatatypeWarning): + _, media = render_image( + ImageData(numpy.zeros((1, 256, 256), dtype="uint16")), + output_format=ImageType.jpeg, + ) + assert media == "image/jpeg" + + with pytest.warns(InvalidDatatypeWarning): + _, media = render_image( + ImageData(numpy.zeros((1, 256, 256), dtype="float32")), + output_format=ImageType.png, + ) + assert media == "image/png" + + with pytest.warns(InvalidDatatypeWarning): + _, media = render_image( + ImageData(numpy.zeros((1, 256, 256), dtype="float32")), + output_format=ImageType.jp2, + ) + assert media == "image/jp2" + + # Make sure that we do not rescale uint16 data when there is a colormap + # Because the colormap will result in data between 0 and 255 it should be of type uint8 + with warnings.catch_warnings(): + warnings.simplefilter("error") + cm = {1: (0, 0, 0, 255), 1000: (255, 255, 255, 255)} + d = numpy.zeros((1, 256, 256), dtype="float32") + 1 + d[0, 0:10, 0:10] = 1000 + content, media = render_image( + ImageData(d), + output_format=ImageType.jpeg, + colormap=cm, + ) + assert media == "image/jpeg" + + with MemoryFile(content) as mem: + with mem.open() as dst: + assert dst.count == 3 + assert dst.dtypes == ("uint8", "uint8", "uint8") + assert dst.read()[:, 0, 0].tolist() == [255, 255, 255] + assert dst.read()[:, 11, 11].tolist() == [0, 0, 0] + + # Partial alpha values + cm = { + 1: (0, 0, 0, 0), + 500: (100, 100, 100, 50), + 1000: (255, 255, 255, 255), + } + d = numpy.ma.zeros((1, 256, 256), dtype="float32") + 1 + d[0, 0:10, 0:10] = 500 + d[0, 10:20, 10:20] = 1000 + content, media = render_image( + ImageData(d), + output_format=ImageType.png, + colormap=cm, + ) + assert media == "image/png" + + with MemoryFile(content) as mem: + with mem.open() as dst: + assert dst.count == 4 + assert dst.dtypes == ("uint8", "uint8", "uint8", "uint8") + assert dst.read()[:, 0, 0].tolist() == [100, 100, 100, 50] + assert dst.read()[:, 11, 11].tolist() == [255, 255, 255, 255] + assert dst.read()[:, 30, 30].tolist() == [0, 0, 0, 0] diff --git a/src/titiler/core/titiler/core/__init__.py b/src/titiler/core/titiler/core/__init__.py index f26d8963f..3e188b236 100644 --- a/src/titiler/core/titiler/core/__init__.py +++ b/src/titiler/core/titiler/core/__init__.py @@ -1,11 +1,14 @@ """titiler.core""" -__version__ = "0.11.7" +__version__ = "0.19.0.dev" from . import dependencies, errors, factory, routing # noqa from .factory import ( # noqa - BaseTilerFactory, + AlgorithmFactory, + BaseFactory, + ColorMapFactory, MultiBandTilerFactory, MultiBaseTilerFactory, TilerFactory, + TMSFactory, ) diff --git a/src/titiler/core/titiler/core/algorithm/__init__.py b/src/titiler/core/titiler/core/algorithm/__init__.py index b77327384..5fc35667f 100644 --- a/src/titiler/core/titiler/core/algorithm/__init__.py +++ b/src/titiler/core/titiler/core/algorithm/__init__.py @@ -7,8 +7,10 @@ import attr from fastapi import HTTPException, Query from pydantic import ValidationError +from typing_extensions import Annotated -from titiler.core.algorithm.base import AlgorithmMetadata, BaseAlgorithm # noqa +from titiler.core.algorithm.base import AlgorithmMetadata # noqa +from titiler.core.algorithm.base import BaseAlgorithm from titiler.core.algorithm.dem import Contours, HillShade, TerrainRGB, Terrarium from titiler.core.algorithm.index import NormalizedIndex @@ -55,10 +57,14 @@ def dependency(self): """FastAPI PostProcess dependency.""" def post_process( - algorithm: Literal[tuple(self.data.keys())] = Query( - None, description="Algorithm name" - ), - algorithm_params: str = Query(None, description="Algorithm parameter"), + algorithm: Annotated[ + Literal[tuple(self.data.keys())], + Query(description="Algorithm name"), + ] = None, + algorithm_params: Annotated[ + Optional[str], + Query(description="Algorithm parameter"), + ] = None, ) -> Optional[BaseAlgorithm]: """Data Post-Processing options.""" kwargs = json.loads(algorithm_params) if algorithm_params else {} diff --git a/src/titiler/core/titiler/core/algorithm/base.py b/src/titiler/core/titiler/core/algorithm/base.py index 5eb6bdf3b..33ee9c370 100644 --- a/src/titiler/core/titiler/core/algorithm/base.py +++ b/src/titiler/core/titiler/core/algorithm/base.py @@ -15,26 +15,26 @@ class BaseAlgorithm(BaseModel, metaclass=abc.ABCMeta): """ # metadata - input_nbands: Optional[int] - output_nbands: Optional[int] - output_dtype: Optional[str] - output_min: Optional[Sequence] - output_max: Optional[Sequence] + input_nbands: Optional[int] = None + output_nbands: Optional[int] = None + output_dtype: Optional[str] = None + output_min: Optional[Sequence] = None + output_max: Optional[Sequence] = None + + model_config = {"extra": "allow"} @abc.abstractmethod def __call__(self, img: ImageData) -> ImageData: """Apply algorithm""" ... - class Config: - """Config for model.""" - - extra = "allow" - class AlgorithmMetadata(BaseModel): """Algorithm metadata.""" + title: Optional[str] = None + description: Optional[str] = None + inputs: Dict outputs: Dict parameters: Dict diff --git a/src/titiler/core/titiler/core/algorithm/dem.py b/src/titiler/core/titiler/core/algorithm/dem.py index 858d538d2..9aff42ac4 100644 --- a/src/titiler/core/titiler/core/algorithm/dem.py +++ b/src/titiler/core/titiler/core/algorithm/dem.py @@ -1,6 +1,7 @@ """titiler.core.algorithm DEM.""" import numpy +from pydantic import Field from rasterio import windows from rio_tiler.colormap import apply_cmap, cmap from rio_tiler.models import ImageData @@ -12,10 +13,13 @@ class HillShade(BaseAlgorithm): """Hillshade.""" + title: str = "Hillshade" + description: str = "Create hillshade from DEM dataset." + # parameters - azimuth: int = 90 - angle_altitude: float = 90 - buffer: int = 3 + azimuth: int = Field(45, ge=0, le=360) + angle_altitude: float = Field(45.0, ge=-90.0, le=90.0) + buffer: int = Field(3, ge=0, le=99) # metadata input_nbands: int = 1 @@ -24,41 +28,36 @@ class HillShade(BaseAlgorithm): def __call__(self, img: ImageData) -> ImageData: """Create hillshade from DEM dataset.""" - data = img.data[0] - mask = img.mask - bounds = img.bounds - - x, y = numpy.gradient(data) - + x, y = numpy.gradient(img.array[0]) slope = numpy.pi / 2.0 - numpy.arctan(numpy.sqrt(x * x + y * y)) aspect = numpy.arctan2(-x, y) - azimuthrad = self.azimuth * numpy.pi / 180.0 + azimuth = 360.0 - self.azimuth + azimuthrad = azimuth * numpy.pi / 180.0 altituderad = self.angle_altitude * numpy.pi / 180.0 shaded = numpy.sin(altituderad) * numpy.sin(slope) + numpy.cos( altituderad ) * numpy.cos(slope) * numpy.cos(azimuthrad - aspect) - hillshade_array = 255 * (shaded + 1) / 2 - - data = numpy.expand_dims(hillshade_array, axis=0).astype(dtype=numpy.uint8) + data = 255 * (shaded + 1) / 2 + data[data < 0] = 0 # set hillshade values to min of 0. + bounds = img.bounds if self.buffer: - data = data[:, self.buffer : -self.buffer, self.buffer : -self.buffer] - mask = mask[self.buffer : -self.buffer, self.buffer : -self.buffer] - # image bounds without buffer + data = data[self.buffer : -self.buffer, self.buffer : -self.buffer] + window = windows.Window( col_off=self.buffer, row_off=self.buffer, - width=mask.shape[1], - height=mask.shape[0], + width=data.shape[1], + height=data.shape[0], ) bounds = windows.bounds(window, img.transform) return ImageData( - data, - mask, + data.astype(self.output_dtype), assets=img.assets, crs=img.crs, bounds=bounds, + band_names=["hillshade"], ) @@ -68,11 +67,14 @@ class Contours(BaseAlgorithm): Original idea from https://custom-scripts.sentinel-hub.com/dem/contour-lines/ """ + title: str = "Contours" + description: str = "Create contours from DEM dataset." + # parameters - increment: int = 35 - thickness: int = 1 - minz: int = -12000 - maxz: int = 8000 + increment: int = Field(35, ge=0, le=999) + thickness: int = Field(1, ge=0, le=10) + minz: int = Field(-12000, ge=-99999, le=99999) + maxz: int = Field(8000, ge=-99999, le=99999) # metadata input_nbands: int = 1 @@ -81,18 +83,22 @@ class Contours(BaseAlgorithm): def __call__(self, img: ImageData) -> ImageData: """Add contours.""" - data = img.data + data = img.data.astype("float64") # Apply rescaling for minz,maxz to 1->255 and apply Terrain colormap - arr = linear_rescale(data, (self.minz, self.maxz), (1, 255)).astype("uint8") + arr = linear_rescale(data, (self.minz, self.maxz), (1, 255)).astype( + self.output_dtype + ) arr, _ = apply_cmap(arr, cmap.get("terrain")) # set black (0) for contour lines arr = numpy.where(data % self.increment < self.thickness, 0, arr) + data = numpy.ma.MaskedArray(arr) + data.mask = ~img.mask + return ImageData( - arr, - img.mask, + data, assets=img.assets, crs=img.crs, bounds=img.bounds, @@ -102,6 +108,9 @@ def __call__(self, img: ImageData) -> ImageData: class Terrarium(BaseAlgorithm): """Encode DEM into RGB (Mapzen Terrarium).""" + title: str = "Terrarium" + description: str = "Encode DEM into RGB (Mapzen Terrarium)." + # metadata input_nbands: int = 1 output_nbands: int = 3 @@ -109,15 +118,13 @@ class Terrarium(BaseAlgorithm): def __call__(self, img: ImageData) -> ImageData: """Encode DEM into RGB.""" - data = numpy.clip(img.data[0] + 32768.0, 0.0, 65535.0) + data = numpy.clip(img.array[0] + 32768.0, 0.0, 65535.0) r = data / 256 g = data % 256 b = (data * 256) % 256 - arr = numpy.stack([r, g, b]).astype(numpy.uint8) return ImageData( - arr, - img.mask, + numpy.ma.stack([r, g, b]).astype(self.output_dtype), assets=img.assets, crs=img.crs, bounds=img.bounds, @@ -127,9 +134,12 @@ def __call__(self, img: ImageData) -> ImageData: class TerrainRGB(BaseAlgorithm): """Encode DEM into RGB (Mapbox Terrain RGB).""" + title: str = "TerrainRGB" + description: str = "Encode DEM into RGB (Mapbox Terrain RGB)." + # parameters - interval: float = 0.1 - baseval: float = -10000.0 + interval: float = Field(0.1, ge=0.0, le=1.0) + baseval: float = Field(-10000.0, ge=-99999.0, le=99999.0) # metadata input_nbands: int = 1 @@ -153,27 +163,22 @@ def _range_check(datarange): round_digits = 0 - data = img.data[0].astype(numpy.float64) + data = img.array[0].astype(numpy.float64) data -= self.baseval data /= self.interval data = numpy.around(data / 2**round_digits) * 2**round_digits - rows, cols = data.shape datarange = data.max() - data.min() if _range_check(datarange): - raise ValueError("Data of {} larger than 256 ** 3".format(datarange)) + raise ValueError(f"Data of {datarange} larger than 256 ** 3") - rgb = numpy.zeros((3, rows, cols), dtype=numpy.uint8) - rgb[2] = ((data / 256) - (data // 256)) * 256 - rgb[1] = (((data // 256) / 256) - ((data // 256) // 256)) * 256 - rgb[0] = ( - (((data // 256) // 256) / 256) - (((data // 256) // 256) // 256) - ) * 256 + r = ((((data // 256) // 256) / 256) - (((data // 256) // 256) // 256)) * 256 + g = (((data // 256) / 256) - ((data // 256) // 256)) * 256 + b = ((data / 256) - (data // 256)) * 256 return ImageData( - rgb, - img.mask, + numpy.ma.stack([r, g, b]).astype(self.output_dtype), assets=img.assets, crs=img.crs, bounds=img.bounds, diff --git a/src/titiler/core/titiler/core/algorithm/index.py b/src/titiler/core/titiler/core/algorithm/index.py index 1e28e0f45..e0351d77e 100644 --- a/src/titiler/core/titiler/core/algorithm/index.py +++ b/src/titiler/core/titiler/core/algorithm/index.py @@ -11,6 +11,9 @@ class NormalizedIndex(BaseAlgorithm): """Normalized Difference Index.""" + title: str = "Normalized Difference Index" + description: str = "Compute normalized difference index from two bands." + # metadata input_nbands: int = 2 output_nbands: int = 1 @@ -20,18 +23,14 @@ class NormalizedIndex(BaseAlgorithm): def __call__(self, img: ImageData) -> ImageData: """Normalized difference.""" - b1 = img.data[0].astype("float32") - b2 = img.data[1].astype("float32") - - arr = numpy.where(img.mask, (b2 - b1) / (b2 + b1), 0) - - # ImageData only accept image in form of (count, height, width) - arr = numpy.expand_dims(arr, axis=0).astype(self.output_dtype) - + b1 = img.array[0].astype("float32") + b2 = img.array[1].astype("float32") + arr = numpy.ma.MaskedArray((b2 - b1) / (b2 + b1), dtype=self.output_dtype) + bnames = img.band_names return ImageData( arr, - img.mask, assets=img.assets, crs=img.crs, bounds=img.bounds, + band_names=[f"({bnames[1]} - {bnames[0]}) / ({bnames[1]} + {bnames[0]})"], ) diff --git a/src/titiler/core/titiler/core/dependencies.py b/src/titiler/core/titiler/core/dependencies.py index efc19260c..110b677cc 100644 --- a/src/titiler/core/titiler/core/dependencies.py +++ b/src/titiler/core/titiler/core/dependencies.py @@ -1,55 +1,64 @@ """Common dependency.""" import json +import warnings from dataclasses import dataclass -from enum import Enum -from typing import Dict, List, Optional, Sequence, Tuple, Union +from typing import Callable, Dict, List, Literal, Optional, Sequence, Tuple, Union import numpy from fastapi import HTTPException, Query from rasterio.crs import CRS -from rasterio.enums import Resampling -from rio_tiler.colormap import cmap, parse_color +from rio_tiler.colormap import ColorMaps +from rio_tiler.colormap import cmap as default_cmap +from rio_tiler.colormap import parse_color from rio_tiler.errors import MissingAssets, MissingBands -from rio_tiler.types import ColorMapType - -ColorMapName = Enum( # type: ignore - "ColorMapName", [(a, a) for a in sorted(cmap.list())] -) -ResamplingName = Enum( # type: ignore - "ResamplingName", [(r.name, r.name) for r in Resampling] -) - - -def ColorMapParams( - colormap_name: ColorMapName = Query(None, description="Colormap name"), - colormap: str = Query(None, description="JSON encoded custom Colormap"), -) -> Optional[ColorMapType]: - """Colormap Dependency.""" - if colormap_name: - return cmap.get(colormap_name.value) - - if colormap: - try: - c = json.loads( - colormap, - object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()}, - ) +from rio_tiler.types import RIOResampling, WarpResampling +from typing_extensions import Annotated - # Make sure to match colormap type - if isinstance(c, Sequence): - c = [(tuple(inter), parse_color(v)) for (inter, v) in c] - return c - except json.JSONDecodeError as e: - raise HTTPException( - status_code=400, detail="Could not parse the colormap value." - ) from e +def create_colormap_dependency(cmap: ColorMaps) -> Callable: + """Create Colormap Dependency.""" + + def deps( + colormap_name: Annotated[ # type: ignore + Literal[tuple(cmap.list())], + Query(description="Colormap name"), + ] = None, + colormap: Annotated[ + Optional[str], Query(description="JSON encoded custom Colormap") + ] = None, + ): + if colormap_name: + return cmap.get(colormap_name) + + if colormap: + try: + c = json.loads( + colormap, + object_hook=lambda x: { + int(k): parse_color(v) for k, v in x.items() + }, + ) + + # Make sure to match colormap type + if isinstance(c, Sequence): + c = [(tuple(inter), parse_color(v)) for (inter, v) in c] + + return c + except json.JSONDecodeError as e: + raise HTTPException( + status_code=400, detail="Could not parse the colormap value." + ) from e + + return None + + return deps - return None +ColorMapParams = create_colormap_dependency(default_cmap) -def DatasetPathParams(url: str = Query(..., description="Dataset URL")) -> str: + +def DatasetPathParams(url: Annotated[str, Query(description="Dataset URL")]) -> str: """Create dataset path from args""" return url @@ -60,43 +69,61 @@ class DefaultDependency: def keys(self): """Return Keys.""" + warnings.warn( + "Dict unpacking will be removed for `DefaultDependency` in titiler 0.19.0", + DeprecationWarning, + ) return self.__dict__.keys() def __getitem__(self, key): """Return value.""" return self.__dict__[key] + def as_dict(self, exclude_none: bool = True) -> Dict: + """Transform dataclass to dict.""" + if exclude_none: + return {k: v for k, v in self.__dict__.items() if v is not None} + + return dict(self.__dict__.items()) + # Dependencies for simple BaseReader (e.g COGReader) @dataclass class BidxParams(DefaultDependency): """Band Indexes parameters.""" - indexes: Optional[List[int]] = Query( - None, - title="Band indexes", - alias="bidx", - description="Dataset band indexes", - examples={"one-band": {"value": [1]}, "multi-bands": {"value": [1, 2, 3]}}, - ) + indexes: Annotated[ + Optional[List[int]], + Query( + title="Band indexes", + alias="bidx", + description="Dataset band indexes", + openapi_examples={ + "one-band": {"value": [1]}, + "multi-bands": {"value": [1, 2, 3]}, + }, + ), + ] = None @dataclass class ExpressionParams(DefaultDependency): """Expression parameters.""" - expression: Optional[str] = Query( - None, - title="Band Math expression", - description="rio-tiler's band math expression", - examples={ - "simple": {"description": "Simple band math.", "value": "b1/b2"}, - "multi-bands": { - "description": "Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).", - "value": "b1/b2;b2+b3", + expression: Annotated[ + Optional[str], + Query( + title="Band Math expression", + description="rio-tiler's band math expression", + openapi_examples={ + "simple": {"description": "Simple band math.", "value": "b1/b2"}, + "multi-bands": { + "description": "Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).", + "value": "b1/b2;b2+b3", + }, }, - }, - ) + ), + ] = None @dataclass @@ -111,76 +138,86 @@ class BidxExprParams(ExpressionParams, BidxParams): class AssetsParams(DefaultDependency): """Assets parameters.""" - assets: List[str] = Query( - None, - title="Asset names", - description="Asset's names.", - examples={ - "one-asset": { - "description": "Return results for asset `data`.", - "value": ["data"], + assets: Annotated[ + Optional[List[str]], + Query( + title="Asset names", + description="Asset's names.", + openapi_examples={ + "one-asset": { + "description": "Return results for asset `data`.", + "value": ["data"], + }, + "multi-assets": { + "description": "Return results for assets `data` and `cog`.", + "value": ["data", "cog"], + }, }, - "multi-assets": { - "description": "Return results for assets `data` and `cog`.", - "value": ["data", "cog"], - }, - }, - ) + ), + ] = None + + +def parse_asset_indexes( + asset_indexes: Union[Sequence[str], Dict[str, Sequence[int]]], +) -> Dict[str, Sequence[int]]: + """parse asset indexes parameters.""" + return { + idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) + for idx in asset_indexes + } + + +def parse_asset_expression( + asset_expression: Union[Sequence[str], Dict[str, str]], +) -> Dict[str, str]: + """parse asset expression parameters.""" + return {idx.split("|")[0]: idx.split("|")[1] for idx in asset_expression} @dataclass -class AssetsBidxExprParams(DefaultDependency): +class AssetsBidxExprParams(AssetsParams, BidxParams): """Assets, Expression and Asset's band Indexes parameters.""" - assets: Optional[List[str]] = Query( - None, - title="Asset names", - description="Asset's names.", - examples={ - "one-asset": { - "description": "Return results for asset `data`.", - "value": ["data"], - }, - "multi-assets": { - "description": "Return results for assets `data` and `cog`.", - "value": ["data", "cog"], - }, - }, - ) - expression: Optional[str] = Query( - None, - title="Band Math expression", - description="Band math expression between assets", - examples={ - "simple": { - "description": "Return results of expression between assets.", - "value": "asset1_b1 + asset2_b1 / asset3_b1", + expression: Annotated[ + Optional[str], + Query( + title="Band Math expression", + description="Band math expression between assets", + openapi_examples={ + "simple": { + "description": "Return results of expression between assets.", + "value": "asset1_b1 + asset2_b1 / asset3_b1", + }, }, - }, - ) - - asset_indexes: Optional[Sequence[str]] = Query( - None, - title="Per asset band indexes", - description="Per asset band indexes (coma separated indexes)", - alias="asset_bidx", - examples={ - "one-asset": { - "description": "Return indexes 1,2,3 of asset `data`.", - "value": ["data|1,2,3"], + ), + ] = None + + asset_indexes: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band indexes", + description="Per asset band indexes (coma separated indexes)", + alias="asset_bidx", + openapi_examples={ + "one-asset": { + "description": "Return indexes 1,2,3 of asset `data`.", + "value": ["data|1,2,3"], + }, + "multi-assets": { + "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", + "value": ["data|1,2,3", "cog|1"], + }, }, - "multi-assets": { - "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", - "value": ["data|1,2,3", "cog|1"], - }, - }, - ) + ), + ] = None - asset_as_band: Optional[bool] = Query( - None, - title="Consider asset as a 1 band dataset", - description="Asset as Band", - ) + asset_as_band: Annotated[ + Optional[bool], + Query( + title="Consider asset as a 1 band dataset", + description="Asset as Band", + ), + ] = None def __post_init__(self): """Post Init.""" @@ -190,10 +227,13 @@ def __post_init__(self): ) if self.asset_indexes: - self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore - idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) - for idx in self.asset_indexes - } + self.asset_indexes = parse_asset_indexes(self.asset_indexes) + + if self.asset_indexes and self.indexes: + warnings.warn( + "Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.", + UserWarning, + ) @dataclass @@ -203,61 +243,69 @@ class AssetsBidxExprParamsOptional(AssetsBidxExprParams): def __post_init__(self): """Post Init.""" if self.asset_indexes: - self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore - idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) - for idx in self.asset_indexes - } + self.asset_indexes = parse_asset_indexes(self.asset_indexes) + + if self.asset_indexes and self.indexes: + warnings.warn( + "Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.", + UserWarning, + ) @dataclass -class AssetsBidxParams(AssetsParams): +class AssetsBidxParams(AssetsParams, BidxParams): """Assets, Asset's band Indexes and Asset's band Expression parameters.""" - asset_indexes: Optional[Sequence[str]] = Query( - None, - title="Per asset band indexes", - description="Per asset band indexes", - alias="asset_bidx", - examples={ - "one-asset": { - "description": "Return indexes 1,2,3 of asset `data`.", - "value": ["data|1;2;3"], - }, - "multi-assets": { - "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", - "value": ["data|1;2;3", "cog|1"], - }, - }, - ) - - asset_expression: Optional[Sequence[str]] = Query( - None, - title="Per asset band expression", - description="Per asset band expression", - examples={ - "one-asset": { - "description": "Return results for expression `b1*b2+b3` of asset `data`.", - "value": ["data|b1*b2+b3"], + asset_indexes: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band indexes", + description="Per asset band indexes", + alias="asset_bidx", + openapi_examples={ + "one-asset": { + "description": "Return indexes 1,2,3 of asset `data`.", + "value": ["data|1;2;3"], + }, + "multi-assets": { + "description": "Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`", + "value": ["data|1;2;3", "cog|1"], + }, }, - "multi-assets": { - "description": "Return results for expressions `b1*b2+b3` for asset `data` and `b1+b3` for asset `cog`.", - "value": ["data|b1*b2+b3", "cog|b1+b3"], + ), + ] = None + + asset_expression: Annotated[ + Optional[Sequence[str]], + Query( + title="Per asset band expression", + description="Per asset band expression", + openapi_examples={ + "one-asset": { + "description": "Return results for expression `b1*b2+b3` of asset `data`.", + "value": ["data|b1*b2+b3"], + }, + "multi-assets": { + "description": "Return results for expressions `b1*b2+b3` for asset `data` and `b1+b3` for asset `cog`.", + "value": ["data|b1*b2+b3", "cog|b1+b3"], + }, }, - }, - ) + ), + ] = None def __post_init__(self): """Post Init.""" if self.asset_indexes: - self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore - idx.split("|")[0]: list(map(int, idx.split("|")[1].split(","))) - for idx in self.asset_indexes - } + self.asset_indexes = parse_asset_indexes(self.asset_indexes) if self.asset_expression: - self.asset_expression: Dict[str, str] = { # type: ignore - idx.split("|")[0]: idx.split("|")[1] for idx in self.asset_expression - } + self.asset_expression = parse_asset_expression(self.asset_expression) + + if self.asset_indexes and self.indexes: + warnings.warn( + "Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.", + UserWarning, + ) # Dependencies for MultiBandReader @@ -265,21 +313,23 @@ def __post_init__(self): class BandsParams(DefaultDependency): """Band names parameters.""" - bands: List[str] = Query( - None, - title="Band names", - description="Band's names.", - examples={ - "one-band": { - "description": "Return results for band `B01`.", - "value": ["B01"], - }, - "multi-bands": { - "description": "Return results for bands `B01` and `B02`.", - "value": ["B01", "B02"], + bands: Annotated[ + Optional[List[str]], + Query( + title="Band names", + description="Band's names.", + openapi_examples={ + "one-band": { + "description": "Return results for band `B01`.", + "value": ["B01"], + }, + "multi-bands": { + "description": "Return results for bands `B01` and `B02`.", + "value": ["B01", "B02"], + }, }, - }, - ) + ), + ] = None @dataclass @@ -302,14 +352,26 @@ def __post_init__(self): @dataclass -class ImageParams(DefaultDependency): - """Common Preview/Crop parameters.""" +class PreviewParams(DefaultDependency): + """Common Preview parameters.""" - max_size: Optional[int] = Query( - 1024, description="Maximum image size to read onto." - ) - height: Optional[int] = Query(None, description="Force output image height.") - width: Optional[int] = Query(None, description="Force output image width.") + max_size: Annotated[int, "Maximum image size to read onto."] = 1024 + height: Annotated[Optional[int], "Force output image height."] = None + width: Annotated[Optional[int], "Force output image width."] = None + + def __post_init__(self): + """Post Init.""" + if self.width and self.height: + self.max_size = None + + +@dataclass +class PartFeatureParams(DefaultDependency): + """Common parameters for bbox and feature.""" + + max_size: Annotated[Optional[int], "Maximum image size to read onto."] = None + height: Annotated[Optional[int], "Force output image height."] = None + width: Annotated[Optional[int], "Force output image width."] = None def __post_init__(self): """Post Init.""" @@ -321,50 +383,86 @@ def __post_init__(self): class DatasetParams(DefaultDependency): """Low level WarpedVRT Optional parameters.""" - nodata: Optional[Union[str, int, float]] = Query( - None, title="Nodata value", description="Overwrite internal Nodata value" - ) - unscale: Optional[bool] = Query( - False, - title="Apply internal Scale/Offset", - description="Apply internal Scale/Offset", - ) - resampling_method: ResamplingName = Query( - ResamplingName.nearest, # type: ignore - alias="resampling", - description="Resampling method.", - ) + nodata: Annotated[ + Optional[Union[str, int, float]], + Query( + title="Nodata value", + description="Overwrite internal Nodata value", + ), + ] = None + unscale: Annotated[ + Optional[bool], + Query( + title="Apply internal Scale/Offset", + description="Apply internal Scale/Offset. Defaults to `False`.", + ), + ] = None + resampling_method: Annotated[ + Optional[RIOResampling], + Query( + alias="resampling", + description="RasterIO resampling algorithm. Defaults to `nearest`.", + ), + ] = None + reproject_method: Annotated[ + Optional[WarpResampling], + Query( + alias="reproject", + description="WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`.", + ), + ] = None def __post_init__(self): """Post Init.""" if self.nodata is not None: self.nodata = numpy.nan if self.nodata == "nan" else float(self.nodata) - self.resampling_method = self.resampling_method.value # type: ignore + + if self.unscale is not None: + self.unscale = bool(self.unscale) @dataclass class ImageRenderingParams(DefaultDependency): """Image Rendering options.""" - add_mask: bool = Query( - True, alias="return_mask", description="Add mask to the output data." - ) + add_mask: Annotated[ + Optional[bool], + Query( + alias="return_mask", + description="Add mask to the output data. Defaults to `True`", + ), + ] = None RescaleType = List[Tuple[float, ...]] def RescalingParams( - rescale: Optional[List[str]] = Query( - None, - title="Min/Max data Rescaling", - description="comma (',') delimited Min,Max range. Can set multiple time for multiple bands.", - example=["0,2000", "0,1000", "0,10000"], # band 1 # band 2 # band 3 - ) + rescale: Annotated[ + Optional[List[str]], + Query( + title="Min/Max data Rescaling", + description="comma (',') delimited Min,Max range. Can set multiple time for multiple bands.", + examples=["0,2000", "0,1000", "0,10000"], # band 1 # band 2 # band 3 + ), + ] = None, ) -> Optional[RescaleType]: """Min/Max data Rescaling""" if rescale: - return [tuple(map(float, r.replace(" ", "").split(","))) for r in rescale] + rescale_array = [] + for r in rescale: + parsed = tuple( + map( + float, + r.replace(" ", "").replace("[", "").replace("]", "").split(","), + ) + ) + assert ( + len(parsed) == 2 + ), f"Invalid rescale values: {rescale}, should be of form ['min,max', 'min,max'] or [[min,max], [min, max]]" + rescale_array.append(parsed) + + return rescale_array return None @@ -373,57 +471,72 @@ def RescalingParams( class StatisticsParams(DefaultDependency): """Statistics options.""" - categorical: bool = Query( - False, description="Return statistics for categorical dataset." - ) - categories: List[Union[float, int]] = Query( - None, - alias="c", - title="Pixels values for categories.", - description="List of values for which to report counts.", - example=[1, 2, 3], - ) - percentiles: List[int] = Query( - [2, 98], - alias="p", - title="Percentile values", - description="List of percentile values.", - example=[2, 5, 95, 98], - ) + categorical: Annotated[ + Optional[bool], + Query( + description="Return statistics for categorical dataset. Defaults to `False`" + ), + ] = None + categories: Annotated[ + Optional[List[Union[float, int]]], + Query( + alias="c", + title="Pixels values for categories.", + description="List of values for which to report counts.", + examples=[1, 2, 3], + ), + ] = None + percentiles: Annotated[ + Optional[List[int]], + Query( + alias="p", + title="Percentile values", + description="List of percentile values (default to [2, 98]).", + examples=[2, 5, 95, 98], + ), + ] = None + + def __post_init__(self): + """Set percentiles default.""" + if not self.percentiles: + self.percentiles = [2, 98] @dataclass class HistogramParams(DefaultDependency): """Numpy Histogram options.""" - bins: Optional[str] = Query( - None, - alias="histogram_bins", - title="Histogram bins.", - description=""" + bins: Annotated[ + Optional[str], + Query( + alias="histogram_bins", + title="Histogram bins.", + description=""" Defines the number of equal-width bins in the given range (10, by default). If bins is a sequence (comma `,` delimited values), it defines a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform bin widths. link: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html - """, - examples={ - "simple": { - "description": "Defines the number of equal-width bins", - "value": 8, - }, - "array": { - "description": "Defines custom bin edges (comma `,` delimited values)", - "value": "0,100,200,300", + """, + openapi_examples={ + "simple": { + "description": "Defines the number of equal-width bins", + "value": 8, + }, + "array": { + "description": "Defines custom bin edges (comma `,` delimited values)", + "value": "0,100,200,300", + }, }, - }, - ) - - range: Optional[str] = Query( - None, - alias="histogram_range", - title="Histogram range", - description=""" + ), + ] = None + + range: Annotated[ + Optional[str], + Query( + alias="histogram_range", + title="Histogram range", + description=""" Comma `,` delimited range of the bins. The lower and upper range of the bins. If not provided, range is simply (a.min(), a.max()). @@ -432,9 +545,10 @@ class HistogramParams(DefaultDependency): range affects the automatic bin computation as well. link: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html - """, - example="0,1000", - ) + """, + examples="0,1000", + ), + ] = None def __post_init__(self): """Post Init.""" @@ -448,15 +562,22 @@ def __post_init__(self): self.bins = 10 if self.range: - self.range = list(map(float, self.range.split(","))) # type: ignore + parsed = list(map(float, self.range.split(","))) + assert ( + len(parsed) == 2 + ), f"Invalid histogram_range values: {self.range}, should be of form 'min,max'" + + self.range = parsed # type: ignore def CoordCRSParams( - crs: str = Query( - None, - alias="coord-crs", - description="Coordinate Reference System of the input coords. Default to `epsg:4326`.", - ) + crs: Annotated[ + Optional[str], + Query( + alias="coord_crs", + description="Coordinate Reference System of the input coords. Default to `epsg:4326`.", + ), + ] = None, ) -> Optional[CRS]: """Coordinate Reference System Coordinates Param.""" if crs: @@ -466,14 +587,81 @@ def CoordCRSParams( def DstCRSParams( - crs: str = Query( - None, - alias="dst-crs", - description="Output Coordinate Reference System.", - ) + crs: Annotated[ + Optional[str], + Query( + alias="dst_crs", + description="Output Coordinate Reference System.", + ), + ] = None, +) -> Optional[CRS]: + """Coordinate Reference System Coordinates Param.""" + if crs: + return CRS.from_user_input(crs) + + return None + + +def CRSParams( + crs: Annotated[ + Optional[str], + Query( + description="Coordinate Reference System.", + ), + ] = None, ) -> Optional[CRS]: """Coordinate Reference System Coordinates Param.""" if crs: return CRS.from_user_input(crs) return None + + +def BufferParams( + buffer: Annotated[ + Optional[float], + Query( + gt=0, + title="Tile buffer.", + description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", + ), + ] = None, +) -> Optional[float]: + """Tile buffer Parameter.""" + return buffer + + +def ColorFormulaParams( + color_formula: Annotated[ + Optional[str], + Query( + title="Color Formula", + description="rio-color formula (info: https://github.com/mapbox/rio-color)", + ), + ] = None, +) -> Optional[str]: + """ColorFormula Parameter.""" + return color_formula + + +@dataclass +class TileParams(DefaultDependency): + """Tile options.""" + + buffer: Annotated[ + Optional[float], + Query( + gt=0, + title="Tile buffer.", + description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", + ), + ] = None + + padding: Annotated[ + Optional[int], + Query( + gt=0, + title="Tile padding.", + description="Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`.", + ), + ] = None diff --git a/src/titiler/core/titiler/core/errors.py b/src/titiler/core/titiler/core/errors.py index 141aadcc7..678aa01ac 100644 --- a/src/titiler/core/titiler/core/errors.py +++ b/src/titiler/core/titiler/core/errors.py @@ -15,7 +15,7 @@ ) from starlette import status from starlette.requests import Request -from starlette.responses import JSONResponse +from starlette.responses import JSONResponse, Response class TilerError(Exception): @@ -52,6 +52,9 @@ def exception_handler_factory(status_code: int) -> Callable: """ def handler(request: Request, exc: Exception): + if status_code == status.HTTP_204_NO_CONTENT: + return Response(content=None, status_code=204) + return JSONResponse(content={"detail": str(exc)}, status_code=status_code) return handler diff --git a/src/titiler/core/titiler/core/factory.py b/src/titiler/core/titiler/core/factory.py index 52e16a3d6..bdd2276cd 100644 --- a/src/titiler/core/titiler/core/factory.py +++ b/src/titiler/core/titiler/core/factory.py @@ -1,30 +1,45 @@ """TiTiler Router factories.""" import abc -from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Type, Union +from typing import ( + Any, + Callable, + Dict, + List, + Literal, + Optional, + Sequence, + Tuple, + Type, + Union, +) from urllib.parse import urlencode import jinja2 +import numpy import rasterio +from attrs import define, field from fastapi import APIRouter, Body, Depends, Path, Query from fastapi.dependencies.utils import get_parameterless_sub_dependant from fastapi.params import Depends as DependsFunc from geojson_pydantic.features import Feature, FeatureCollection -from geojson_pydantic.geometries import Polygon +from geojson_pydantic.geometries import MultiPolygon, Polygon from morecantile import TileMatrixSet from morecantile import tms as morecantile_tms from morecantile.defaults import TileMatrixSets -from rasterio.crs import CRS +from pydantic import Field +from rio_tiler.colormap import ColorMaps +from rio_tiler.colormap import cmap as default_cmap from rio_tiler.constants import WGS84_CRS from rio_tiler.io import BaseReader, MultiBandReader, MultiBaseReader, Reader -from rio_tiler.models import BandStatistics, Bounds, Info +from rio_tiler.models import Bounds, ImageData, Info from rio_tiler.types import ColorMapType -from rio_tiler.utils import get_array_statistics +from rio_tiler.utils import CRS_to_uri from starlette.requests import Request from starlette.responses import HTMLResponse, Response -from starlette.routing import Match, compile_path, replace_params +from starlette.routing import Match, NoMatchFound, compile_path, replace_params from starlette.templating import Jinja2Templates +from typing_extensions import Annotated from titiler.core.algorithm import AlgorithmMetadata, Algorithms, BaseAlgorithm from titiler.core.algorithm import algorithms as available_algorithms @@ -37,22 +52,27 @@ BandsExprParamsOptional, BandsParams, BidxExprParams, + ColorFormulaParams, ColorMapParams, CoordCRSParams, + CRSParams, DatasetParams, DatasetPathParams, DefaultDependency, DstCRSParams, HistogramParams, - ImageParams, ImageRenderingParams, + PartFeatureParams, + PreviewParams, RescaleType, RescalingParams, StatisticsParams, + TileParams, ) from titiler.core.models.mapbox import TileJSON -from titiler.core.models.OGC import TileMatrixSetList +from titiler.core.models.OGC import TileMatrixSetList, TileSet, TileSetList from titiler.core.models.responses import ( + ColorMapsList, InfoGeoJSON, MultiBaseInfo, MultiBaseInfoGeoJSON, @@ -62,15 +82,15 @@ Statistics, StatisticsGeoJSON, ) -from titiler.core.resources.enums import ImageType, MediaType, OptionalHeader +from titiler.core.resources.enums import ImageType from titiler.core.resources.responses import GeoJSONResponse, JSONResponse, XMLResponse from titiler.core.routing import EndpointScope +from titiler.core.utils import render_image -DEFAULT_TEMPLATES = Jinja2Templates( - directory="", - loader=jinja2.ChoiceLoader([jinja2.PackageLoader(__package__, "templates")]), -) # type:ignore - +jinja2_env = jinja2.Environment( + loader=jinja2.ChoiceLoader([jinja2.PackageLoader(__package__, "templates")]) +) +DEFAULT_TEMPLATES = Jinja2Templates(env=jinja2_env) img_endpoint_params: Dict[str, Any] = { "responses": { @@ -91,93 +111,45 @@ } -@dataclass # type: ignore +@define class FactoryExtension(metaclass=abc.ABCMeta): """Factory Extension.""" @abc.abstractmethod - def register(self, factory: "BaseTilerFactory"): + def register(self, factory: "BaseFactory"): """Register extension to the factory.""" ... -# ref: https://github.com/python/mypy/issues/5374 -@dataclass # type: ignore -class BaseTilerFactory(metaclass=abc.ABCMeta): - """BaseTiler Factory. +@define(kw_only=True) +class BaseFactory(metaclass=abc.ABCMeta): + """Base Factory. Abstract Base Class which defines most inputs used by dynamic tiler. Attributes: - reader (rio_tiler.io.base.BaseReader): A rio-tiler reader (e.g Reader). router (fastapi.APIRouter): Application router to register endpoints to. - path_dependency (Callable): Endpoint dependency defining `path` to pass to the reader init. - dataset_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining dataset overwriting options (e.g nodata). - layer_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining dataset indexes/bands/assets options. - render_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining image rendering options (e.g add_mask). - colormap_dependency (Callable): Endpoint dependency defining ColorMap options (e.g colormap_name). - process_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining image post-processing options (e.g rescaling, color-formula). - tms_dependency (Callable): Endpoint dependency defining TileMatrixSet to use. - reader_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining BaseReader options. - environment_dependency (Callable): Endpoint dependency to define GDAL environment at runtime. router_prefix (str): prefix where the router will be mounted in the application. - optional_headers(sequence of titiler.core.resources.enums.OptionalHeader): additional headers to return with the response. + route_dependencies (list): Additional routes dependencies to add after routes creations. """ - reader: Type[BaseReader] - # FastAPI router - router: APIRouter = field(default_factory=APIRouter) - - # Path Dependency - path_dependency: Callable[..., Any] = DatasetPathParams - - # Rasterio Dataset Options (nodata, unscale, resampling) - dataset_dependency: Type[DefaultDependency] = DatasetParams - - # Indexes/Expression Dependencies - layer_dependency: Type[DefaultDependency] = BidxExprParams - - # Image rendering Dependencies - render_dependency: Type[DefaultDependency] = ImageRenderingParams - colormap_dependency: Callable[..., Optional[ColorMapType]] = ColorMapParams - - rescale_dependency: Callable[..., Optional[RescaleType]] = RescalingParams - - # Post Processing Dependencies (algorithm) - process_dependency: Callable[ - ..., Optional[BaseAlgorithm] - ] = available_algorithms.dependency - - # Reader dependency - reader_dependency: Type[DefaultDependency] = DefaultDependency - - # GDAL ENV dependency - environment_dependency: Callable[..., Dict] = field(default=lambda: {}) - - # TileMatrixSet dependency - supported_tms: TileMatrixSets = morecantile_tms - default_tms: str = "WebMercatorQuad" + router: APIRouter = field(factory=APIRouter) # Router Prefix is needed to find the path for /tile if the TilerFactory.router is mounted # with other router (multiple `.../tile` routes). # e.g if you mount the route with `/cog` prefix, set router_prefix to cog and router_prefix: str = "" - # add additional headers in response - optional_headers: List[OptionalHeader] = field(default_factory=list) - # add dependencies to specific routes route_dependencies: List[Tuple[List[EndpointScope], List[DependsFunc]]] = field( - default_factory=list + factory=list ) - extensions: List[FactoryExtension] = field(default_factory=list) - - templates: Jinja2Templates = DEFAULT_TEMPLATES + extensions: List[FactoryExtension] = field(factory=list) - def __post_init__(self): + def __attrs_post_init__(self): """Post Init: register route and configure specific options.""" # Register endpoints self.register_routes() @@ -192,7 +164,7 @@ def __post_init__(self): @abc.abstractmethod def register_routes(self): - """Register Tiler Routes.""" + """Register Routes.""" ... def url_for(self, request: Request, name: str, **path_params: Any) -> str: @@ -206,7 +178,7 @@ def url_for(self, request: Request, name: str, **path_params: Any) -> str: if "{" in prefix: _, path_format, param_convertors = compile_path(prefix) prefix, _ = replace_params( - path_format, param_convertors, request.path_params + path_format, param_convertors, request.path_params.copy() ) base_url += prefix @@ -235,7 +207,8 @@ def add_route_dependencies( route.dependant.dependencies.insert( # type: ignore 0, get_parameterless_sub_dependant( - depends=depends, path=route.path_format # type: ignore + depends=depends, + path=route.path_format, # type: ignore ), ) @@ -246,17 +219,31 @@ def add_route_dependencies( route.dependencies.extend(dependencies) # type: ignore -@dataclass -class TilerFactory(BaseTilerFactory): +@define(kw_only=True) +class TilerFactory(BaseFactory): """Tiler Factory. Attributes: reader (rio_tiler.io.base.BaseReader): A rio-tiler reader. Defaults to `rio_tiler.io.Reader`. + reader_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining BaseReader options. + path_dependency (Callable): Endpoint dependency defining `path` to pass to the reader init. + layer_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining dataset indexes/bands/assets options. + dataset_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining dataset overwriting options (e.g nodata). + tile_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining tile options (e.g buffer, padding). stats_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining options for rio-tiler's statistics method. histogram_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining options for numpy's histogram method. - img_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining options for rio-tiler's preview/crop method. + img_preview_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining options for rio-tiler's preview method. + img_part_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining options for rio-tiler's part/feature methods. + process_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining image post-processing options (e.g rescaling, color-formula). + rescale_dependency (Callable[..., Optional[RescaleType]]): + color_formula_dependency (Callable[..., Optional[str]]): + colormap_dependency (Callable): Endpoint dependency defining ColorMap options (e.g colormap_name). + render_dependency (titiler.core.dependencies.DefaultDependency): Endpoint dependency defining image rendering options (e.g add_mask). + environment_dependency (Callable): Endpoint dependency to define GDAL environment at runtime. + supported_tms (morecantile.defaults.TileMatrixSets): TileMatrixSets object holding the supported TileMatrixSets. + templates (Jinja2Templates): Jinja2 templates. add_preview (bool): add `/preview` endpoints. Defaults to True. - add_part (bool): add `/crop` endpoints. Defaults to True. + add_part (bool): add `/bbox` and `/feature` endpoints. Defaults to True. add_viewer (bool): add `/map` endpoints. Defaults to True. """ @@ -264,12 +251,47 @@ class TilerFactory(BaseTilerFactory): # Default reader is set to rio_tiler.io.Reader reader: Type[BaseReader] = Reader + # Reader dependency + reader_dependency: Type[DefaultDependency] = DefaultDependency + + # Path Dependency + path_dependency: Callable[..., Any] = DatasetPathParams + + # Indexes/Expression Dependencies + layer_dependency: Type[DefaultDependency] = BidxExprParams + + # Rasterio Dataset Options (nodata, unscale, resampling, reproject) + dataset_dependency: Type[DefaultDependency] = DatasetParams + + # Tile/Tilejson/WMTS Dependencies + tile_dependency: Type[DefaultDependency] = TileParams + # Statistics/Histogram Dependencies stats_dependency: Type[DefaultDependency] = StatisticsParams histogram_dependency: Type[DefaultDependency] = HistogramParams # Crop/Preview endpoints Dependencies - img_dependency: Type[DefaultDependency] = ImageParams + img_preview_dependency: Type[DefaultDependency] = PreviewParams + img_part_dependency: Type[DefaultDependency] = PartFeatureParams + + # Post Processing Dependencies (algorithm) + process_dependency: Callable[ + ..., Optional[BaseAlgorithm] + ] = available_algorithms.dependency + + # Image rendering Dependencies + rescale_dependency: Callable[..., Optional[RescaleType]] = RescalingParams + color_formula_dependency: Callable[..., Optional[str]] = ColorFormulaParams + colormap_dependency: Callable[..., Optional[ColorMapType]] = ColorMapParams + render_dependency: Type[DefaultDependency] = ImageRenderingParams + + # GDAL ENV dependency + environment_dependency: Callable[..., Dict] = field(default=lambda: {}) + + # TileMatrixSet dependency + supported_tms: TileMatrixSets = morecantile_tms + + templates: Jinja2Templates = DEFAULT_TEMPLATES # Add/Remove some endpoints add_preview: bool = True @@ -290,9 +312,12 @@ def register_routes(self): self.bounds() self.info() self.statistics() + self.tilesets() self.tile() - self.tilejson() + if self.add_viewer: + self.map_viewer() self.wmts() + self.tilejson() self.point() # Optional Routes @@ -302,9 +327,6 @@ def register_routes(self): if self.add_part: self.part() - if self.add_viewer: - self.map_viewer() - ############################################################################ # /bounds ############################################################################ @@ -319,12 +341,17 @@ def bounds(self): def bounds( src_path=Depends(self.path_dependency), reader_params=Depends(self.reader_dependency), + crs=Depends(CRSParams), env=Depends(self.environment_dependency), ): """Return the bounds of the COG.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - return {"bounds": src_dst.geographic_bounds} + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + crs = crs or WGS84_CRS + return { + "bounds": src_dst.get_geographic_bounds(crs or WGS84_CRS), + "crs": CRS_to_uri(crs) or crs.to_wkt(), + } ############################################################################ # /info @@ -346,7 +373,7 @@ def info( ): """Return dataset's basic info.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: return src_dst.info() @self.router.get( @@ -364,14 +391,27 @@ def info( def info_geojson( src_path=Depends(self.path_dependency), reader_params=Depends(self.reader_dependency), + crs=Depends(CRSParams), env=Depends(self.environment_dependency), ): """Return dataset's basic info as a GeoJSON feature.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + bounds = src_dst.get_geographic_bounds(crs or WGS84_CRS) + if bounds[0] > bounds[2]: + pl = Polygon.from_bounds(-180, bounds[1], bounds[2], bounds[3]) + pr = Polygon.from_bounds(bounds[0], bounds[1], 180, bounds[3]) + geometry = MultiPolygon( + type="MultiPolygon", + coordinates=[pl.coordinates, pr.coordinates], + ) + else: + geometry = Polygon.from_bounds(*bounds) + return Feature( type="Feature", - geometry=Polygon.from_bounds(*src_dst.geographic_bounds), + bbox=bounds, + geometry=geometry, properties=src_dst.info(), ) @@ -395,23 +435,30 @@ def statistics(self): ) def statistics( src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_preview_dependency), + post_process=Depends(self.process_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Dataset statistics.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - return src_dst.statistics( - **layer_params, - **image_params, - **dataset_params, - **stats_params, - hist_options={**histogram_params}, + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + image = src_dst.preview( + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), + ) + + if post_process: + image = post_process(image) + + return image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) # POST endpoint @@ -422,23 +469,26 @@ def statistics( response_class=GeoJSONResponse, responses={ 200: { - "content": {"application/json": {}}, - "description": "Return dataset's statistics.", + "content": {"application/geo+json": {}}, + "description": "Return dataset's statistics from feature or featureCollection.", } }, ) def geojson_statistics( - geojson: Union[FeatureCollection, Feature] = Body( - ..., description="GeoJSON Feature or FeatureCollection." - ), + geojson: Annotated[ + Union[FeatureCollection, Feature], + Body(description="GeoJSON Feature or FeatureCollection."), + ], src_path=Depends(self.path_dependency), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), + coord_crs=Depends(CoordCRSParams), + dst_crs=Depends(DstCRSParams), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_part_dependency), + post_process=Depends(self.process_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Statistics from a geojson feature or featureCollection.""" @@ -447,34 +497,261 @@ def geojson_statistics( fc = FeatureCollection(type="FeatureCollection", features=[geojson]) with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: for feature in fc: - data = src_dst.feature( - feature.dict(exclude_none=True), + shape = feature.model_dump(exclude_none=True) + image = src_dst.feature( + shape, + shape_crs=coord_crs or WGS84_CRS, + dst_crs=dst_crs, + align_bounds_with_dataset=True, + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), + ) + + # Get the coverage % array + coverage_array = image.get_coverage_array( + shape, shape_crs=coord_crs or WGS84_CRS, - **layer_params, - **image_params, - **dataset_params, ) - stats = get_array_statistics( - data.as_masked(), - **stats_params, - **histogram_params, + + if post_process: + image = post_process(image) + + stats = image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), + coverage=coverage_array, ) feature.properties = feature.properties or {} - feature.properties.update( + feature.properties.update({"statistics": stats}) + + return fc.features[0] if isinstance(geojson, Feature) else fc + + ############################################################################ + # /tileset + ############################################################################ + def tilesets(self): + """Register OGC tilesets endpoints.""" + + @self.router.get( + "/tiles", + response_model=TileSetList, + response_class=JSONResponse, + response_model_exclude_none=True, + responses={ + 200: { + "content": { + "application/json": {}, + } + } + }, + summary="Retrieve a list of available raster tilesets for the specified dataset.", + ) + async def tileset_list( + request: Request, + src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), + crs=Depends(CRSParams), + env=Depends(self.environment_dependency), + ): + """Retrieve a list of available raster tilesets for the specified dataset.""" + with rasterio.Env(**env): + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + bounds = src_dst.get_geographic_bounds(crs or WGS84_CRS) + + collection_bbox = { + "lowerLeft": [bounds[0], bounds[1]], + "upperRight": [bounds[2], bounds[3]], + "crs": CRS_to_uri(crs or WGS84_CRS), + } + + qs = [ + (key, value) + for (key, value) in request.query_params._list + if key.lower() not in ["crs"] + ] + query_string = f"?{urlencode(qs)}" if qs else "" + + tilesets = [] + for tms in self.supported_tms.list(): + tileset = { + "title": f"tileset tiled using {tms} TileMatrixSet", + "dataType": "map", + "crs": self.supported_tms.get(tms).crs, + "boundingBox": collection_bbox, + "links": [ + { + "href": self.url_for( + request, "tileset", tileMatrixSetId=tms + ) + + query_string, + "rel": "self", + "type": "application/json", + "title": f"Tileset tiled using {tms} TileMatrixSet", + }, + { + "href": self.url_for( + request, + "tile", + tileMatrixSetId=tms, + z="{z}", + x="{x}", + y="{y}", + ) + + query_string, + "rel": "tile", + "title": "Templated link for retrieving Raster tiles", + }, + ], + } + + try: + tileset["links"].append( + { + "href": str( + request.url_for("tilematrixset", tileMatrixSetId=tms) + ), + "rel": "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes", + "type": "application/json", + "title": f"Definition of '{tms}' tileMatrixSet", + } + ) + except NoMatchFound: + pass + + tilesets.append(tileset) + + data = TileSetList.model_validate({"tilesets": tilesets}) + return data + + @self.router.get( + "/tiles/{tileMatrixSetId}", + response_model=TileSet, + response_class=JSONResponse, + response_model_exclude_none=True, + responses={200: {"content": {"application/json": {}}}}, + summary="Retrieve the raster tileset metadata for the specified dataset and tiling scheme (tile matrix set).", + ) + async def tileset( + request: Request, + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path( + description="Identifier selecting one of the TileMatrixSetId supported." + ), + ], + src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), + env=Depends(self.environment_dependency), + ): + """Retrieve the raster tileset metadata for the specified dataset and tiling scheme (tile matrix set).""" + tms = self.supported_tms.get(tileMatrixSetId) + with rasterio.Env(**env): + with self.reader( + src_path, tms=tms, **reader_params.as_dict() + ) as src_dst: + bounds = src_dst.get_geographic_bounds(tms.rasterio_geographic_crs) + minzoom = src_dst.minzoom + maxzoom = src_dst.maxzoom + + collection_bbox = { + "lowerLeft": [bounds[0], bounds[1]], + "upperRight": [bounds[2], bounds[3]], + "crs": CRS_to_uri(tms.rasterio_geographic_crs), + } + + tilematrix_limit = [] + for zoom in range(minzoom, maxzoom + 1, 1): + matrix = tms.matrix(zoom) + ulTile = tms.tile(bounds[0], bounds[3], int(matrix.id)) + lrTile = tms.tile(bounds[2], bounds[1], int(matrix.id)) + minx, maxx = (min(ulTile.x, lrTile.x), max(ulTile.x, lrTile.x)) + miny, maxy = (min(ulTile.y, lrTile.y), max(ulTile.y, lrTile.y)) + tilematrix_limit.append( { - "statistics": { - f"{data.band_names[ix]}": BandStatistics( - **stats[ix] - ) - for ix in range(len(stats)) - } + "tileMatrix": matrix.id, + "minTileRow": max(miny, 0), + "maxTileRow": min(maxy, matrix.matrixHeight), + "minTileCol": max(minx, 0), + "maxTileCol": min(maxx, matrix.matrixWidth), } ) - return fc.features[0] if isinstance(geojson, Feature) else fc + qs = [(key, value) for (key, value) in request.query_params._list] + query_string = f"?{urlencode(qs)}" if qs else "" + + links = [ + { + "href": self.url_for( + request, + "tileset", + tileMatrixSetId=tileMatrixSetId, + ), + "rel": "self", + "type": "application/json", + "title": f"Tileset tiled using {tileMatrixSetId} TileMatrixSet", + }, + { + "href": self.url_for( + request, + "tile", + tileMatrixSetId=tileMatrixSetId, + z="{z}", + x="{x}", + y="{y}", + ) + + query_string, + "rel": "tile", + "title": "Templated link for retrieving Raster tiles", + "templated": True, + }, + ] + try: + links.append( + { + "href": str( + request.url_for( + "tilematrixset", tileMatrixSetId=tileMatrixSetId + ) + ), + "rel": "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes", + "type": "application/json", + "title": f"Definition of '{tileMatrixSetId}' tileMatrixSet", + } + ) + except NoMatchFound: + pass + + if self.add_viewer: + links.append( + { + "href": self.url_for( + request, + "map_viewer", + tileMatrixSetId=tileMatrixSetId, + ) + + query_string, + "type": "text/html", + "rel": "data", + "title": f"Map viewer for '{tileMatrixSetId}' tileMatrixSet", + } + ) + + data = TileSet.model_validate( + { + "title": f"tileset tiled using {tileMatrixSetId} TileMatrixSet", + "dataType": "map", + "crs": tms.crs, + "boundingBox": collection_bbox, + "links": links, + "tileMatrixSetLimits": tilematrix_limit, + } + ) + + return data ############################################################################ # /tiles @@ -482,68 +759,78 @@ def geojson_statistics( def tile(self): # noqa: C901 """Register /tiles endpoint.""" - @self.router.get(r"/tiles/{z}/{x}/{y}", **img_endpoint_params) - @self.router.get(r"/tiles/{z}/{x}/{y}.{format}", **img_endpoint_params) - @self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x", **img_endpoint_params) - @self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params) - @self.router.get(r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params) + @self.router.get(r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params ) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params ) @self.router.get( - r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", + r"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params, ) def tile( - z: int = Path(..., ge=0, le=30, description="TMS tiles's zoom level"), - x: int = Path(..., description="TMS tiles's column"), - y: int = Path(..., description="TMS tiles's row"), - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( - self.default_tms, - description=f"TileMatrixSet Name (default: '{self.default_tms}')", - ), - scale: int = Query( - 1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." - ), - format: ImageType = Query( - None, description="Output image type. Default is auto." - ), + z: Annotated[ + int, + Path( + description="Identifier (Z) selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile.", + ), + ], + x: Annotated[ + int, + Path( + description="Column (X) index of the tile on the selected TileMatrix. It cannot exceed the MatrixHeight-1 for the selected TileMatrix.", + ), + ], + y: Annotated[ + int, + Path( + description="Row (Y) index of the tile on the selected TileMatrix. It cannot exceed the MatrixWidth-1 for the selected TileMatrix.", + ), + ], + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path( + description="Identifier selecting one of the TileMatrixSetId supported." + ), + ], + scale: Annotated[ + int, + Field( + gt=0, le=4, description="Tile size scale. 1=256x256, 2=512x512..." + ), + ] = 1, + format: Annotated[ + ImageType, + "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ] = None, src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), + tile_params=Depends(self.tile_dependency), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - buffer: Optional[float] = Query( - None, - gt=0, - title="Tile buffer.", - description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", - ), post_process=Depends(self.process_dependency), rescale=Depends(self.rescale_dependency), - color_formula: Optional[str] = Query( - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), + color_formula=Depends(self.color_formula_dependency), colormap=Depends(self.colormap_dependency), render_params=Depends(self.render_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Create map tile from a dataset.""" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): - with self.reader(src_path, tms=tms, **reader_params) as src_dst: + with self.reader( + src_path, tms=tms, **reader_params.as_dict() + ) as src_dst: image = src_dst.tile( x, y, z, tilesize=scale * 256, - buffer=buffer, - **layer_params, - **dataset_params, + **tile_params.as_dict(), + **layer_params.as_dict(), + **dataset_params.as_dict(), ) dst_colormap = getattr(src_dst, "colormap", None) @@ -556,72 +843,62 @@ def tile( if color_formula: image.apply_color_formula(color_formula) - if cmap := colormap or dst_colormap: - image = image.apply_colormap(cmap) - - if not format: - format = ImageType.jpeg if image.mask.all() else ImageType.png - - content = image.render( - img_format=format.driver, - **format.profile, - **render_params, + content, media_type = render_image( + image, + output_format=format, + colormap=colormap or dst_colormap, + **render_params.as_dict(), ) - return Response(content, media_type=format.mediatype) + return Response(content, media_type=media_type) def tilejson(self): # noqa: C901 """Register /tilejson.json endpoint.""" @self.router.get( - "/tilejson.json", - response_model=TileJSON, - responses={200: {"description": "Return a tilejson"}}, - response_model_exclude_none=True, - ) - @self.router.get( - "/{TileMatrixSetId}/tilejson.json", + "/{tileMatrixSetId}/tilejson.json", response_model=TileJSON, responses={200: {"description": "Return a tilejson"}}, response_model_exclude_none=True, ) def tilejson( request: Request, - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( - self.default_tms, - description=f"TileMatrixSet Name (default: '{self.default_tms}')", - ), + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path( + description="Identifier selecting one of the TileMatrixSetId supported." + ), + ], + tile_format: Annotated[ + Optional[ImageType], + Query( + description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ), + ] = None, + tile_scale: Annotated[ + int, + Query( + gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." + ), + ] = 1, + minzoom: Annotated[ + Optional[int], + Query(description="Overwrite default minzoom."), + ] = None, + maxzoom: Annotated[ + Optional[int], + Query(description="Overwrite default maxzoom."), + ] = None, src_path=Depends(self.path_dependency), - tile_format: Optional[ImageType] = Query( - None, description="Output image type. Default is auto." - ), - tile_scale: int = Query( - 1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." - ), - minzoom: Optional[int] = Query( - None, description="Overwrite default minzoom." - ), - maxzoom: Optional[int] = Query( - None, description="Overwrite default maxzoom." - ), - layer_params=Depends(self.layer_dependency), # noqa - dataset_params=Depends(self.dataset_dependency), # noqa - buffer: Optional[float] = Query( # noqa - None, - gt=0, - title="Tile buffer.", - description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", - ), - post_process=Depends(self.process_dependency), # noqa - rescale=Depends(self.rescale_dependency), # noqa - color_formula: Optional[str] = Query( # noqa - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), - colormap=Depends(self.colormap_dependency), # noqa - render_params=Depends(self.render_dependency), # noqa reader_params=Depends(self.reader_dependency), + tile_params=Depends(self.tile_dependency), + layer_params=Depends(self.layer_dependency), + dataset_params=Depends(self.dataset_dependency), + post_process=Depends(self.process_dependency), + rescale=Depends(self.rescale_dependency), + color_formula=Depends(self.color_formula_dependency), + colormap=Depends(self.colormap_dependency), + render_params=Depends(self.render_dependency), env=Depends(self.environment_dependency), ): """Return TileJSON document for a dataset.""" @@ -630,7 +907,7 @@ def tilejson( "x": "{x}", "y": "{y}", "scale": tile_scale, - "TileMatrixSetId": TileMatrixSetId, + "tileMatrixSetId": tileMatrixSetId, } if tile_format: route_params["format"] = tile_format.value @@ -651,11 +928,15 @@ def tilejson( if qs: tiles_url += f"?{urlencode(qs)}" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): - with self.reader(src_path, tms=tms, **reader_params) as src_dst: + with self.reader( + src_path, tms=tms, **reader_params.as_dict() + ) as src_dst: return { - "bounds": src_dst.geographic_bounds, + "bounds": src_dst.get_geographic_bounds( + tms.rasterio_geographic_crs + ), "minzoom": minzoom if minzoom is not None else src_dst.minzoom, "maxzoom": maxzoom if maxzoom is not None else src_dst.maxzoom, "tiles": [tiles_url], @@ -664,62 +945,62 @@ def tilejson( def map_viewer(self): # noqa: C901 """Register /map endpoint.""" - @self.router.get("/map", response_class=HTMLResponse) - @self.router.get("/{TileMatrixSetId}/map", response_class=HTMLResponse) + @self.router.get("/{tileMatrixSetId}/map", response_class=HTMLResponse) def map_viewer( request: Request, + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path( + description="Identifier selecting one of the TileMatrixSetId supported." + ), + ], + tile_format: Annotated[ + Optional[ImageType], + Query( + description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ), + ] = None, + tile_scale: Annotated[ + int, + Query( + gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." + ), + ] = 1, + minzoom: Annotated[ + Optional[int], + Query(description="Overwrite default minzoom."), + ] = None, + maxzoom: Annotated[ + Optional[int], + Query(description="Overwrite default maxzoom."), + ] = None, src_path=Depends(self.path_dependency), - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( - self.default_tms, - description=f"TileMatrixSet Name (default: '{self.default_tms}')", - ), # noqa - tile_format: Optional[ImageType] = Query( - None, description="Output image type. Default is auto." - ), # noqa - tile_scale: int = Query( - 1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." - ), # noqa - minzoom: Optional[int] = Query( - None, description="Overwrite default minzoom." - ), # noqa - maxzoom: Optional[int] = Query( - None, description="Overwrite default maxzoom." - ), # noqa - layer_params=Depends(self.layer_dependency), # noqa - dataset_params=Depends(self.dataset_dependency), # noqa - buffer: Optional[float] = Query( # noqa - None, - gt=0, - title="Tile buffer.", - description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", - ), - post_process=Depends(self.process_dependency), # noqa - rescale=Depends(self.rescale_dependency), # noqa - color_formula: Optional[str] = Query( # noqa - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), - colormap=Depends(self.colormap_dependency), # noqa - render_params=Depends(self.render_dependency), # noqa - reader_params=Depends(self.reader_dependency), # noqa - env=Depends(self.environment_dependency), # noqa + reader_params=Depends(self.reader_dependency), + tile_params=Depends(self.tile_dependency), + layer_params=Depends(self.layer_dependency), + dataset_params=Depends(self.dataset_dependency), + post_process=Depends(self.process_dependency), + rescale=Depends(self.rescale_dependency), + color_formula=Depends(self.color_formula_dependency), + colormap=Depends(self.colormap_dependency), + render_params=Depends(self.render_dependency), + env=Depends(self.environment_dependency), ): """Return TileJSON document for a dataset.""" tilejson_url = self.url_for( - request, "tilejson", TileMatrixSetId=TileMatrixSetId + request, "tilejson", tileMatrixSetId=tileMatrixSetId ) if request.query_params._list: tilejson_url += f"?{urlencode(request.query_params._list)}" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) return self.templates.TemplateResponse( + request, name="map.html", context={ - "request": request, "tilejson_endpoint": tilejson_url, "tms": tms, - "resolutions": [tms._resolution(matrix) for matrix in tms], + "resolutions": [matrix.cellSize for matrix in tms], }, media_type="text/html", ) @@ -727,47 +1008,51 @@ def map_viewer( def wmts(self): # noqa: C901 """Register /wmts endpoint.""" - @self.router.get("/WMTSCapabilities.xml", response_class=XMLResponse) @self.router.get( - "/{TileMatrixSetId}/WMTSCapabilities.xml", response_class=XMLResponse + "/{tileMatrixSetId}/WMTSCapabilities.xml", response_class=XMLResponse ) def wmts( request: Request, - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query( - self.default_tms, - description=f"TileMatrixSet Name (default: '{self.default_tms}')", - ), + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path( + description="Identifier selecting one of the TileMatrixSetId supported." + ), + ], + tile_format: Annotated[ + ImageType, + Query(description="Output image type. Default is png."), + ] = ImageType.png, + tile_scale: Annotated[ + int, + Query( + gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." + ), + ] = 1, + minzoom: Annotated[ + Optional[int], + Query(description="Overwrite default minzoom."), + ] = None, + maxzoom: Annotated[ + Optional[int], + Query(description="Overwrite default maxzoom."), + ] = None, + use_epsg: Annotated[ + bool, + Query( + description="Use EPSG code, not opengis.net, for the ows:SupportedCRS in the TileMatrixSet (set to True to enable ArcMap compatability)" + ), + ] = False, src_path=Depends(self.path_dependency), - tile_format: ImageType = Query( - ImageType.png, description="Output image type. Default is png." - ), - tile_scale: int = Query( - 1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..." - ), - minzoom: Optional[int] = Query( - None, description="Overwrite default minzoom." - ), - maxzoom: Optional[int] = Query( - None, description="Overwrite default maxzoom." - ), - layer_params=Depends(self.layer_dependency), # noqa - dataset_params=Depends(self.dataset_dependency), # noqa - buffer: Optional[float] = Query( # noqa - None, - gt=0, - title="Tile buffer.", - description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", - ), - post_process=Depends(self.process_dependency), # noqa - rescale=Depends(self.rescale_dependency), # noqa - color_formula: Optional[str] = Query( # noqa - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), - colormap=Depends(self.colormap_dependency), # noqa - render_params=Depends(self.render_dependency), # noqa reader_params=Depends(self.reader_dependency), + tile_params=Depends(self.tile_dependency), + layer_params=Depends(self.layer_dependency), + dataset_params=Depends(self.dataset_dependency), + post_process=Depends(self.process_dependency), + rescale=Depends(self.rescale_dependency), + color_formula=Depends(self.color_formula_dependency), + colormap=Depends(self.colormap_dependency), + render_params=Depends(self.render_dependency), env=Depends(self.environment_dependency), ): """OGC WMTS endpoint.""" @@ -777,7 +1062,7 @@ def wmts( "y": "{TileRow}", "scale": tile_scale, "format": tile_format.value, - "TileMatrixSetId": TileMatrixSetId, + "tileMatrixSetId": tileMatrixSetId, } tiles_url = self.url_for(request, "tile", **route_params) @@ -788,6 +1073,7 @@ def wmts( "minzoom", "maxzoom", "service", + "use_epsg", "request", ] qs = [ @@ -795,13 +1081,13 @@ def wmts( for (key, value) in request.query_params._list if key.lower() not in qs_key_to_remove ] - if qs: - tiles_url += f"?{urlencode(qs)}" - tms = self.supported_tms.get(TileMatrixSetId) + tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): - with self.reader(src_path, tms=tms, **reader_params) as src_dst: - bounds = src_dst.geographic_bounds + with self.reader( + src_path, tms=tms, **reader_params.as_dict() + ) as src_dst: + bounds = src_dst.get_geographic_bounds(tms.rasterio_geographic_crs) minzoom = minzoom if minzoom is not None else src_dst.minzoom maxzoom = maxzoom if maxzoom is not None else src_dst.maxzoom @@ -810,9 +1096,9 @@ def wmts( matrix = tms.matrix(zoom) tm = f""" - {matrix.identifier} + {matrix.id} {matrix.scaleDenominator} - {matrix.topLeftCorner[0]} {matrix.topLeftCorner[1]} + {matrix.pointOfOrigin[0]} {matrix.pointOfOrigin[1]} {matrix.tileWidth} {matrix.tileHeight} {matrix.matrixWidth} @@ -820,19 +1106,40 @@ def wmts( """ tileMatrix.append(tm) - return self.templates.TemplateResponse( - "wmts.xml", + if use_epsg: + supported_crs = f"EPSG:{tms.crs.to_epsg()}" + else: + supported_crs = tms.crs.srs + + layers = [ { - "request": request, - "tiles_endpoint": tiles_url, + "title": src_path if isinstance(src_path, str) else "TiTiler", + "name": "default", + "tiles_url": tiles_url, + "query_string": urlencode(qs, doseq=True) if qs else None, "bounds": bounds, + }, + ] + + bbox_crs_type = "WGS84BoundingBox" + bbox_crs_uri = "urn:ogc:def:crs:OGC:2:84" + if tms.rasterio_geographic_crs != WGS84_CRS: + bbox_crs_type = "BoundingBox" + bbox_crs_uri = CRS_to_uri(tms.rasterio_geographic_crs) + + return self.templates.TemplateResponse( + request, + name="wmts.xml", + context={ + "tileMatrixSetId": tms.id, "tileMatrix": tileMatrix, - "tms": tms, - "title": "Cloud Optimized GeoTIFF", - "layer_name": "cogeo", + "supported_crs": supported_crs, + "bbox_crs_type": bbox_crs_type, + "bbox_crs_uri": bbox_crs_uri, + "layers": layers, "media_type": tile_format.mediatype, }, - media_type=MediaType.xml.value, + media_type="application/xml", ) ############################################################################ @@ -848,25 +1155,25 @@ def point(self): responses={200: {"description": "Return a value for a point"}}, ) def point( - lon: float = Path(..., description="Longitude"), - lat: float = Path(..., description="Latitude"), + lon: Annotated[float, Path(description="Longitude")], + lat: Annotated[float, Path(description="Latitude")], src_path=Depends(self.path_dependency), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), + coord_crs=Depends(CoordCRSParams), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Point value for a dataset.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: pts = src_dst.point( lon, lat, coord_crs=coord_crs or WGS84_CRS, - **layer_params, - **dataset_params, + **layer_params.as_dict(), + **dataset_params.as_dict(), ) return { @@ -884,33 +1191,30 @@ def preview(self): @self.router.get(r"/preview", **img_endpoint_params) @self.router.get(r"/preview.{format}", **img_endpoint_params) def preview( - format: ImageType = Query( - None, description="Output image type. Default is auto." - ), + format: Annotated[ + ImageType, + "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ] = None, src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), layer_params=Depends(self.layer_dependency), - dst_crs: Optional[CRS] = Depends(DstCRSParams), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_preview_dependency), + dst_crs=Depends(DstCRSParams), post_process=Depends(self.process_dependency), - rescale=Depends(self.rescale_dependency), # noqa - color_formula: Optional[str] = Query( - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), + rescale=Depends(self.rescale_dependency), + color_formula=Depends(self.color_formula_dependency), colormap=Depends(self.colormap_dependency), render_params=Depends(self.render_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Create preview of a dataset.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: image = src_dst.preview( - **layer_params, - **image_params, - **dataset_params, + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), dst_crs=dst_crs, ) dst_colormap = getattr(src_dst, "colormap", None) @@ -924,69 +1228,63 @@ def preview( if color_formula: image.apply_color_formula(color_formula) - if cmap := colormap or dst_colormap: - image = image.apply_colormap(cmap) - - if not format: - format = ImageType.jpeg if image.mask.all() else ImageType.png - - content = image.render( - img_format=format.driver, - **format.profile, - **render_params, + content, media_type = render_image( + image, + output_format=format, + colormap=colormap or dst_colormap, + **render_params.as_dict(), ) - return Response(content, media_type=format.mediatype) + return Response(content, media_type=media_type) ############################################################################ - # /crop (Optional) + # /bbox and /feature (Optional) ############################################################################ def part(self): # noqa: C901 - """Register /crop endpoint.""" + """Register /bbox and `/feature` endpoints.""" # GET endpoints @self.router.get( - r"/crop/{minx},{miny},{maxx},{maxy}.{format}", + "/bbox/{minx},{miny},{maxx},{maxy}.{format}", **img_endpoint_params, ) @self.router.get( - r"/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}", + "/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}", **img_endpoint_params, ) - def part( - minx: float = Path(..., description="Bounding box min X"), - miny: float = Path(..., description="Bounding box min Y"), - maxx: float = Path(..., description="Bounding box max X"), - maxy: float = Path(..., description="Bounding box max Y"), - format: ImageType = Query(..., description="Output image type."), + def bbox_image( + minx: Annotated[float, Path(description="Bounding box min X")], + miny: Annotated[float, Path(description="Bounding box min Y")], + maxx: Annotated[float, Path(description="Bounding box max X")], + maxy: Annotated[float, Path(description="Bounding box max Y")], + format: Annotated[ + ImageType, + "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ] = None, src_path=Depends(self.path_dependency), - dst_crs: Optional[CRS] = Depends(DstCRSParams), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_part_dependency), + dst_crs=Depends(DstCRSParams), + coord_crs=Depends(CoordCRSParams), post_process=Depends(self.process_dependency), rescale=Depends(self.rescale_dependency), - color_formula: Optional[str] = Query( - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), + color_formula=Depends(self.color_formula_dependency), colormap=Depends(self.colormap_dependency), render_params=Depends(self.render_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): - """Create image from part of a dataset.""" + """Create image from a bbox.""" with rasterio.Env(**env): with self.reader(src_path, **reader_params) as src_dst: image = src_dst.part( [minx, miny, maxx, maxy], dst_crs=dst_crs, bounds_crs=coord_crs or WGS84_CRS, - **layer_params, - **image_params, - **dataset_params, + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), ) dst_colormap = getattr(src_dst, "colormap", None) @@ -999,61 +1297,58 @@ def part( if color_formula: image.apply_color_formula(color_formula) - if cmap := colormap or dst_colormap: - image = image.apply_colormap(cmap) - - content = image.render( - img_format=format.driver, - **format.profile, - **render_params, + content, media_type = render_image( + image, + output_format=format, + colormap=colormap or dst_colormap, + **render_params.as_dict(), ) - return Response(content, media_type=format.mediatype) + return Response(content, media_type=media_type) # POST endpoints @self.router.post( - r"/crop", + "/feature", **img_endpoint_params, ) @self.router.post( - r"/crop.{format}", + "/feature.{format}", **img_endpoint_params, ) @self.router.post( - r"/crop/{width}x{height}.{format}", + "/feature/{width}x{height}.{format}", **img_endpoint_params, ) - def geojson_crop( - geojson: Feature = Body(..., description="GeoJSON Feature."), - format: ImageType = Query( - None, description="Output image type. Default is auto." - ), + def feature_image( + geojson: Annotated[Feature, Body(description="GeoJSON Feature.")], + format: Annotated[ + ImageType, + "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", + ] = None, src_path=Depends(self.path_dependency), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), layer_params=Depends(self.layer_dependency), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_part_dependency), + coord_crs=Depends(CoordCRSParams), + dst_crs=Depends(DstCRSParams), post_process=Depends(self.process_dependency), rescale=Depends(self.rescale_dependency), - color_formula: Optional[str] = Query( - None, - title="Color Formula", - description="rio-color formula (info: https://github.com/mapbox/rio-color)", - ), + color_formula=Depends(self.color_formula_dependency), colormap=Depends(self.colormap_dependency), render_params=Depends(self.render_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Create image from a geojson feature.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: image = src_dst.feature( - geojson.dict(exclude_none=True), + geojson.model_dump(exclude_none=True), shape_crs=coord_crs or WGS84_CRS, - **layer_params, - **image_params, - **dataset_params, + dst_crs=dst_crs, + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), ) dst_colormap = getattr(src_dst, "colormap", None) @@ -1066,22 +1361,17 @@ def geojson_crop( if color_formula: image.apply_color_formula(color_formula) - if cmap := colormap or dst_colormap: - image = image.apply_colormap(cmap) - - if not format: - format = ImageType.jpeg if image.mask.all() else ImageType.png - - content = image.render( - img_format=format.driver, - **format.profile, - **render_params, + content, media_type = render_image( + image, + output_format=format, + colormap=colormap or dst_colormap, + **render_params.as_dict(), ) - return Response(content, media_type=format.mediatype) + return Response(content, media_type=media_type) -@dataclass +@define(kw_only=True) class MultiBaseTilerFactory(TilerFactory): """Custom Tiler Factory for MultiBaseReader classes. @@ -1121,14 +1411,14 @@ def info(self): ) def info( src_path=Depends(self.path_dependency), - asset_params=Depends(self.assets_dependency), reader_params=Depends(self.reader_dependency), + asset_params=Depends(self.assets_dependency), env=Depends(self.environment_dependency), ): """Return dataset's basic info or the list of available assets.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - return src_dst.info(**asset_params) + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + return src_dst.info(**asset_params.as_dict()) @self.router.get( "/info.geojson", @@ -1144,20 +1434,33 @@ def info( ) def info_geojson( src_path=Depends(self.path_dependency), - asset_params=Depends(self.assets_dependency), reader_params=Depends(self.reader_dependency), + asset_params=Depends(self.assets_dependency), + crs=Depends(CRSParams), env=Depends(self.environment_dependency), ): """Return dataset's basic info as a GeoJSON feature.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + bounds = src_dst.get_geographic_bounds(crs or WGS84_CRS) + if bounds[0] > bounds[2]: + pl = Polygon.from_bounds(-180, bounds[1], bounds[2], bounds[3]) + pr = Polygon.from_bounds(bounds[0], bounds[1], 180, bounds[3]) + geometry = MultiPolygon( + type="MultiPolygon", + coordinates=[pl.coordinates, pr.coordinates], + ) + else: + geometry = Polygon.from_bounds(*bounds) + return Feature( type="Feature", - geometry=Polygon.from_bounds(*src_dst.geographic_bounds), + bbox=bounds, + geometry=geometry, properties={ asset: asset_info for asset, asset_info in src_dst.info( - **asset_params + **asset_params.as_dict() ).items() }, ) @@ -1174,7 +1477,7 @@ def available_assets( ): """Return a list of supported assets.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: return src_dst.assets # Overwrite the `/statistics` endpoint because the MultiBaseReader output model is different (Dict[str, Dict[str, BandStatistics]]) @@ -1196,23 +1499,23 @@ def statistics(self): # noqa: C901 ) def asset_statistics( src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), asset_params=Depends(AssetsBidxParams), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_preview_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Per Asset statistics""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: return src_dst.statistics( - **asset_params, - **image_params, - **dataset_params, - **stats_params, - hist_options={**histogram_params}, + **asset_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) # MultiBaseReader merged statistics @@ -1231,27 +1534,34 @@ def asset_statistics( ) def statistics( src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), layer_params=Depends(AssetsBidxExprParamsOptional), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_preview_dependency), + post_process=Depends(self.process_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Merged assets statistics.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: # Default to all available assets if not layer_params.assets and not layer_params.expression: layer_params.assets = src_dst.assets - return src_dst.merged_statistics( - **layer_params, - **image_params, - **dataset_params, - **stats_params, - hist_options={**histogram_params}, + image = src_dst.preview( + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), + ) + + if post_process: + image = post_process(image) + + return image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) # POST endpoint @@ -1262,23 +1572,26 @@ def statistics( response_class=GeoJSONResponse, responses={ 200: { - "content": {"application/json": {}}, - "description": "Return dataset's statistics.", + "content": {"application/geo+json": {}}, + "description": "Return dataset's statistics from feature or featureCollection.", } }, ) def geojson_statistics( - geojson: Union[FeatureCollection, Feature] = Body( - ..., description="GeoJSON Feature or FeatureCollection." - ), + geojson: Annotated[ + Union[FeatureCollection, Feature], + Body(description="GeoJSON Feature or FeatureCollection."), + ], src_path=Depends(self.path_dependency), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), layer_params=Depends(AssetsBidxExprParamsOptional), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + coord_crs=Depends(CoordCRSParams), + dst_crs=Depends(DstCRSParams), + post_process=Depends(self.process_dependency), + image_params=Depends(self.img_part_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Statistics from a geojson feature or featureCollection.""" @@ -1287,42 +1600,39 @@ def geojson_statistics( fc = FeatureCollection(type="FeatureCollection", features=[geojson]) with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: # Default to all available assets if not layer_params.assets and not layer_params.expression: layer_params.assets = src_dst.assets for feature in fc: - data = src_dst.feature( - feature.dict(exclude_none=True), + image = src_dst.feature( + feature.model_dump(exclude_none=True), shape_crs=coord_crs or WGS84_CRS, - **layer_params, - **image_params, - **dataset_params, + dst_crs=dst_crs, + align_bounds_with_dataset=True, + **layer_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), ) - stats = get_array_statistics( - data.as_masked(), - **stats_params, - **histogram_params, + if post_process: + image = post_process(image) + + stats = image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) feature.properties = feature.properties or {} - feature.properties.update( - { - # NOTE: because we use `src_dst.feature` the statistics will be in form of - # `Dict[str, BandStatistics]` and not `Dict[str, Dict[str, BandStatistics]]` - "statistics": { - f"{data.band_names[ix]}": BandStatistics(**stats[ix]) - for ix in range(len(stats)) - } - } - ) + # NOTE: because we use `src_dst.feature` the statistics will be in form of + # `Dict[str, BandStatistics]` and not `Dict[str, Dict[str, BandStatistics]]` + feature.properties.update({"statistics": stats}) return fc.features[0] if isinstance(geojson, Feature) else fc -@dataclass +@define(kw_only=True) class MultiBandTilerFactory(TilerFactory): """Custom Tiler Factory for MultiBandReader classes. @@ -1359,14 +1669,14 @@ def info(self): ) def info( src_path=Depends(self.path_dependency), - bands_params=Depends(self.bands_dependency), reader_params=Depends(self.reader_dependency), + bands_params=Depends(self.bands_dependency), env=Depends(self.environment_dependency), ): """Return dataset's basic info.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - return src_dst.info(**bands_params) + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + return src_dst.info(**bands_params.as_dict()) @self.router.get( "/info.geojson", @@ -1382,17 +1692,30 @@ def info( ) def info_geojson( src_path=Depends(self.path_dependency), - bands_params=Depends(self.bands_dependency), reader_params=Depends(self.reader_dependency), + bands_params=Depends(self.bands_dependency), + crs=Depends(CRSParams), env=Depends(self.environment_dependency), ): """Return dataset's basic info as a GeoJSON feature.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + bounds = src_dst.get_geographic_bounds(crs or WGS84_CRS) + if bounds[0] > bounds[2]: + pl = Polygon.from_bounds(-180, bounds[1], bounds[2], bounds[3]) + pr = Polygon.from_bounds(bounds[0], bounds[1], 180, bounds[3]) + geometry = MultiPolygon( + type="MultiPolygon", + coordinates=[pl.coordinates, pr.coordinates], + ) + else: + geometry = Polygon.from_bounds(*bounds) + return Feature( type="Feature", - geometry=Polygon.from_bounds(*src_dst.geographic_bounds), - properties=src_dst.info(**bands_params), + bbox=bounds, + geometry=geometry, + properties=src_dst.info(**bands_params.as_dict()), ) @self.router.get( @@ -1407,7 +1730,7 @@ def available_bands( ): """Return a list of supported bands.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: return src_dst.bands # Overwrite the `/statistics` endpoint because we need bands to default to the list of bands. @@ -1428,23 +1751,34 @@ def statistics(self): # noqa: C901 ) def statistics( src_path=Depends(self.path_dependency), + reader_params=Depends(self.reader_dependency), bands_params=Depends(BandsExprParamsOptional), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_preview_dependency), + post_process=Depends(self.process_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Dataset statistics.""" with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - return src_dst.statistics( - **bands_params, - **image_params, - **dataset_params, - **stats_params, - hist_options={**histogram_params}, + with self.reader(src_path, **reader_params.as_dict()) as src_dst: + # Default to all available bands + if not bands_params.bands and not bands_params.expression: + bands_params.bands = src_dst.bands + + image = src_dst.preview( + **bands_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), + ) + + if post_process: + image = post_process(image) + + return image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) # POST endpoint @@ -1455,23 +1789,26 @@ def statistics( response_class=GeoJSONResponse, responses={ 200: { - "content": {"application/json": {}}, - "description": "Return dataset's statistics.", + "content": {"application/geo+json": {}}, + "description": "Return dataset's statistics from feature or featureCollection.", } }, ) def geojson_statistics( - geojson: Union[FeatureCollection, Feature] = Body( - ..., description="GeoJSON Feature or FeatureCollection." - ), + geojson: Annotated[ + Union[FeatureCollection, Feature], + Body(description="GeoJSON Feature or FeatureCollection."), + ], src_path=Depends(self.path_dependency), - coord_crs: Optional[CRS] = Depends(CoordCRSParams), + reader_params=Depends(self.reader_dependency), bands_params=Depends(BandsExprParamsOptional), dataset_params=Depends(self.dataset_dependency), - image_params=Depends(self.img_dependency), + image_params=Depends(self.img_part_dependency), + coord_crs=Depends(CoordCRSParams), + dst_crs=Depends(DstCRSParams), + post_process=Depends(self.process_dependency), stats_params=Depends(self.stats_dependency), histogram_params=Depends(self.histogram_dependency), - reader_params=Depends(self.reader_dependency), env=Depends(self.environment_dependency), ): """Get Statistics from a geojson feature or featureCollection.""" @@ -1480,67 +1817,42 @@ def geojson_statistics( fc = FeatureCollection(type="FeatureCollection", features=[geojson]) with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: + with self.reader(src_path, **reader_params.as_dict()) as src_dst: # Default to all available bands if not bands_params.bands and not bands_params.expression: bands_params.bands = src_dst.bands for feature in fc: - data = src_dst.feature( - feature.dict(exclude_none=True), + image = src_dst.feature( + feature.model_dump(exclude_none=True), shape_crs=coord_crs or WGS84_CRS, - **bands_params, - **image_params, - **dataset_params, + dst_crs=dst_crs, + align_bounds_with_dataset=True, + **bands_params.as_dict(), + **image_params.as_dict(), + **dataset_params.as_dict(), ) - stats = get_array_statistics( - data.as_masked(), - **stats_params, - **histogram_params, + + if post_process: + image = post_process(image) + + stats = image.statistics( + **stats_params.as_dict(), + hist_options=histogram_params.as_dict(), ) feature.properties = feature.properties or {} - feature.properties.update( - { - "statistics": { - f"{data.band_names[ix]}": BandStatistics( - **stats[ix] - ) - for ix in range(len(stats)) - } - } - ) + feature.properties.update({"statistics": stats}) return fc.features[0] if isinstance(geojson, Feature) else fc -@dataclass -class TMSFactory: +@define(kw_only=True) +class TMSFactory(BaseFactory): """TileMatrixSet endpoints Factory.""" supported_tms: TileMatrixSets = morecantile_tms - # FastAPI router - router: APIRouter = field(default_factory=APIRouter) - - # Router Prefix is needed to find the path for /tile if the TilerFactory.router is mounted - # with other router (multiple `.../tile` routes). - # e.g if you mount the route with `/cog` prefix, set router_prefix to cog and - router_prefix: str = "" - - def __post_init__(self): - """Post Init: register route and configure specific options.""" - self.register_routes() - - def url_for(self, request: Request, name: str, **path_params: Any) -> str: - """Return full url (with prefix) for a specific endpoint.""" - url_path = self.router.url_path_for(name, **path_params) - base_url = str(request.base_url) - if self.router_prefix: - base_url += self.router_prefix.lstrip("/") - - return str(url_path.make_absolute_url(base_url=base_url)) - def register_routes(self): """Register TMS endpoint routes.""" @@ -1550,66 +1862,90 @@ def register_routes(self): response_model_exclude_none=True, summary="Retrieve the list of available tiling schemes (tile matrix sets).", operation_id="getTileMatrixSetsList", + responses={ + 200: { + "content": { + "application/json": {}, + }, + }, + }, ) - async def TileMatrixSet_list(request: Request): + async def tilematrixsets(request: Request): """ OGC Specification: http://docs.opengeospatial.org/per/19-069.html#_tilematrixsets """ - return { - "tileMatrixSets": [ + data = TileMatrixSetList( + tileMatrixSets=[ { - "id": tms, - "title": tms, + "id": tms_id, "links": [ { "href": self.url_for( request, - "TileMatrixSet_info", - TileMatrixSetId=tms, + "tilematrixset", + tileMatrixSetId=tms_id, ), - "rel": "item", + "rel": "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes", "type": "application/json", + "title": f"Definition of {tms_id} tileMatrixSet", } ], } - for tms in self.supported_tms.list() + for tms_id in self.supported_tms.list() ] - } + ) + + return data @self.router.get( - r"/tileMatrixSets/{TileMatrixSetId}", + "/tileMatrixSets/{tileMatrixSetId}", response_model=TileMatrixSet, response_model_exclude_none=True, summary="Retrieve the definition of the specified tiling scheme (tile matrix set).", operation_id="getTileMatrixSet", + responses={ + 200: { + "content": { + "application/json": {}, + }, + }, + }, ) - async def TileMatrixSet_info( - TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Path( - ..., description="TileMatrixSet Name." - ) + async def tilematrixset( + request: Request, + tileMatrixSetId: Annotated[ + Literal[tuple(self.supported_tms.list())], + Path(description="Identifier for a supported TileMatrixSet."), + ], ): """ OGC Specification: http://docs.opengeospatial.org/per/19-069.html#_tilematrixset """ - return self.supported_tms.get(TileMatrixSetId) + return self.supported_tms.get(tileMatrixSetId) -@dataclass -class AlgorithmFactory: +@define(kw_only=True) +class AlgorithmFactory(BaseFactory): """Algorithm endpoints Factory.""" # Supported algorithm supported_algorithm: Algorithms = available_algorithms - # FastAPI router - router: APIRouter = field(default_factory=APIRouter) - - def __post_init__(self): - """Post Init: register routes""" + def register_routes(self): + """Register Algorithm routes.""" def metadata(algorithm: BaseAlgorithm) -> AlgorithmMetadata: """Algorithm Metadata""" - props = algorithm.schema()["properties"] + props = algorithm.model_json_schema()["properties"] + + # title and description + info = { + k: v["default"] + for k, v in props.items() + if k == "title" or k == "description" + } + title = info.get("title", None) + description = info.get("description", None) # Inputs Metadata ins = { @@ -1629,9 +1965,18 @@ def metadata(algorithm: BaseAlgorithm) -> AlgorithmMetadata: params = { k: v for k, v in props.items() - if not k.startswith("input_") and not k.startswith("output_") + if not k.startswith("input_") + and not k.startswith("output_") + and k != "title" + and k != "description" } - return AlgorithmMetadata(inputs=ins, outputs=outs, parameters=params) + return AlgorithmMetadata( + title=title, + description=description, + inputs=ins, + outputs=outs, + parameters=params, + ) @self.router.get( "/algorithms", @@ -1650,9 +1995,174 @@ def available_algorithms(request: Request): operation_id="getAlgorithm", ) def algorithm_metadata( - algorithm: Literal[tuple(self.supported_algorithm.list())] = Path( - ..., description="Algorithm name", alias="algorithmId" - ), + algorithm: Annotated[ + Literal[tuple(self.supported_algorithm.list())], + Path(description="Algorithm name", alias="algorithmId"), + ], ): """Retrieve the metadata of the specified algorithm.""" return metadata(self.supported_algorithm.get(algorithm)) + + +@define(kw_only=True) +class ColorMapFactory(BaseFactory): + """Colormap endpoints Factory.""" + + # Supported colormaps + supported_colormaps: ColorMaps = default_cmap + + def register_routes(self): # noqa: C901 + """Register ColorMap routes.""" + + @self.router.get( + "/colorMaps", + response_model=ColorMapsList, + response_model_exclude_none=True, + summary="Retrieve the list of available colormaps.", + operation_id="getColorMaps", + ) + def available_colormaps(request: Request): + """Retrieve the list of available colormaps.""" + return { + "colorMaps": self.supported_colormaps.list(), + "links": [ + { + "title": "List of available colormaps", + "href": self.url_for( + request, + "available_colormaps", + ), + "type": "application/json", + "rel": "self", + }, + { + "title": "Retrieve colorMap metadata", + "href": self.url_for( + request, "colormap_metadata", colorMapId="{colorMapId}" + ), + "type": "application/json", + "rel": "data", + "templated": True, + }, + { + "title": "Retrieve colorMap as image", + "href": self.url_for( + request, "colormap_metadata", colorMapId="{colorMapId}" + ) + + "?format=png", + "type": "image/png", + "rel": "data", + "templated": True, + }, + ], + } + + @self.router.get( + "/colorMaps/{colorMapId}", + response_model=ColorMapType, + summary="Retrieve the colorMap metadata or image.", + operation_id="getColorMap", + responses={ + 200: { + "content": { + "application/json": {}, + "image/png": {}, + "image/jpeg": {}, + "image/jpg": {}, + "image/webp": {}, + "image/jp2": {}, + "image/tiff; application=geotiff": {}, + "application/x-binary": {}, + } + }, + }, + ) + def colormap_metadata( + colormap: Annotated[ + Literal[tuple(self.supported_colormaps.list())], + Path(description="ColorMap name", alias="colorMapId"), + ], + # Image Output Options + format: Annotated[ + Optional[ImageType], + Query( + description="Return colorMap as Image.", + ), + ] = None, + orientation: Annotated[ + Optional[Literal["vertical", "horizontal"]], + Query( + description="Image Orientation.", + ), + ] = None, + height: Annotated[ + Optional[int], + Query( + description="Image Height (default to 20px for horizontal or 256px for vertical).", + ), + ] = None, + width: Annotated[ + Optional[int], + Query( + description="Image Width (default to 256px for horizontal or 20px for vertical).", + ), + ] = None, + ): + """Retrieve the metadata of the specified colormap.""" + cmap = self.supported_colormaps.get(colormap) + + if format: + ############################################################### + # SEQUENCE CMAP + if isinstance(cmap, Sequence): + values = [minv for ((minv, _), _) in cmap] + arr = numpy.array([values] * 20) + + if orientation == "vertical": + height = height or 256 if len(values) < 256 else len(values) + else: + width = width or 256 if len(values) < 256 else len(values) + + ############################################################### + # DISCRETE CMAP + elif len(cmap) != 256 or max(cmap) >= 256 or min(cmap) < 0: + values = list(cmap) + arr = numpy.array([values] * 20) + + if orientation == "vertical": + height = height or 256 if len(values) < 256 else len(values) + else: + width = width or 256 if len(values) < 256 else len(values) + + ############################################################### + # LINEAR CMAP + else: + cmin, cmax = min(cmap), max(cmap) + arr = numpy.array( + [ + numpy.round(numpy.linspace(cmin, cmax, num=256)).astype( + numpy.uint8 + ) + ] + * 20 + ) + + if orientation == "vertical": + arr = arr.transpose([1, 0]) + + img = ImageData(arr) + + width = width or img.width + height = height or img.height + if width != img.width or height != img.height: + img = img.resize(height, width) + + return Response( + img.render(img_format=format.driver, colormap=cmap), + media_type=format.mediatype, + ) + + if isinstance(cmap, Sequence): + return [(k, numpy.array(v).tolist()) for (k, v) in cmap] + else: + return {k: numpy.array(v).tolist() for k, v in cmap.items()} diff --git a/src/titiler/core/titiler/core/middleware.py b/src/titiler/core/titiler/core/middleware.py index f592c962d..0d19eb688 100644 --- a/src/titiler/core/titiler/core/middleware.py +++ b/src/titiler/core/titiler/core/middleware.py @@ -3,6 +3,7 @@ import logging import re import time +import urllib.parse from typing import Optional, Set from fastapi.logger import logger @@ -160,7 +161,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send): query_string = "" for k, v in request.query_params.multi_items(): - query_string += k.lower() + "=" + v + "&" + query_string += k.lower() + "=" + urllib.parse.quote(v) + "&" query_string = query_string[:-1] request.scope["query_string"] = query_string.encode(DECODE_FORMAT) diff --git a/src/titiler/core/titiler/core/models/OGC.py b/src/titiler/core/titiler/core/models/OGC.py index 6b735dff2..87a399e1d 100644 --- a/src/titiler/core/titiler/core/models/OGC.py +++ b/src/titiler/core/titiler/core/models/OGC.py @@ -1,9 +1,13 @@ """OGC models.""" +from datetime import datetime +from typing import Dict, List, Literal, Optional, Set, Union -from typing import List +from morecantile.models import CRSType +from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, RootModel +from typing_extensions import Annotated -from pydantic import AnyHttpUrl, BaseModel +from titiler.core.resources.enums import MediaType class TileMatrixSetLink(BaseModel): @@ -18,22 +22,16 @@ class TileMatrixSetLink(BaseModel): rel: str = "item" type: str = "application/json" - class Config: - """Config for model.""" - - use_enum_values = True - class TileMatrixSetRef(BaseModel): """ TileMatrixSetRef model. Based on http://docs.opengeospatial.org/per/19-069.html#_tilematrixsets - """ id: str - title: str + title: Optional[str] = None links: List[TileMatrixSetLink] @@ -42,7 +40,693 @@ class TileMatrixSetList(BaseModel): TileMatrixSetList model. Based on http://docs.opengeospatial.org/per/19-069.html#_tilematrixsets - """ tileMatrixSets: List[TileMatrixSetRef] + + +class Link(BaseModel): + """Link model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/common-core/link.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + href: Annotated[ + str, + Field( + json_schema_extra={ + "description": "Supplies the URI to a remote resource (or resource fragment).", + "examples": ["http://data.example.com/buildings/123"], + } + ), + ] + rel: Annotated[ + str, + Field( + json_schema_extra={ + "description": "The type or semantics of the relation.", + "examples": ["alternate"], + } + ), + ] + type: Annotated[ + Optional[MediaType], + Field( + json_schema_extra={ + "description": "A hint indicating what the media type of the result of dereferencing the link should be.", + "examples": ["application/geo+json"], + } + ), + ] = None + templated: Annotated[ + Optional[bool], + Field( + json_schema_extra={ + "description": "This flag set to true if the link is a URL template.", + } + ), + ] = None + varBase: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "A base path to retrieve semantic information about the variables used in URL template.", + "examples": ["/ogcapi/vars/"], + } + ), + ] = None + hreflang: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "A hint indicating what the language of the result of dereferencing the link should be.", + "examples": ["en"], + } + ), + ] = None + title: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Used to label the destination of a link such that it can be used as a human-readable identifier.", + "examples": ["Trierer Strasse 70, 53115 Bonn"], + } + ), + ] = None + length: Optional[int] = None + + model_config = {"use_enum_values": True} + + +class TimeStamp(RootModel): + """TimeStamp model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/common-geodata/timeStamp.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + root: Annotated[ + datetime, + Field( + json_schema_extra={ + "description": "This property indicates the time and date when the response was generated using RFC 3339 notation.", + "examples": ["2017-08-17T08:05:32Z"], + } + ), + ] + + +class BoundingBox(BaseModel): + """BoundingBox model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/2DBoundingBox.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + lowerLeft: Annotated[ + List[float], + Field( + max_length=2, + min_length=2, + json_schema_extra={ + "description": "A 2D Point in the CRS indicated elsewhere", + }, + ), + ] + upperRight: Annotated[ + List[float], + Field( + max_length=2, + min_length=2, + json_schema_extra={ + "description": "A 2D Point in the CRS indicated elsewhere", + }, + ), + ] + crs: Annotated[Optional[CRSType], Field(json_schema_extra={"title": "CRS"})] = None + orderedAxes: Annotated[ + Optional[List[str]], Field(max_length=2, min_length=2) + ] = None + + +# Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml +Type = Literal["array", "boolean", "integer", "null", "number", "object", "string"] + +# Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml +AccessConstraints = Literal[ + "unclassified", "restricted", "confidential", "secret", "topSecret" +] + + +class Properties(BaseModel): + """Properties model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + title: Optional[str] = None + description: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Implements 'description'", + } + ), + ] = None + type: Optional[Type] = None + enum: Annotated[ + Optional[Set], + Field( + min_length=1, + json_schema_extra={ + "description": "Implements 'acceptedValues'", + }, + ), + ] = None + format: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Complements implementation of 'type'", + } + ), + ] = None + contentMediaType: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Implements 'mediaType'", + } + ), + ] = None + maximum: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Implements 'range'", + } + ), + ] = None + exclusiveMaximum: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Implements 'range'", + } + ), + ] = None + minimum: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Implements 'range'", + } + ), + ] = None + exclusiveMinimum: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Implements 'range'", + } + ), + ] = None + pattern: Optional[str] = None + maxItems: Annotated[ + Optional[int], + Field( + ge=0, + json_schema_extra={ + "description": "Implements 'upperMultiplicity'", + }, + ), + ] = None + minItems: Annotated[ + Optional[int], + Field( + ge=0, + json_schema_extra={ + "description": "Implements 'lowerMultiplicity'", + }, + ), + ] = 0 + observedProperty: Optional[str] = None + observedPropertyURI: Optional[AnyUrl] = None + uom: Optional[str] = None + uomURI: Optional[AnyUrl] = None + + +class PropertiesSchema(BaseModel): + """PropertiesSchema model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + type: Literal["object"] + required: Annotated[ + Optional[List[str]], + Field( + min_length=1, + json_schema_extra={ + "description": "Implements 'multiplicity' by citing property 'name' defined as 'additionalProperties'", + }, + ), + ] = None + properties: Dict[str, Properties] + + +class Style(BaseModel): + """Style model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/style.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + id: Annotated[ + str, + Field( + json_schema_extra={ + "description": "An identifier for this style. Implementation of 'identifier'", + } + ), + ] + title: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "A title for this style", + } + ), + ] = None + description: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Brief narrative description of this style", + } + ), + ] = None + keywords: Annotated[ + Optional[List[str]], + Field( + json_schema_extra={ + "description": "keywords about this style", + } + ), + ] = None + links: Annotated[ + Optional[List[Link]], + Field( + min_length=1, + json_schema_extra={ + "description": "Links to style related resources. Possible link 'rel' values are: 'style' for a URL pointing to the style description, 'styleSpec' for a URL pointing to the specification or standard used to define the style.", + }, + ), + ] = None + + +class GeospatialData(BaseModel): + """Geospatial model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/geospatialData.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + title: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Title of this tile matrix set, normally used for display to a human", + } + ), + ] = None + description: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Brief narrative description of this tile matrix set, normally available for display to a human", + } + ), + ] = None + keywords: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Unordered list of one or more commonly used or formalized word(s) or phrase(s) used to describe this layer", + } + ), + ] = None + id: Annotated[ + str, + Field( + json_schema_extra={ + "description": "Unique identifier of the Layer. Implementation of 'identifier'", + } + ), + ] + dataType: Annotated[ + Literal["map", "vector", "coverage"], + Field( + json_schema_extra={ + "description": "Type of data represented in the tileset", + } + ), + ] + geometryDimension: Annotated[ + Optional[int], + Field( # type: ignore + ge=0, + le=3, + json_schema_extra={ + "description": "The geometry dimension of the features shown in this layer (0: points, 1: curves, 2: surfaces, 3: solids), unspecified: mixed or unknown", + }, + ), + ] = None + featureType: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Feature type identifier. Only applicable to layers of datatype 'geometries'", + } + ), + ] = None + attribution: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Short reference to recognize the author or provider", + } + ), + ] = None + license: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "License applicable to the tiles", + } + ), + ] = None + pointOfContact: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Useful information to contact the authors or custodians for the layer (e.g. e-mail address, a physical address, phone numbers, etc)", + } + ), + ] = None + publisher: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Organization or individual responsible for making the layer available", + } + ), + ] = None + theme: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Category where the layer can be grouped", + } + ), + ] = None + crs: Annotated[Optional[CRSType], Field(json_schema_extra={"title": "CRS"})] = None + epoch: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Epoch of the Coordinate Reference System (CRS)", + } + ), + ] = None + minScaleDenominator: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Minimum scale denominator for usage of the layer", + } + ), + ] = None + maxScaleDenominator: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Maximum scale denominator for usage of the layer", + } + ), + ] = None + minCellSize: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Minimum cell size for usage of the layer", + } + ), + ] = None + maxCellSize: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Maximum cell size for usage of the layer", + } + ), + ] = None + maxTileMatrix: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "TileMatrix identifier associated with the minScaleDenominator", + } + ), + ] = None + minTileMatrix: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "TileMatrix identifier associated with the maxScaleDenominator", + } + ), + ] = None + boundingBox: Optional[BoundingBox] = None + created: Optional[TimeStamp] = None + updated: Optional[TimeStamp] = None + style: Optional[Style] = None + geoDataClasses: Annotated[ + Optional[List[str]], + Field( + json_schema_extra={ + "description": "URI identifying a class of data contained in this layer (useful to determine compatibility with styles or processes)", + } + ), + ] = None + propertiesSchema: Optional[PropertiesSchema] = None + links: Annotated[ + Optional[List[Link]], + Field( + min_length=1, + json_schema_extra={ + "description": "Links related to this layer. Possible link 'rel' values are: 'geodata' for a URL pointing to the collection of geospatial data.", + }, + ), + ] = None + + +class TilePoint(BaseModel): + """TilePoint model. + + Ref: https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tilePoint.yaml + + Code generated using https://github.com/koxudaxi/datamodel-code-generator/ + """ + + coordinates: Annotated[List[float], Field(max_length=2, min_length=2)] + crs: Annotated[Optional[CRSType], Field(json_schema_extra={"title": "CRS"})] + tileMatrix: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "TileMatrix identifier associated with the scaleDenominator", + } + ), + ] = None + scaleDenominator: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Scale denominator of the tile matrix selected", + } + ), + ] = None + cellSize: Annotated[ + Optional[float], + Field( + json_schema_extra={ + "description": "Cell size of the tile matrix selected", + } + ), + ] = None + + +class TileMatrixLimits(BaseModel): + """ + The limits for an individual tile matrix of a TileSet's TileMatrixSet, as defined in the OGC 2D TileMatrixSet and TileSet Metadata Standard + + Based on https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tileMatrixLimits.yaml + """ + + tileMatrix: str + minTileRow: Annotated[int, Field(ge=0)] + maxTileRow: Annotated[int, Field(ge=0)] + minTileCol: Annotated[int, Field(ge=0)] + maxTileCol: Annotated[int, Field(ge=0)] + + +class TileSet(BaseModel): + """ + TileSet model. + + Based on https://github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tileSet.yaml + """ + + title: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "A title for this tileset", + } + ), + ] = None + description: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Brief narrative description of this tile set", + } + ), + ] = None + dataType: Annotated[ + Literal["map", "vector", "coverage"], + Field( + json_schema_extra={ + "description": "Type of data represented in the tileset", + } + ), + ] + crs: Annotated[CRSType, Field(json_schema_extra={"title": "CRS"})] + tileMatrixSetURI: Annotated[ + Optional[AnyUrl], + Field( + json_schema_extra={ + "description": "Reference to a Tile Matrix Set on an official source for Tile Matrix Sets", + } + ), + ] = None + links: Annotated[ + List[Link], + Field( + json_schema_extra={ + "description": "Links to related resources", + } + ), + ] + tileMatrixSetLimits: Annotated[ + Optional[List[TileMatrixLimits]], + Field( + json_schema_extra={ + "description": "Limits for the TileRow and TileCol values for each TileMatrix in the tileMatrixSet. If missing, there are no limits other that the ones imposed by the TileMatrixSet. If present the TileMatrices listed are limited and the rest not available at all", + } + ), + ] = None + epoch: Annotated[ + Optional[Union[float, int]], + Field( + json_schema_extra={ + "description": "Epoch of the Coordinate Reference System (CRS)", + } + ), + ] = None + layers: Annotated[ + Optional[List[GeospatialData]], + Field(min_length=1), + ] = None + boundingBox: Optional[BoundingBox] = None + centerPoint: Optional[TilePoint] = None + style: Optional[Style] = None + attribution: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Short reference to recognize the author or provider", + } + ), + ] = None + license: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "License applicable to the tiles", + } + ), + ] = None + accessConstraints: Annotated[ + Optional[AccessConstraints], + Field( + json_schema_extra={ + "description": "Restrictions on the availability of the Tile Set that the user needs to be aware of before using or redistributing the Tile Set", + } + ), + ] = "unclassified" + keywords: Annotated[ + Optional[List[str]], + Field( + json_schema_extra={ + "description": "keywords about this tileset", + } + ), + ] = None + version: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Version of the Tile Set. Changes if the data behind the tiles has been changed", + } + ), + ] = None + created: Optional[TimeStamp] = None + updated: Optional[TimeStamp] = None + pointOfContact: Annotated[ + Optional[str], + Field( + json_schema_extra={ + "description": "Useful information to contact the authors or custodians for the Tile Set", + } + ), + ] = None + mediaTypes: Annotated[ + Optional[List[str]], + Field( + json_schema_extra={ + "description": "Media types available for the tiles", + } + ), + ] = None + + +class TileSetList(BaseModel): + """ + TileSetList model. + + Based on https://docs.ogc.org/is/20-057/20-057.html#toc34 + """ + + tilesets: List[TileSet] diff --git a/src/titiler/core/titiler/core/models/mapbox.py b/src/titiler/core/titiler/core/models/mapbox.py index 755ce6901..a52962e1b 100644 --- a/src/titiler/core/titiler/core/models/mapbox.py +++ b/src/titiler/core/titiler/core/models/mapbox.py @@ -1,16 +1,8 @@ """Common response models.""" -from enum import Enum -from typing import List, Optional, Tuple +from typing import List, Literal, Optional, Tuple -from pydantic import BaseModel, Field, root_validator - - -class SchemeEnum(str, Enum): - """TileJSON scheme choice.""" - - xyz = "xyz" - tms = "tms" +from pydantic import BaseModel, Field, model_validator class TileJSON(BaseModel): @@ -22,34 +14,29 @@ class TileJSON(BaseModel): """ tilejson: str = "2.2.0" - name: Optional[str] - description: Optional[str] + name: Optional[str] = None + description: Optional[str] = None version: str = "1.0.0" - attribution: Optional[str] - template: Optional[str] - legend: Optional[str] - scheme: SchemeEnum = SchemeEnum.xyz + attribution: Optional[str] = None + template: Optional[str] = None + legend: Optional[str] = None + scheme: Literal["xyz", "tms"] = "xyz" tiles: List[str] - grids: Optional[List[str]] - data: Optional[List[str]] + grids: Optional[List[str]] = None + data: Optional[List[str]] = None minzoom: int = Field(0, ge=0, le=30) maxzoom: int = Field(30, ge=0, le=30) bounds: List[float] = [-180, -90, 180, 90] - center: Optional[Tuple[float, float, int]] + center: Optional[Tuple[float, float, int]] = None - @root_validator - def compute_center(cls, values): + @model_validator(mode="after") + def compute_center(self): """Compute center if it does not exist.""" - bounds = values["bounds"] - if not values.get("center"): - values["center"] = ( + bounds = self.bounds + if not self.center: + self.center = ( (bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, - values["minzoom"], + self.minzoom, ) - return values - - class Config: - """TileJSON model configuration.""" - - use_enum_values = True + return self diff --git a/src/titiler/core/titiler/core/models/responses.py b/src/titiler/core/titiler/core/models/responses.py index 7932c02e8..280a11894 100644 --- a/src/titiler/core/titiler/core/models/responses.py +++ b/src/titiler/core/titiler/core/models/responses.py @@ -3,10 +3,12 @@ from typing import Dict, List, Union from geojson_pydantic.features import Feature, FeatureCollection -from geojson_pydantic.geometries import Geometry, Polygon +from geojson_pydantic.geometries import Geometry, MultiPolygon, Polygon from pydantic import BaseModel from rio_tiler.models import BandStatistics, Info +from titiler.core.models.OGC import Link + class Point(BaseModel): """ @@ -21,7 +23,7 @@ class Point(BaseModel): band_names: List[str] -InfoGeoJSON = Feature[Polygon, Info] +InfoGeoJSON = Feature[Union[Polygon, MultiPolygon], Info] Statistics = Dict[str, BandStatistics] @@ -30,23 +32,24 @@ class StatisticsInGeoJSON(BaseModel): statistics: Statistics - class Config: - """Config for model.""" - - extra = "allow" + model_config = {"extra": "allow"} StatisticsGeoJSON = Union[ - FeatureCollection[Geometry, StatisticsInGeoJSON], + FeatureCollection[Feature[Geometry, StatisticsInGeoJSON]], Feature[Geometry, StatisticsInGeoJSON], ] # MultiBase Models MultiBaseInfo = Dict[str, Info] -MultiBaseInfoGeoJSON = Feature[Polygon, MultiBaseInfo] +MultiBaseInfoGeoJSON = Feature[Union[Polygon, MultiPolygon], MultiBaseInfo] MultiBaseStatistics = Dict[str, Statistics] -MultiBaseStatisticsGeoJSON = Union[ - FeatureCollection[Geometry, StatisticsInGeoJSON], - Feature[Geometry, StatisticsInGeoJSON], -] +MultiBaseStatisticsGeoJSON = StatisticsGeoJSON + + +class ColorMapsList(BaseModel): + """Model for colormap list.""" + + colorMaps: List[str] + links: List[Link] diff --git a/src/titiler/core/titiler/core/py.typed b/src/titiler/core/titiler/core/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/src/titiler/core/titiler/core/resources/enums.py b/src/titiler/core/titiler/core/resources/enums.py index 1f0fbfa32..42d23246c 100644 --- a/src/titiler/core/titiler/core/resources/enums.py +++ b/src/titiler/core/titiler/core/resources/enums.py @@ -23,7 +23,13 @@ class MediaType(str, Enum): html = "text/html" text = "text/plain" pbf = "application/x-protobuf" - mvt = "application/x-protobuf" + mvt = "application/vnd.mapbox-vector-tile" + ndjson = "application/ndjson" + geojsonseq = "application/geo+json-seq" + schemajson = "application/schema+json" + csv = "text/csv" + openapi30_json = "application/vnd.oai.openapi+json;version=3.0" + openapi30_yaml = "application/vnd.oai.openapi;version=3.0" class ImageDriver(str, Enum): diff --git a/src/titiler/core/titiler/core/resources/responses.py b/src/titiler/core/titiler/core/resources/responses.py index cd37a6a85..3a99d6be5 100644 --- a/src/titiler/core/titiler/core/resources/responses.py +++ b/src/titiler/core/titiler/core/resources/responses.py @@ -2,6 +2,7 @@ from typing import Any +import numpy import simplejson as json from starlette import responses @@ -12,6 +13,16 @@ class XMLResponse(responses.Response): media_type = "application/xml" +class NumpyEncoder(json.JSONEncoder): + """Custom JSON Encoder.""" + + def default(self, obj): + """Catch numpy types and convert them.""" + if isinstance(obj, (numpy.ndarray, numpy.generic)): + return obj.tolist() + return super().default(obj) + + class JSONResponse(responses.JSONResponse): """Custom JSON Response.""" @@ -27,6 +38,7 @@ def render(self, content: Any) -> bytes: indent=None, ignore_nan=True, separators=(",", ":"), + cls=NumpyEncoder, ).encode("utf-8") diff --git a/src/titiler/core/titiler/core/routing.py b/src/titiler/core/titiler/core/routing.py index 28b66d6ec..d0dcf3e46 100644 --- a/src/titiler/core/titiler/core/routing.py +++ b/src/titiler/core/titiler/core/routing.py @@ -1,6 +1,5 @@ """Custom routing classes.""" -import sys import warnings from typing import Callable, Dict, List, Optional, Type @@ -11,11 +10,7 @@ from starlette.requests import Request from starlette.responses import Response from starlette.routing import BaseRoute, Match - -if sys.version_info >= (3, 8): - from typing import TypedDict # pylint: disable=no-name-in-module -else: - from typing_extensions import TypedDict +from typing_extensions import TypedDict def apiroute_factory(env: Optional[Dict] = None) -> Type[APIRoute]: diff --git a/src/titiler/core/titiler/core/templates/map.html b/src/titiler/core/titiler/core/templates/map.html index 700b2dae8..4a361f98c 100644 --- a/src/titiler/core/titiler/core/templates/map.html +++ b/src/titiler/core/titiler/core/templates/map.html @@ -4,10 +4,18 @@ TiTiler Map Viewer - - - - + + + + {{ media_type }} - {{ tms.identifier }} + {{ tileMatrixSetId }} - + + {% endfor %} - {{ tms.identifier }} - {{ tms.crs.srs }} + {{ tileMatrixSetId }} + {{ supported_crs }} {% for item in tileMatrix %} {{ item | safe }} {% endfor %} - + diff --git a/src/titiler/core/titiler/core/utils.py b/src/titiler/core/titiler/core/utils.py new file mode 100644 index 000000000..eaffe7307 --- /dev/null +++ b/src/titiler/core/titiler/core/utils.py @@ -0,0 +1,118 @@ +"""titiler.core utilities.""" + +import warnings +from typing import Any, Optional, Sequence, Tuple, Union + +import numpy +from rasterio.dtypes import dtype_ranges +from rio_tiler.colormap import apply_cmap +from rio_tiler.errors import InvalidDatatypeWarning +from rio_tiler.models import ImageData +from rio_tiler.types import ColorMapType, IntervalTuple +from rio_tiler.utils import linear_rescale, render + +from titiler.core.resources.enums import ImageType + + +def rescale_array( + array: numpy.ndarray, + mask: numpy.ndarray, + in_range: Sequence[IntervalTuple], + out_range: Sequence[IntervalTuple] = ((0, 255),), + out_dtype: Union[str, numpy.number] = "uint8", +) -> numpy.ndarray: + """Rescale data array""" + if len(array.shape) < 3: + array = numpy.expand_dims(array, axis=0) + + nbands = array.shape[0] + if len(in_range) != nbands: + in_range = ((in_range[0]),) * nbands + + if len(out_range) != nbands: + out_range = ((out_range[0]),) * nbands + + for bdx in range(nbands): + array[bdx] = numpy.where( + mask[bdx], + linear_rescale( + array[bdx], in_range=in_range[bdx], out_range=out_range[bdx] + ), + 0, + ) + + return array.astype(out_dtype) + + +def render_image( + image: ImageData, + output_format: Optional[ImageType] = None, + colormap: Optional[ColorMapType] = None, + add_mask: bool = True, + **kwargs: Any, +) -> Tuple[bytes, str]: + """convert image data to file. + + This is adapted from https://github.com/cogeotiff/rio-tiler/blob/066878704f841a332a53027b74f7e0a97f10f4b2/rio_tiler/models.py#L698-L764 + """ + data, mask = image.data.copy(), image.mask.copy() + datatype_range = image.dataset_statistics or (dtype_ranges[str(data.dtype)],) + + if colormap: + data, alpha_from_cmap = apply_cmap(data, colormap) + # Combine both Mask from dataset and Alpha band from Colormap + mask = numpy.bitwise_and(alpha_from_cmap, mask) + datatype_range = (dtype_ranges[str(data.dtype)],) + + # If output_format is not set, we choose between JPEG and PNG + if not output_format: + output_format = ImageType.jpeg if mask.all() else ImageType.png + + if output_format == ImageType.png and data.dtype not in ["uint8", "uint16"]: + warnings.warn( + f"Invalid type: `{data.dtype}` for the `{output_format}` driver. Data will be rescaled using min/max type bounds or dataset_statistics.", + InvalidDatatypeWarning, + ) + data = rescale_array(data, mask, in_range=datatype_range) + + elif output_format in [ + ImageType.jpeg, + ImageType.jpg, + ImageType.webp, + ] and data.dtype not in ["uint8"]: + warnings.warn( + f"Invalid type: `{data.dtype}` for the `{output_format}` driver. Data will be rescaled using min/max type bounds or dataset_statistics.", + InvalidDatatypeWarning, + ) + data = rescale_array(data, mask, in_range=datatype_range) + + elif output_format == ImageType.jp2 and data.dtype not in [ + "uint8", + "int16", + "uint16", + ]: + warnings.warn( + f"Invalid type: `{data.dtype}` for the `{output_format}` driver. Data will be rescaled using min/max type bounds or dataset_statistics.", + InvalidDatatypeWarning, + ) + data = rescale_array(data, mask, in_range=datatype_range) + + creation_options = {**kwargs, **output_format.profile} + if output_format == ImageType.tif: + if "transform" not in creation_options: + creation_options.update({"transform": image.transform}) + if "crs" not in creation_options and image.crs: + creation_options.update({"crs": image.crs}) + + if not add_mask: + mask = None + + return ( + render( + data, + mask, + img_format=output_format.driver, + **creation_options, + ), + output_format.mediatype, + ) diff --git a/src/titiler/extensions/README.md b/src/titiler/extensions/README.md index 4ae1309ea..c7ce05f70 100644 --- a/src/titiler/extensions/README.md +++ b/src/titiler/extensions/README.md @@ -5,14 +5,14 @@ Extent TiTiler Tiler Factories ## Installation ```bash -$ pip install -U pip +$ python -m pip install -U pip # From Pypi -$ pip install titiler.extensions +$ python -m pip install titiler.extensions # Or from sources $ git clone https://github.com/developmentseed/titiler.git -$ cd titiler && pip install -e titiler/core -e titiler/extensions +$ cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions ``` ## Available extensions @@ -118,12 +118,12 @@ class thumbnailExtension(FactoryExtension): env=Depends(factory.environment_dependency), ): with rasterio.Env(**env): - with self.reader(src_path, **reader_params) as src_dst: - im = src.preview( - max_size=self.max_size, - **layer_params, - **dataset_params, - ) + with factory.reader(src_path, **reader_params) as src: + image = src.preview( + max_size=self.max_size, + **layer_params, + **dataset_params, + ) if post_process: image = post_process(image) @@ -138,7 +138,7 @@ class thumbnailExtension(FactoryExtension): content = image.render( img_format=format.driver, - colormap=colormap or dst_colormap, + colormap=colormap, **format.profile, **render_params, ) diff --git a/src/titiler/extensions/pyproject.toml b/src/titiler/extensions/pyproject.toml index 3c3972416..0f43ab8e9 100644 --- a/src/titiler/extensions/pyproject.toml +++ b/src/titiler/extensions/pyproject.toml @@ -21,15 +21,17 @@ classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", ] dynamic = ["version"] dependencies = [ - "titiler.core==0.11.7" + "titiler.core==0.19.0.dev" ] [project.optional-dependencies] @@ -38,13 +40,13 @@ test = [ "pytest-cov", "pytest-asyncio", "httpx", - "jsonschema>=3.0", + "pystac[validation]>=1.0.0,<2.0.0", ] cogeo = [ - "rio-cogeo>=3.1,<4.0", + "rio-cogeo>=5.0,<6.0", ] stac = [ - "rio-stac>=0.6,<0.7", + "rio-stac>=0.8,<0.9", ] [project.urls] diff --git a/src/titiler/extensions/tests/test_viewer.py b/src/titiler/extensions/tests/test_viewer.py index 2a56f119b..18a4cd929 100644 --- a/src/titiler/extensions/tests/test_viewer.py +++ b/src/titiler/extensions/tests/test_viewer.py @@ -25,7 +25,9 @@ def test_cogViewerExtension(): def test_stacViewerExtension(): """Test stacViewerExtension class.""" tiler = MultiBaseTilerFactory(reader=STACReader) - tiler_plus_viewer = MultiBaseTilerFactory(extensions=[stacViewerExtension()]) + tiler_plus_viewer = MultiBaseTilerFactory( + reader=STACReader, extensions=[stacViewerExtension()] + ) assert len(tiler_plus_viewer.router.routes) == len(tiler.router.routes) + 1 app = FastAPI() diff --git a/src/titiler/extensions/tests/test_wms.py b/src/titiler/extensions/tests/test_wms.py index 993817f1f..c7aa2f6de 100644 --- a/src/titiler/extensions/tests/test_wms.py +++ b/src/titiler/extensions/tests/test_wms.py @@ -386,3 +386,53 @@ def test_wmsExtension_GetMap(): -52.301598718454485, 74.66298001264106, ] + + +def test_wmsExtension_GetFeatureInfo(): + """Test wmsValidateExtension class for GetFeatureInfo request.""" + tiler_plus_wms = TilerFactory(extensions=[wmsExtension()]) + + app = FastAPI() + app.include_router(tiler_plus_wms.router) + + with TestClient(app) as client: + # Setup the basic GetFeatureInfo request + params = { + "VERSION": "1.3.0", + "REQUEST": "GetFeatureInfo", + "LAYERS": cog, + "QUERY_LAYERS": cog, + "BBOX": "500975.102,8182890.453,501830.647,8183959.884", + "CRS": "EPSG:32621", + "WIDTH": 334, + "HEIGHT": 333, + "INFO_FORMAT": "text/html", + "I": "0", + "J": "0", + } + + response = client.get("/wms", params=params) + + assert response.status_code == 200 + assert response.content == b"2800" + + params = { + "VERSION": "1.3.0", + "REQUEST": "GetFeatureInfo", + "LAYERS": cog, + "QUERY_LAYERS": cog, + "BBOX": "500975.102,8182890.453,501830.647,8183959.884", + "CRS": "EPSG:32621", + "WIDTH": 334, + "HEIGHT": 333, + "INFO_FORMAT": "text/html", + "I": "333", + "J": "332", + } + + response = client.get("/wms", params=params) + + assert response.status_code == 200 + assert response.content == b"3776" + + # Add additional assertions to check the text response diff --git a/src/titiler/extensions/titiler/extensions/__init__.py b/src/titiler/extensions/titiler/extensions/__init__.py index 18c28ff64..b2da13b85 100644 --- a/src/titiler/extensions/titiler/extensions/__init__.py +++ b/src/titiler/extensions/titiler/extensions/__init__.py @@ -1,6 +1,6 @@ """titiler.extensions""" -__version__ = "0.11.7" +__version__ = "0.19.0.dev" from .cogeo import cogValidateExtension # noqa from .stac import stacExtension # noqa diff --git a/src/titiler/extensions/titiler/extensions/cogeo.py b/src/titiler/extensions/titiler/extensions/cogeo.py index 5b0193060..92732a23b 100644 --- a/src/titiler/extensions/titiler/extensions/cogeo.py +++ b/src/titiler/extensions/titiler/extensions/cogeo.py @@ -1,10 +1,11 @@ """rio-cogeo Extension.""" -from dataclasses import dataclass - +from attrs import define from fastapi import Depends, Query +from typing_extensions import Annotated -from titiler.core.factory import BaseTilerFactory, FactoryExtension +from titiler.core.factory import FactoryExtension, TilerFactory +from titiler.core.resources.responses import JSONResponse try: from rio_cogeo.cogeo import cog_info @@ -14,21 +15,28 @@ Info = None -@dataclass +@define class cogValidateExtension(FactoryExtension): """Add /validate endpoint to a COG TilerFactory.""" - def register(self, factory: BaseTilerFactory): + def register(self, factory: TilerFactory): """Register endpoint to the tiler factory.""" assert ( cog_info is not None - ), "'rio_cogeo' must be installed to use CogValidateExtension" + ), "'rio-cogeo' must be installed to use CogValidateExtension" - @factory.router.get("/validate", response_model=Info) + @factory.router.get( + "/validate", + response_model=Info, + response_class=JSONResponse, + ) def validate( - src_path: str = Depends(factory.path_dependency), - strict: bool = Query(False, description="Treat warnings as errors"), + src_path=Depends(factory.path_dependency), + strict: Annotated[ + bool, + Query(description="Treat warnings as errors"), + ] = False, ): """Validate a COG""" return cog_info(src_path, strict=strict) diff --git a/src/titiler/extensions/titiler/extensions/py.typed b/src/titiler/extensions/titiler/extensions/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/src/titiler/extensions/titiler/extensions/stac.py b/src/titiler/extensions/titiler/extensions/stac.py index 6848c7a35..4c47284d8 100644 --- a/src/titiler/extensions/titiler/extensions/stac.py +++ b/src/titiler/extensions/titiler/extensions/stac.py @@ -1,21 +1,12 @@ """rio-stac Extension.""" -import sys -from dataclasses import dataclass from typing import Any, Dict, List, Literal, Optional +from attrs import define from fastapi import Depends, Query +from typing_extensions import Annotated, TypedDict -from titiler.core.factory import BaseTilerFactory, FactoryExtension - -# Avoids a Pydantic error: -# TypeError: You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.9.2. -# Without it, there is no way to differentiate required and optional fields when subclassed. -# Ref: https://github.com/pydantic/pydantic/pull/3374 -if sys.version_info < (3, 9, 2): - from typing_extensions import TypedDict -else: - from typing import TypedDict +from titiler.core.factory import FactoryExtension, TilerFactory try: import pystac @@ -42,11 +33,11 @@ class Item(TypedDict, total=False): collection: str -@dataclass +@define class stacExtension(FactoryExtension): """Add /stac endpoint to a COG TilerFactory.""" - def register(self, factory: BaseTilerFactory): + def register(self, factory: TilerFactory): """Register endpoint to the tiler factory.""" assert ( @@ -58,51 +49,83 @@ def register(self, factory: BaseTilerFactory): @factory.router.get("/stac", response_model=Item, name="Create STAC Item") def create_stac( - src_path: str = Depends(factory.path_dependency), - datetime: Optional[str] = Query( - None, - description="The date and time of the assets, in UTC (e.g 2020-01-01, 2020-01-01T01:01:01).", - ), - extensions: Optional[List[str]] = Query( - None, description="STAC extension URL the Item implements." - ), - collection: Optional[str] = Query( - None, description="The Collection ID that this item belongs to." - ), - collection_url: Optional[str] = Query( - None, description="Link to the STAC Collection." - ), + src_path=Depends(factory.path_dependency), + datetime: Annotated[ + Optional[str], + Query( + description="The date and time of the assets, in UTC (e.g 2020-01-01, 2020-01-01T01:01:01).", + ), + ] = None, + extensions: Annotated[ + Optional[List[str]], + Query(description="STAC extension URL the Item implements."), + ] = None, + collection: Annotated[ + Optional[str], + Query(description="The Collection ID that this item belongs to."), + ] = None, + collection_url: Annotated[ + Optional[str], + Query(description="Link to the STAC Collection."), + ] = None, # properties: Optional[Dict] = Query(None, description="Additional properties to add in the item."), - id: Optional[str] = Query( - None, - description="Id to assign to the item (default to the source basename).", - ), - asset_name: Optional[str] = Query( - "data", description="asset name for the source (default to 'data')." - ), - asset_roles: Optional[List[str]] = Query( - None, description="list of asset's roles." - ), - asset_media_type: Literal[tuple(media)] = Query( # type: ignore - "auto", description="Asset's media type" - ), - asset_href: Optional[str] = Query( - None, description="Asset's URI (default to source's path)" - ), - with_proj: bool = Query( - True, description="Add the `projection` extension and properties." - ), - with_raster: bool = Query( - True, description="Add the `raster` extension and properties." - ), - with_eo: bool = Query( - True, description="Add the `eo` extension and properties." - ), - max_size: Optional[int] = Query( - 1024, - gt=0, - description="Limit array size from which to get the raster statistics.", - ), + id: Annotated[ + Optional[str], + Query( + description="Id to assign to the item (default to the source basename)." + ), + ] = None, + asset_name: Annotated[ + Optional[str], + Query(description="asset name for the source (default to 'data')."), + ] = "data", + asset_roles: Annotated[ + Optional[List[str]], + Query(description="list of asset's roles."), + ] = None, + asset_media_type: Annotated[ # type: ignore + Optional[Literal[tuple(media)]], + Query(description="Asset's media type"), + ] = "auto", + asset_href: Annotated[ + Optional[str], + Query(description="Asset's URI (default to source's path)"), + ] = None, + with_proj: Annotated[ + Optional[bool], + Query(description="Add the `projection` extension and properties."), + ] = True, + with_raster: Annotated[ + Optional[bool], + Query(description="Add the `raster` extension and properties."), + ] = True, + with_eo: Annotated[ + Optional[bool], + Query(description="Add the `eo` extension and properties."), + ] = True, + max_size: Annotated[ + Optional[int], + Query( + gt=0, + description="Limit array size from which to get the raster statistics.", + ), + ] = 1024, + geom_densify_pts: Annotated[ + Optional[int], + Query( + alias="geometry_densify", + ge=0, + description="Number of points to add to each edge to account for nonlinear edges transformation.", + ), + ] = 0, + geom_precision: Annotated[ + Optional[int], + Query( + alias="geometry_precision", + ge=-1, + description="Round geometry coordinates to this number of decimal.", + ), + ] = -1, ): """Create STAC item.""" properties = ( @@ -138,4 +161,6 @@ def create_stac( with_raster=with_raster, with_eo=with_eo, raster_max_size=max_size, + geom_densify_pts=geom_densify_pts, + geom_precision=geom_precision, ).to_dict() diff --git a/src/titiler/extensions/titiler/extensions/templates/cog_viewer.html b/src/titiler/extensions/titiler/extensions/templates/cog_viewer.html index d5e3c448f..e782d5326 100644 --- a/src/titiler/extensions/titiler/extensions/templates/cog_viewer.html +++ b/src/titiler/extensions/titiler/extensions/templates/cog_viewer.html @@ -4,13 +4,20 @@ TiTiler - Cloud Optimized GeoTIFF Viewer - - - - - - - + + + + + +