From 78e791d77f7a9098cfcbe3c3a10c19a369449ba0 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 8 Apr 2024 16:43:57 +0200 Subject: [PATCH 1/5] Add raster support with unit test --- docs/src/DEVELOPERS.md | 23 ++++++ docs/src/SUMMARY.md | 1 + docs/src/qsa-api/endpoints.md | 2 +- qsa-api/pyproject.toml | 1 + qsa-api/qsa_api/__init__.py | 8 ++ qsa-api/qsa_api/api/projects.py | 103 ++++++++++++++++++++----- qsa-api/qsa_api/api/symbology.py | 12 +-- qsa-api/qsa_api/mapproxy.py | 2 +- qsa-api/qsa_api/project.py | 51 ++++++++---- qsa-api/tests/api.py | 83 +++++++++++++------- qsa-api/tests/landsat_4326.tif | Bin 0 -> 972 bytes qsa-api/tests/landsat_4326.tif.aux.xml | 29 +++++++ sandbox/data.gpkg | Bin 225280 -> 225280 bytes sandbox/docker-compose.yml | 2 + sandbox/landsat_4326.tif | Bin 0 -> 972 bytes sandbox/map.png | Bin 66118 -> 0 bytes 16 files changed, 245 insertions(+), 72 deletions(-) create mode 100644 docs/src/DEVELOPERS.md create mode 100644 qsa-api/tests/landsat_4326.tif create mode 100644 qsa-api/tests/landsat_4326.tif.aux.xml create mode 100644 sandbox/landsat_4326.tif delete mode 100644 sandbox/map.png diff --git a/docs/src/DEVELOPERS.md b/docs/src/DEVELOPERS.md new file mode 100644 index 0000000..bba7e90 --- /dev/null +++ b/docs/src/DEVELOPERS.md @@ -0,0 +1,23 @@ +# Developers + +Documentation: + +```` console +$ mdbook build docs +```` + +Unit tests: + +```` console +$ cd qsa-api +$ pytest -sv test/api.py +```` + +Integration tests: + +```` console +$ cd sandbox +$ docker-compose up -d +$ cd ../qsa-api +$ QSA_GPKG="/data.gpkg" QSA_HOST=127.0.01 QSA_PORT=5000 pytest -sv tests/api.py +```` diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 00b3d5a..ba8d9ec 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -16,4 +16,5 @@ - [Manage projects](sandbox/projects.md) - [Manage layers](sandbox/layers.md) - [Manage styles](sandbox/styles.md) +- [Developers](DEVELOPERS.md) - [Funders](FUNDERS.md) diff --git a/docs/src/qsa-api/endpoints.md b/docs/src/qsa-api/endpoints.md index cce8cc6..a265abe 100644 --- a/docs/src/qsa-api/endpoints.md +++ b/docs/src/qsa-api/endpoints.md @@ -44,7 +44,7 @@ empty. | GET | `/api/projects/{project}/layers/{layer}` | List layer's metadata | | GET | `/api/projects/{project}/layers/{layer}/map` | WMS `GetMap` result with default parameters | | GET | `/api/projects/{project}/layers/{layer}/map/url` | WMS `GetMap` URL with default parameters | -| POST | `/api/projects/{project}/layers` | Add layer to project with `name`, `datasource` and `crs` | +| POST | `/api/projects/{project}/layers` | Add layer to project with `type`, `name`, `datasource` and `crs` | | POST | `/api/projects/{project}/layers/{layer}/style` | Add/Update layer's style with `name` (style name) and `current` (`True` or `False`) | | DELETE | `/api/projects/{project}/layers/{layer}` | Remove layer from project | diff --git a/qsa-api/pyproject.toml b/qsa-api/pyproject.toml index c86bf92..6bf7846 100644 --- a/qsa-api/pyproject.toml +++ b/qsa-api/pyproject.toml @@ -9,6 +9,7 @@ python = ">=3.10" flask = "3.0.0" click = "8.1.7" pyyaml = "^6.0.1" +jsonschema = "^4.21.1" [build-system] requires = ["poetry-core"] diff --git a/qsa-api/qsa_api/__init__.py b/qsa-api/qsa_api/__init__.py index b727357..62c279d 100644 --- a/qsa-api/qsa_api/__init__.py +++ b/qsa-api/qsa_api/__init__.py @@ -1 +1,9 @@ # coding: utf8 + +from qgis.core import QgsApplication + + +# avoid "Application path not initialized" message +QgsApplication.setPrefixPath('/usr', True) +qgs = QgsApplication([], False) +qgs.initQgis() diff --git a/qsa-api/qsa_api/api/projects.py b/qsa-api/qsa_api/api/projects.py index a327db1..87f83bf 100644 --- a/qsa-api/qsa_api/api/projects.py +++ b/qsa-api/qsa_api/api/projects.py @@ -2,6 +2,8 @@ import shutil import requests +from jsonschema import validate +from jsonschema.exceptions import ValidationError from flask import send_file, Blueprint, jsonify, request from ..wms import WMS @@ -36,15 +38,24 @@ def project_info(name: str): @projects.post("/") def project_add(): - if request.is_json: - if "name" not in request.get_json(): - return {"error": "Parameter 'name' is missing"}, 415 + schema = { + "type": "object", + "required": ["name", "author"], + "properties": { + "name": {"type": "string"}, + "author": {"type": "string"}, + }, + } - if "author" not in request.get_json(): - return {"error": "Parameter 'author' is missing"}, 415 + if request.is_json: + data = request.get_json() + try: + validate(data, schema) + except ValidationError as e: + return {"error": e.message}, 415 - name = request.get_json()["name"] - author = request.get_json()["author"] + name = data["name"] + author = data["author"] project = QSAProject(name) if project.exists(): @@ -97,11 +108,22 @@ def project_del_style(name, style): @projects.post("//layers//style") def project_layer_update_style(name, layer_name): + schema = { + "type": "object", + "required": ["name", "current"], + "properties": { + "name": {"type": "string"}, + "current": {"type": "boolean"}, + }, + } + project = QSAProject(name) if project.exists(): data = request.get_json() - if "current" not in data or "name" not in data: - return {"error": "Invalid parameters"}, 415 + try: + validate(data, schema) + except ValidationError as e: + return {"error": e.message}, 415 current = str_to_bool(data["current"]) @@ -142,17 +164,24 @@ def project_layer_map(name, layer_name): @projects.post("//styles") def project_add_style(name): + schema = { + "type": "object", + "required": ["name", "symbol", "symbology", "properties"], + "properties": { + "name": {"type": "string"}, + "symbol": {"type": "string"}, + "symbology": {"type": "string"}, + "properties": {"type": "object"}, + }, + } + project = QSAProject(name) if project.exists(): data = request.get_json() - - if ( - "name" not in data - or "symbol" not in data - or "symbology" not in data - or "properties" not in data - ): - return {"error": "Invalid parameters"}, 415 + try: + validate(data, schema) + except ValidationError as e: + return {"error": e.message}, 415 # legacy support symbology = data["symbology"] @@ -182,11 +211,22 @@ def project_default_styles(name): @projects.post("//styles/default") def project_update_default_style(name): + schema = { + "type": "object", + "required": ["geometry", "style"], + "properties": { + "name": {"type": "string"}, + "author": {"type": "string"}, + }, + } + project = QSAProject(name) if project.exists(): data = request.get_json() - if "geometry" not in data or "style" not in data: - return {"error": "Invalid parameters"}, 415 + try: + validate(data, schema) + except ValidationError as e: + return {"error": e.message}, 415 project.style_update(data["geometry"], data["style"]) return jsonify(True), 201 @@ -205,11 +245,32 @@ def project_layers(name): @projects.post("//layers") def project_add_layer(name): + schema = { + "type": "object", + "required": ["name", "datasource", "crs", "type"], + "properties": { + "name": {"type": "string"}, + "datasource": {"type": "string"}, + "crs": {"type": "number"}, + "type": {"type": "string"}, + }, + } + project = QSAProject(name) if project.exists(): data = request.get_json() - rc = project.add_layer(data["datasource"], data["name"], data["crs"]) - return jsonify(rc), 201 + try: + validate(data, schema) + except ValidationError as e: + return {"error": e.message}, 415 + + rc, err = project.add_layer( + data["datasource"], data["type"], data["name"], data["crs"] + ) + if rc: + return jsonify(rc), 201 + else: + return {"error": err}, 415 else: return {"error": "Project does not exist"}, 415 diff --git a/qsa-api/qsa_api/api/symbology.py b/qsa-api/qsa_api/api/symbology.py index eb30a4b..7a53ad0 100644 --- a/qsa-api/qsa_api/api/symbology.py +++ b/qsa-api/qsa_api/api/symbology.py @@ -19,16 +19,16 @@ def symbology_symbols_line(): @symbology.get("/vector/polygon/single_symbol/fill/properties") def symbology_symbols_fill(): props = QgsSimpleFillSymbolLayer().properties() - props["outline_style"] = ( - "solid (no, solid, dash, dot, dash dot, dash dot dot)" - ) + props[ + "outline_style" + ] = "solid (no, solid, dash, dot, dash dot, dash dot dot)" return jsonify(props) @symbology.get("/vector/point/single_symbol/marker/properties") def symbology_symbols_marker(): props = QgsSimpleMarkerSymbolLayer().properties() - props["outline_style"] = ( - "solid (no, solid, dash, dot, dash dot, dash dot dot)" - ) + props[ + "outline_style" + ] = "solid (no, solid, dash, dot, dash dot, dash dot dot)" return jsonify(props) diff --git a/qsa-api/qsa_api/mapproxy.py b/qsa-api/qsa_api/mapproxy.py index f2f3bc6..c12c588 100644 --- a/qsa-api/qsa_api/mapproxy.py +++ b/qsa-api/qsa_api/mapproxy.py @@ -31,7 +31,7 @@ def clear_cache(self, layer_name: str) -> None: for d in cache_dir.glob(f"**/{layer_name}_cache_*"): shutil.rmtree(d) - def add_layer(self, name: str, bbox: list, srs: str) -> None: + def add_layer(self, name: str, bbox: list, srs: int) -> None: if "layers" not in self.cfg: self.cfg["layers"] = [] self.cfg["caches"] = {} diff --git a/qsa-api/qsa_api/project.py b/qsa-api/qsa_api/project.py index 19c8a70..5d2ebc8 100644 --- a/qsa-api/qsa_api/project.py +++ b/qsa-api/qsa_api/project.py @@ -7,6 +7,7 @@ from qgis.PyQt.QtCore import Qt from qgis.core import ( + Qgis, QgsSymbol, QgsProject, QgsMapLayer, @@ -14,6 +15,7 @@ QgsFillSymbol, QgsLineSymbol, QgsVectorLayer, + QgsRasterLayer, QgsMarkerSymbol, QgsFeatureRenderer, QgsReadWriteContext, @@ -252,35 +254,48 @@ def remove(self) -> None: if self._mapproxy_enabled: QSAMapProxy(self.name).remove() - def add_layer(self, datasource: str, name: str, epsg_code: int) -> bool: - # add layer in qgis project - vl = QgsVectorLayer(datasource, name, "ogr") - crs = vl.crs() - crs.createFromId(epsg_code) - vl.setCrs(crs) + def add_layer( + self, datasource: str, layer_type: str, name: str, epsg_code: int + ) -> (bool, str): + t = self._layer_type(layer_type) + if t is None: + return False, "Invalid layer type" - if not vl.isValid(): - return False + lyr = None + if t == Qgis.LayerType.Vector: + lyr = QgsVectorLayer(datasource, name, "ogr") + elif t == Qgis.LayerType.Raster: + lyr = QgsRasterLayer(datasource, name, "gdal") + else: + return False, "Invalid layer type" + + crs = lyr.crs() + crs.createFromString(f"EPSG:{epsg_code}") + lyr.setCrs(crs) + + if not lyr.isValid(): + return False, "Invalid layer" # create project project = QgsProject() project.read(self._qgis_project.as_posix()) - project.addMapLayer(vl) + project.addMapLayer(lyr) project.setCrs(crs) project.write() # set default style - geometry = vl.geometryType().name.lower() - default_style = self.style_default(geometry) + if t == Qgis.LayerType.Vector: + geometry = lyr.geometryType().name.lower() + default_style = self.style_default(geometry) - self.layer_update_style(name, default_style, True) + self.layer_update_style(name, default_style, True) # add layer in mapproxy config file bbox = list( map( float, - vl.extent().asWktCoordinates().replace(",", "").split(" "), + lyr.extent().asWktCoordinates().replace(",", "").split(" "), ) ) @@ -290,7 +305,7 @@ def add_layer(self, datasource: str, name: str, epsg_code: int) -> bool: mp.add_layer(name, bbox, epsg_code) mp.write() - return True + return True, "" def add_style( self, @@ -376,6 +391,14 @@ def remove_style(self, name: str) -> bool: def _qgis_projects_dir() -> Path: return Path(current_app.config["CONFIG"].qgisserver_projects) + @staticmethod + def _layer_type(layer_type: str) -> Qgis.LayerType | None: + if layer_type.lower() == "vector": + return Qgis.LayerType.Vector + elif layer_type.lower() == "raster": + return Qgis.LayerType.Raster + return None + @property def _mapproxy_enabled(self) -> bool: return bool(current_app.config["CONFIG"].mapproxy_projects) diff --git a/qsa-api/tests/api.py b/qsa-api/tests/api.py index a070e93..218c1a8 100644 --- a/qsa-api/tests/api.py +++ b/qsa-api/tests/api.py @@ -19,6 +19,10 @@ if "QSA_GPKG" in os.environ: GPKG = os.environ["QSA_GPKG"] +GEOTIFF = Path(__file__).parent / "landsat_4326.tif" +if "QSA_GEOTIFF" in os.environ: + GEOTIFF = os.environ["QSA_GPKG"] + TEST_PROJECT_0 = "qsa_test_project0" TEST_PROJECT_1 = "qsa_test_project1" @@ -29,6 +33,10 @@ def __init__(self, resp, flask_client): self.flask_client = flask_client self.resp = resp + @property + def status_code(self): + return self.resp.status_code + def get_json(self): if self.flask_client: return self.resp.get_json() @@ -113,13 +121,13 @@ def test_projects(self): data["name"] = TEST_PROJECT_0 data["author"] = "pblottiere" p = self.app.post("/api/projects/", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "qsa_test_project1" data["author"] = "pblottiere" p = self.app.post("/api/projects/", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 2 projects p = self.app.get("/api/projects/") @@ -128,7 +136,7 @@ def test_projects(self): # remove project p = self.app.delete(f"/api/projects/{TEST_PROJECT_0}") - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 1 projects p = self.app.get("/api/projects/") @@ -174,7 +182,7 @@ def test_layers(self): data["name"] = TEST_PROJECT_0 data["author"] = "pblottiere" p = self.app.post("/api/projects/", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 0 layer p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers") @@ -185,26 +193,37 @@ def test_layers(self): data["name"] = "layer0" data["datasource"] = f"{GPKG}|layername=polygons" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "layer1" data["datasource"] = f"{GPKG}|layername=lines" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "layer2" data["datasource"] = f"{GPKG}|layername=points" data["crs"] = 4326 + data["type"] = "vector" + p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) + self.assertEqual(p.status_code, 201) + + data = {} + data["name"] = "layer3" + data["datasource"] = f"{GEOTIFF}" + data["crs"] = 4326 + data["type"] = "raster" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 3 layers p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers") - self.assertEqual(p.get_json(), ["layer0", "layer1", "layer2"]) + self.assertEqual(p.get_json(), ["layer0", "layer1", "layer2", "layer3"]) # layer metadata p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers/layer1") @@ -217,11 +236,11 @@ def test_layers(self): # remove layer0 p = self.app.delete(f"/api/projects/{TEST_PROJECT_0}/layers/layer0") - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 2 layer p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers") - self.assertEqual(p.get_json(), ["layer1", "layer2"]) + self.assertEqual(p.get_json(), ["layer1", "layer2", "layer3"]) # remove last project p = self.app.delete(f"/api/projects/{TEST_PROJECT_0}") @@ -232,7 +251,7 @@ def test_style(self): data["name"] = TEST_PROJECT_0 data["author"] = "pblottiere" p = self.app.post("/api/projects/", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 0 style p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles") @@ -245,7 +264,7 @@ def test_style(self): data["symbol"] = "line" data["properties"] = {"line_width": 0.5} p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/styles", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # add fill style to project data = {} @@ -254,11 +273,12 @@ def test_style(self): data["symbol"] = "fill" data["properties"] = {"outline_width": 0.5} p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/styles", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 2 styles p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles") - self.assertEqual(p.get_json(), ["style_line", "style_fill"]) + self.assertTrue("style_line" in p.get_json()) + self.assertTrue("style_fill" in p.get_json()) # style line metadata p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles/style_line") @@ -275,15 +295,17 @@ def test_style(self): data["name"] = "layer0" data["datasource"] = f"{GPKG}|layername=polygons" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "layer1" data["datasource"] = f"{GPKG}|layername=lines" data["crs"] = 32637 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # add style to layers data = {} @@ -292,7 +314,7 @@ def test_style(self): p = self.app.post( f"/api/projects/{TEST_PROJECT_0}/layers/layer0/style", data ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["current"] = True @@ -300,7 +322,7 @@ def test_style(self): p = self.app.post( f"/api/projects/{TEST_PROJECT_0}/layers/layer1/style", data ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # check style for layers p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers/layer0") @@ -317,7 +339,7 @@ def test_style(self): p = self.app.delete( f"/api/projects/{TEST_PROJECT_0}/styles/style_fill" ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # 1 style p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles") @@ -332,7 +354,7 @@ def test_default_style(self): data["name"] = TEST_PROJECT_0 data["author"] = "pblottiere" p = self.app.post("/api/projects/", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # default styles p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles/default") @@ -358,7 +380,7 @@ def test_default_style(self): "line_color": "#0055FF", } p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/styles", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # add fill style to project data = {} @@ -372,7 +394,7 @@ def test_default_style(self): "outline_color": "#002222", } p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/styles", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # add marker style to project data = {} @@ -386,7 +408,7 @@ def test_default_style(self): "angle": 45 } p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/styles", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # set default styles for polygons/fill symbol data = {} @@ -397,7 +419,7 @@ def test_default_style(self): p = self.app.post( f"/api/projects/{TEST_PROJECT_0}/styles/default", data ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # set default styles for line/line symbol data = {} @@ -408,7 +430,7 @@ def test_default_style(self): p = self.app.post( f"/api/projects/{TEST_PROJECT_0}/styles/default", data ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # set default styles for point/marker symbol data = {} @@ -419,7 +441,7 @@ def test_default_style(self): p = self.app.post( f"/api/projects/{TEST_PROJECT_0}/styles/default", data ) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # check default style p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/styles/default") @@ -437,22 +459,25 @@ def test_default_style(self): data["name"] = "layer0" data["datasource"] = f"{GPKG}|layername=polygons" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "layer1" data["datasource"] = f"{GPKG}|layername=lines" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) data = {} data["name"] = "layer2" data["datasource"] = f"{GPKG}|layername=points" data["crs"] = 4326 + data["type"] = "vector" p = self.app.post(f"/api/projects/{TEST_PROJECT_0}/layers", data) - self.assertTrue(p.get_json()) + self.assertEqual(p.status_code, 201) # check if default style is applied when adding a new layer in the project p = self.app.get(f"/api/projects/{TEST_PROJECT_0}/layers/layer0") diff --git a/qsa-api/tests/landsat_4326.tif b/qsa-api/tests/landsat_4326.tif new file mode 100644 index 0000000000000000000000000000000000000000..c98cb6f7949729f93c9c19b51724d189a213d4d6 GIT binary patch literal 972 zcmebD)MAigVqg$tU|?isU}RumUYpN$llP-!vJ(V!!#g!V>=VroMk{Z2ykFPh&X~|1iBd&GBmtk`pD~Q&(QGB zt9O&VJwh#ZR@U-4=OaHFI&5T_rsHjG=Kxa$GL?aWoq+-9aYmpEfnEpO1acrF!!rR! zj*aa=Sq3&RzfYNyV?#Sg4>y=*VCcv#nI^lW2xOkavXBVJaQ9#Z3zHg#+U~M|g!UCT z*VlGchb4BcyR!z!2ukW(cXw@VPlbOhP-<;$cfNm2>x!$(YP-tAQ+hYtUR&Fn>l + + + 127 + 125.33333333333 + 124 + 0.8498365855988 + 100 + + + + + 141 + 139.33333333333 + 137 + 1.3123346456686 + 100 + + + + + 124 + 117.83333333333 + 109 + 5.0634858436544 + 100 + + + diff --git a/sandbox/data.gpkg b/sandbox/data.gpkg index 6824f9fcb1a01bab6e35048ccee65b83746b5bd8..06ee70c09ec021077106dcef032a6763897f229f 100644 GIT binary patch delta 28 kcmZp8z}xVEcY-wI`-w8njPDy0S`!#s6PUIpFfYgl0I8x2D*ylh delta 28 jcmZp8z}xVEcY-uy@I)DB#^A<;)&$1Z1g5PC%nR}Xm4*rP diff --git a/sandbox/docker-compose.yml b/sandbox/docker-compose.yml index 2161569..cec4912 100644 --- a/sandbox/docker-compose.yml +++ b/sandbox/docker-compose.yml @@ -3,6 +3,7 @@ services: qgisserver: image: opengisch/qgis-server:3.30-jammy volumes: + - ./landsat_4326.tif:/landsat_4326.tif - ./data.gpkg:/data.gpkg - ./projects/qgis:/io/data - ../qsa-plugin:/io/plugins/qsa @@ -18,6 +19,7 @@ services: context: .. dockerfile: sandbox/Dockerfile volumes: + - ./landsat_4326.tif:/landsat_4326.tif - ./data.gpkg:/data.gpkg - ./projects/qgis:/projects ports: diff --git a/sandbox/landsat_4326.tif b/sandbox/landsat_4326.tif new file mode 100644 index 0000000000000000000000000000000000000000..c98cb6f7949729f93c9c19b51724d189a213d4d6 GIT binary patch literal 972 zcmebD)MAigVqg$tU|?isU}RumUYpN$llP-!vJ(V!!#g!V>=VroMk{Z2ykFPh&X~|1iBd&GBmtk`pD~Q&(QGB zt9O&VJwh#ZR@U-4=OaHFI&5T_rsHjG=Kxa$GL?aWoq+-9aYmpEfnEpO1acrF!!rR! zj*aa=Sq3&RzfYNyV?#Sg4>y=*VCcv#nI^lW2xOkavXBVJaQ9#Z3zHg#+U~M|g!UCT z*VlGchb4BcyR!z!2ukW(cXw@VPlbOhP-<;$cfNm2>x!$(YP-tAQ+hYtUR&Fn>l(h~7SUK>t8N`jwVX`gXJLXN$?e@eAL*F539}k1KsWJ^#J5KbvrN z>^?Zh<2{PWjt@Lm(QQB5-b%bY=VpIs4Rfc6jcB8=!o|QK;GLzgPo8%2to7R~=O}TT zd&$dtdb6`PK$ntK9zZmfG^U-TG=7slTG$wTuz2Ov6z$XMy>~OSmt;8ZNrbh_SiZ$u zUDumz;uCUblX|TT{ivp~e-#jLd9g-GP1UgFK7mkSN;VI&3~MOT4|f}11iB?(EVXGWo{ z+zV}1-PjKMy{3-(MCOC8C6>LHAr_Ax-EJvyX$Yva>@7rKpWl%FvgCIcGM<~5muByl zS0k7^>VL@)f`xuY7%zc-_H^7+gD)ml7q<@@>Fq&?h3@h+qVK@9<#MwGy~7k^$u&o$ zIn09Lm9-q^$DF1@uOdxTwyeUtW81@WWJ)5lhDFLVm8Io%7H4K+*xxakLiXw(ErXHq zMDId={uAgHG9MZvPyg;JcB=vE@l*;9e$@}h(7DzdeKi6T-y2Ve%z`qn0s~oYV{Yid zZBV#MqK}}mAmPtKmfou9>X(*MsLsWxAaiUw$S)wk?RM)&CXz+Q>O#LA>e7 zY0}jDdVopvzvn@}e;zu894Ec&S^El}(UG>Hhv=aXzNL2m>c!F}bEd`!kqn6q$Q(&} z(j6cs1```(Tw3yAChlY)($bbmu*_ND9+M57%g9{a`Zr}D=bs8~R)5vN=8?BGBNA1P zsqijlUP?dW$j}xvv^GrQ|AL3TuE4YrE&b)Y<6oUOADD=vad8QwWGm%l)%jvbVjmVO z4s);bs}w}ZFqLTkeM$K%38zo`on(>pq$g#x^tTe$XRTQzMn-v-l;em<-|(jE1Zfxr zgdKT~f!UX=_yry|$J}y>);7ZsBm-gyQGoFXDX3X9R~R<`stJ44-EJCm+)oKnSWN3; zuvJ-ld~-7|C|@3hc0GVhRzS1gfUejjwP<~;@kmu_80-jnP-*j-?ygtvd~(+Kw$9M1 zWpdeC^m$t=RG^0|HkOwV*KAYRx#g6q!Sn+<1L5?;&I);#xp+vV3wN)Jw*Z}(=f?x- zcYf88@k1}gV9LMOa(9n-2=VuPvxsAOQ{}%xh_&qaphjy4kIB#fPEiq@oQx1f@qAV$ zCJ2+iyy*-S!|)34b|Tau^?Uda#=BSSS#cIH)k;GqD*ULLXJ~Ck_ow^khxZ-nBEAXL zBqU8<2Moxqt!RAw*-Jbzx{ikrj~}7gGz|7HGQ?$y^xUx{VcrX9@gmA-AK4Q(HyKP= zd3d>92*lzg_TMtnc|AlYU}AjuZ~_riuyrLoyLYt1L@lE+>;0mf0r|N^o2JLtWRDyR zJ^0;v2o^q#Lc+Q#L?S_*&(UO0gEXvsQ1yX@Li?H6DmP6}wqZ$RHe1L%?K406cT#O@ zM@0Kq8fTLa>&YqLUE9Bj17~J>tIjtcqkBi4Kf%U-6uFbx`wH**MtEEMFB}l^%w4CE z$Do+}0}d%ZobOJOpd}$XJT@jLX%u3o^b=HH` zd?obIHh;6HT!dH%B2mJ&?6-7p(fY`699G>tygf%oX9DMjmY<;|$AED3hd;S;WnK{*mFS_Cw}ZyU3+J*S&|8s1JK$+-+{{Z zaIL=+UB}w$I{{0`d7xMW2{z*J;-OHM#QxX+;uZP61D61oyYa{St%kFkfCR`$qVmW4 z>@t@|Bcc%$lUbPVOH}4!e~UT|Nq;#uCX@e0N+9~7f<9RPU$DN^Gh%!a{4kbrJ4`n* zg#@3)_D8dI9veTy2hraV2;T=ZOr2f=LYm|EFSdo5#rv{=OtX4+KafwN5)g1?zMcnk zGQ>aRD8mbr#63RdZvIY>`0?cb4&x)eg!R3RY<1SBy3P{uma-TaplspsWOEWu^W85U zvk>x6RTsmlDw?a2_ntVa4dLpHTjxmg*20A_Yp7?sJDj51PO5Q>nQ$7%)RI9^3s+`A zU~Sdry`*8$_*v7bet!F0N*43>WNSe2hz-wc!%@#)1%1)h^4lwJDR-_xnd>TKOw{3R z>YP2D!-UM2m%9hd!m~CFsx4aWdq*Q|h}$RA7~tqed?dzld66Zzxm&P3Ne)g$lSbL% z(R8O`K_6SvuT0&Us$rUl;zD*GrE%X;&x5)2_ma=vjQ^eJU7=9Pr?m-YpW)_KPAo8; z*C#u%N_5EnH-`m6f{j*1kw-X9b-%u%GxS&7BQ*5O%k%izS<#A{#Qf($K|WtNTZ7fE zlUG;IP1G``Nc?j(O=U*UvDM-hb>PVtdl;A-2Nb-wW<}CvT9TulIq|)s*U~*vl0}{R zOdb8JOjCmaS~}LuV}2R#BqZptabx;A%Kv_wI$VedhX^OapmgF2?VrqzZ3ovSmCFE* z9)1UEnFRgCJ28so4WIM{bsIGpO&p^h8J_k`qJ&fQRq$|xlQNPa5m^aWeO)&0n`^kr zIKuKzYgVR3LP3E>**Iw7G+wVC$x-|lHAj>@=69}!WE(-{&MF&dn#-uz27w~lh)0If zcts)`>H5~jf_n_j#s8Gai{sZ0id#XJi8Qy{Ew@YnoSiV)2 zI&?JEkL#5GZmZ$^ItVRDoGrY~0ygNv8fKb`{_vmcAn{kNbaT&+Gj}mMprA0YmzKIL z9NW^0dgJRQV39`ouZnumV- zug!3Cb!CzxDq!c=lK>Ku>$hqIh$V+RbJX=#4PSSH0&zm;?ZinlPQ_}dPo?sG0dk%=-=Rv}rjG;cIr)Hgw$+h=ZQ zOAsH_eEnusDeWQwDpHliS*?%miHU--p|*My1(CV7$7d%amZ-)=WWHk9I61k{oDCr^ z`lsjEdT@TD6^X86lHh4 z>(bfP%{e1c12ML2=&MVAzi)rH`YTHJxn=_r<9e3z5)|GAiHAu1tIjRYO-NsNGzSiy zh4q?~8YaSd`Myo#;(prRY_Q~-H-26I=Hl;f;^2e&wGTVF9zraNK96c&-3MmzH7Q0z zW4sZ0pR2CENzd9*WmV-XDeXO!ucZP5ZIr1V*%2EXDbms{o3O>gtya!uX-=Rx!3h%n z?_8X1s+OPMm-U@T3R7jXiXei&EC;1?8zW5_6JI4mFcK9k=yll5Q2b$Tl+URwt11ZY zRNP-g`dnu+X=sn?8XB0tOv|Ul&y{AVHG*xNUAP#Lqg>TH%_Q46?rZ$F>2KjwPiZ;} zS(>i@8i0<~bh+E86%hg6+7p@V+D6b@zZmQOgsS|di6CKCn>}rAllb>w!SuRN^qG%fQ{l9%hWT%j2GJ)XT~|A?S9RV@xX_55?RUKQ z{#zt3B4t{YGq3Zxk!rRf{y}jJ256qO9EwcLuBVlUV)JL_ll-xu2b|qk^7v;&aqfO* z@9J0Xaq6VxLQ94c9MT;g?m`m*;JKgu!>Go<>KezxjEqtM%XtJp zIHD$ELoM}`hX#luyVg&xgv5muic6v*M>K*i4lOrhFp@F?H3G!wN>Xr}Z*E|om&ZsI&@^=Q4a}8BdmA~O->`DL zVS0{SOmg#8+tzO${8&)aS?iHeTde&CCNNm#_>xiKE6YSiMqZkVT^l`H@l;M_eQx}z z*7GFxsM^$lox6C*{>21V7)zH~sa&pq>;N3eE{JbO|IfYwJXX{QOR_SFXJKGa zYnF$YT5B-_CR&~u&*B1i(cqwHhvAM34dx8@FkWbJL43)WkF{?~muBQ+>mf;Rfi8f8 zPlSuD-!;wf4N!Mu2EEFk-~D8`Fv%_``2{XhuDe_qbTjU|)K94z9sT3W3(g`|X4saD z9U^8|Fb~#eWi-0}7~`NdIeg1$&c~8+#?v66n%^UXwzaiO_q&k@6+)#0v2kLPr=7$? z_!;u9ZGJb#H~S6aqsAv{2@q2oDx(V+xg8&qu&|F=K>D8l<_qG3bAMxz4(MdRmBN~v zp}xu5;$JdneKLT~bNV1{Z$Cww4sRUQ2-4IBkd3cwq)^@tkhjn$e3*yd13vkMvkUp> zi7*(fpMBp#4%2k#OJE=L!o_+mbE2U@1Hn%FvFo(6bx9C5GE&(yc=LwJ)`;1f0=D_W zIWXUFHC{IwUya~vhHN+Y#2-)EGn*!y^f=iKhunj}%o>QE_H!;IELHjU3k8;+f2L`q z0t0c+&h|Ks9lo)GDyCT$KIQOROB>oDA)w;%l{V&3w1_lD3&)FTWv=v%5D|VYWC}jC z*l_Iaq3#G>KQ??FGLHcOMJ!E_%z|}RAiS2-Xio*bv-p%?Qb^6Li)vt z|H2-?xJ7aA8{t+MPusp2)R-m7r%`{!IVNT&@@_1JGRW2AM}F;y>Qa(h%HC&WfNMa2 z+BLbg^+o-H>7dUqYm_ZO2ZuF-sx5+Is^wZ{jEM_jIwmWBB1w)5G#jz=^vkN?eJ+lc zVcV-rEBZ0nEn?sZuRo=D%TBsT=SyaFd)SU9K7~Rh%um%UrG=r@Ybt>B;~)Z607?&3p0~KZ8UOc(b4QMX35!gI|#a~EizHm{ZTHr_u<2U?G$UD z7a0Wy8GHNN7e|7K0WdMdZXJF!-G3b?rucKOkdZ2%k;)RgZpr~6YJxhgiS#VgY^to2 zhfr92EtJEx#MZwrP|Kenk$f3 zVx@}opVLP1l(E1(E}?ycgEJ>jSM>3Ae}Bx&Es0Xnsc;icfy^I`cqX<;Jh>C=?7__T4e$i zKVa3Ij&Ze!lLR(#UkeggKDI~R2F>=Zfb9EPSiJE{D&8_r{(bhYlLJZ?Zi#Vq`3ZtO zH#HSlFIGMj@n=$@7olfp&~fZQ&Q5s0Kvned>0zqvZySEPb9A@RuX95iM{5KQAtolQ zT^8K(pukCYwf+nR^^h?thvk?l5fB~V@ez(BLO*XLV;K@4JG&@X!o(@Csa=M#ptpM0 zuo~~TNq?Efie(4Ddu~*!Tz8sZgq^JDk6j%>-9Q~g0rL%!HhZ$3QlXHy1pn2$r7$oJ z#zH|zOC22cXS`>#XB`-jyl)s>CkFi9f(J{?&6Pegz2o%Rzu%O+$R0DnSg@)ETUL*! zk)d{1m196o-f;^+>|Q%t56yxJLoj*XKNtKh?fv)F@ph@q`UmMx*486kUL~0AZ%ybn z98Oz=86U@Tnfg$|R1m`EHr|B~vjQSwq#@ zQaQ5ts^(kqwKE%=zuDsfCi{k2&4pv9vhLQ&3Hl9RH0Q#~(qi@>k*!8$htvsw%>GQM znyZY~&D#9vocs2s(kMA-$WU4&=0~}E(Hr~7);DFpzdPQ3(`qQ|hL^kRjj{;ir(F2n zI#@G{*7A6e!gR3&jN2;by-j?>vW)d5X=$XrY2GFLaLjHA9XQ2MG+s0A@I2U?h?O8qjGX2mqiM5<7W8OG`}xXL@HOO(5;U zz8)icfs&F^P8aGBH!xZumy~$WzT=nruC5-CZ(|hE!gz@1@9sc51%-`s`uHULa-PUPQU*mO-}Q8v65oysY4HI zT`gqMRm-S{*o*01+WnDa{~*nPqJB=#JL&=t9~72{ZU`H_THvub}~?g?@AFw_Ec2dDR>scCBJ<41fQ1gl8vE>$ zq_DaVCOPxEZzuWjv@P8N3?ZMSWDSGc@Hz!vxv+ENJ{*AhE^q5q5?>O&&@O?5QCGdi z1PHe0on5o)@E$V-7R`iHz`HkE+RsUGKSDSkbSiqZ@qpws`|h|8*arhzf!Qis&FUR^ zogHdfrMioxI77Ct>YKqEr6ya{GG6Eh>-B=Na9d61(bxjV*W3eeACZ_GP-}8W>g1@p z0_{krY(8r}X;qu%M@M13lxeGcYil0bdbGl|Bz6Je;-d=4t9BpS((CwYg-So3h-oq? zapk9azFmJ2T&rn2(wo9$IlBDW`X~<|j6zVAX&=XVQ9T-kIH#r`*nn9Wv_ukA1b9ci z>%wa=1hWn`o~+cf#jD|Ox@6Q1gT8UyHL{_gRAi(an-Ln{1 z4roYVUp~lT-)|rOC^v5Li7**Tpf=4`7(aFCN2khoz7vXre-FNeu$%BQ!0T}RHpve! zRM~-2A!24_rMzi25izw7F5py-i{VSd-tx#AK^Q{^jejX4U9QDjxBnFf+ zE;#afEIwZJz(mh@HNxLtXlG;Q$7mg(aB2cFO=zf5BzJQZqhMWqeVfDkb#1hU*}v4pTtPlZc9@i@8eH9pHEo1!}Za{`55Z1j&iVD&I0gA$0KqCF;tR zT6C8IC8Q>P_~F}6;_Z7Kr*-`q|GvYLxL@-Vp7siX((`vQ-A>zPW!(+sNB)!B zndZsbNfl3UJg#p17b!bzgih_62b+o!o#fzUavVSk*N%bS=2A9d6FDT17Eq$QJf-5; zlGJTj-RD6aGijCROdi$mdkO(vi%-|@ig z{tx}M&!e7CYS)_;Za49Ar(Z59V?$ROIAZIcAakTN^!pzw6(}S#7g5B9ZZt@XQfVU@ zidk5$hPjQ%J6w15rM%P0)@D;3N#K?NSHs^8$;VvR_ig?-rVSg}unaKW#hC7&f%a!c zyG7C#8gUObp2RMF`fM3w_4dJ>iaY(|V#PLYpKf!7+m(RKI40sw(WB?BNqzgiYLxp` z#Z|Txu&t4EyX>FU85ta8>pRfD(ECp2J5(MBu(J;@t@8$U&8YmAFvyd+(Xkrl+a0s` z2y-4kQPFHKD$0&|zuY!v7!}uN#q=m?{r=}D+hyEPP_eQa`Wl+`S-AB%N%jNq+d?Dn z>v-6bU^-UB#o|8qQ-P#|p#P&#<6?J(#LOwK%PK3$UUt=%Xx}dMCHkBFujd^o(PE-< zY!3xwt~0LZ?hhaO zpRUyf-C5d~Jik3#UAq{WHO~Cd<6Od{U2&9_mgc|c{;|DbDFD!_OM(^g?tgQExc1>-gR?_d1J7X%Q4uy#zVf2?1R`UKj2l&tZaP!U4hXP8&#b_O&A%%gakY?c=s#?TRPqkzp+n|lacqV!h2EVl?3F| z!)|UBnxSx5?9%X`AMm=bNd*N9vJ7U=kDm>rW>i|V%tVtJGX@gaNLh4BY#L&h1i`#e zV_Z1mK)X@sn>7@{j$9YHN(CveS@g#C+vc;{?ek}ULuj}Gq?VS$3`yG@@A$#vo=WdwDr;W0P{d z{)%R9o0|BoV2WX^U#_c#|EP*eu6~K5v;3QiPzcWi3VCwj3+?#HMXP=al+ZKE9?zn& zt)`mqKb?mLDi_b(TSn$d2AHZhpmV8NYfq5vco;$^oOE99aqNht{nJg>*0QaBJ1U^j z)vldbYUjUAE#11zJl{&fx%07&#E**`JF%*k0rJPhZ_ldC!ZjV%C($dG!e`l)*|wz{ z!q11omAH}=wK$^k)>i+hn!g^`)6}NQRG1-GdR`U&>Q|x!%GLreG)4Ad2g!YN*B&=W z4+C`pFSJP3+$Ek%I7 zK2@nzcW}F8>>SMH>X61(Qqp9pXQ7gKS5;H9<~iod{WRkFkhD!3^hJtQ)Z<;a=?@#5 z*w9@;jyG%fn2dr0AF%`V=y~N%^H-EgW)xyhaeh{mEsSrg#>=ipW@|K;9KO&1bx7m* z@B5Ne*|&651zVc6j&8IuRhE@`Tj-XQ7L*AJE=CAt5=O#*b)!>GJe5abylO|WTbku# zU-XPt@-KYhY8i`Viebf;3&|^v$>w@iOoHVSe6eB<+9*_vu7yi z`Eg$*Ja9}urhZI&_DfnW7tLyR_OsiK+_>LZ7?bIltC!cXz)A{}>wfERG7O*724(_j z9Z&^8o9uEEFcra%8Bl5A=561T&An!fJD3xxp|VfyCOA3Xj=bBPq7omQQ~Tx4cy$#O zb^S0-Z(+qRquwglh53Ezh8;3TL%z6K9A0hef0vnWh;MV#+_egYR(e6(>&^OQ+ z=Oe~qP2L`)JVwA z*%4+vO-DANnwJMXB}P}#i_L+8FUoVw;PepK#b5yCuD}vJZJ1jM8xjeFiK%iEz6CX8 zzyYpY*_#h$y4}si<)06hIy!(<99%yB;vcoxXcyBpS>2*B-Gi$XJ^l7dM{o?`kOpB9 znLbvO$h^w&X##G<1t4W^ivDDd11h?Jh2vljxpFS8qZ38F*Q#q^FT@W)_9pxxzH<{B z>S=7yp9XWC$9Vt+7}@S*D|>s0BLTqlqMAvyVe%;j_4%n*pkqF&HTUnVGx0jFl%Y!dcb@hO0{?BruQ7 zKfBI$<&(=E&|lVGWCge5TjRDL(5sxKpZvXg3!E55#jvaMCD_e=2dH0j!8?4v##m#Q z_E%Ss_X0ZES5@Wx1A@R0*%{0{NQAGwRk#H7S6^CbkNF5o8ocY?*;3JZOsrE-cu&R< zY3u#;dNgb;et^x8O{m%&G%-Ll9d5zUX2l=1N*OM~Ue3;0wRz@#$uGkiuWr-Wr1PB% z|88WSVxf+cdXl{)EloK#W&|}8v((UA)qnVxZ1nMC_Oc3+xGtaLX(6*dh-!XSPk@uB zd1xsl+ryir$yQ!^)RXE0R@mKxeR%_f?cRj<>;4Xm3dDeec`x;hbRpAk8FUOVX6ovh zSi1#WtFu*sHk>aTDr?Q$hWgRrn$Gyr*`0<^^pgmn=dJElzcnHMLkJMweb~D@*IDux zOv%Ro`mAkghht)#XIf{Px&_d{mR8v|pfNV2MUDPL!&>=oax;gHwwL0L`g7xmW28UG zGBlDLy>paoqyK3APT%DiD?$>;xZj&9UuEleajQ6>#P&T^%eORHl7=0rkN7(@!32=H znpv;h=FhmR>izTjkatdBpMe)P&CB)d12ibuy+b=GG{-t4!Z)Va@ps(r%*3Y(enx-KDhL?jb&(@Vat$hU-s$rAJu z#U)$iEj{I130z57=QkdmX1%q;yvm81`I7bemh;E9bupHWTMZNiz82&Xv@+5J%lH6T zM60OI{*LNfJDTyTSg*`})*TUH;32?EDcju>|0E3Xa-hE-{Y_#sOi3~IbJdto*#q-Y zLLGijc&rwVrP;gPa!#w{VqJb z=Bq?~_s%YK6$)RFM$sz(=f>=58$LpgC})J_aY;!u9^TW)!=>EV^^oa*$W=4Uth~s^ zt9EUj-@Z&nOX=)i}RL4umcqN%PAwo}uf0WXvV8_Nb5g6z{4ciNMXI5kI z_2U969@wD<%@+*CiUHO!eW9ul8m6|>?{h)n&(Y?!WV}0 z>RP;bT{z*uufh5RXN4Exxb&KoQ$?+M*x}H{nJX?`v~jQRh#IJ++4>dL-x1x6g=%vn z{&dn}-?2NZ9JN$B+-Qg^bx{h9)M`+`*;W|7Ne~jEl6vT9a$@Ti5s7@~fA;~5y6 zsw`_d$N6Hu&a^=p zxELje7dz>kZktDY@MWwjRW!BJ88t|sBxb!&+x%6*N0olouQu^Bpn7tV@<)w%|M-rO zwFn;O7mlR!kO7t3i@0kng}6sup}lD@Uta$e+1uaU=U!DE2%4<25ylVAM2qm%wuF+} z@enw8m$AWRW)PHzS_|yOtWs_B2;X@}XfZ+sFe$JKI5g%~;_FHQN;TIB1te^ZA;0&Q zD!BrL^i>2g;f{#{D%~(Dwp7_)d(4|Wj~xm}JZP%IM;4oF3br5tV#24{0Ax`b&DDfx z@bjOu&CFb<#07o6(JURmc-(q-)kK0T$^C%=31k`vJ+dlWVr~^fM8sF9QK1<{G$c{F zV_^nUuESGr49X;W6qgqMN*AuMJxymgE|ZnD7L}WOJ#jR&*BC>@iK|603`EQ;OH zF`JLd0j@||9e)~v>N^_?j(f<_%8*OU|6)IUs!LEXYba5)a% z+45505}ySSplnH%NeW|RN2(}rF$^45?x`q}QBQ+KOQab+MIQ2K|F>$cnVt>68x>Rh z4?`ZzWX=I1In{FHyCbjxYN<$7vxkdXB?*A3Ftz-(J=EqKE8MbDqf8Sg%qPeU&FP0h z0yh<2!UA_-=H4(Jd6c0%(^==AjJ$2ftX0APym?R{)8IvHC_tPhW!tj$LzL(NoD!%o zUI9)+G2dMpzp`*A3XUYT2f&|@3|s*vT?|8Uaidz=K-oE8t{y@#5h53-rfCLyv9ds` zYjL*0a`G#_i9{w}BxeSq7sI>ezGOH?-t`Qq2nV!~txaUr1*F^^^{0q-I2Jn|xg!SF z)Y9isa~Jp>fq`@%(9KerPOOds1L3Yw&%O0PhLtE#c#ZyvWD_CVVinM zGpb8~KykL@y_SkQI=HK9>d#VS^8GbR?NrD!=WrNYIC?$Wr+{!1vhvK36CFRZG91s> ztf;PQ_~plRPpegq%=ag%9kN*uUiXD0blz1Wx^pWj=#I2JYV3Oo%8%5NlP|{7`2G#9 zX&Ee%e=lB>=mIse{)G?{hk79SacF%rfItR}DexS1x~D7sgK^eu zSL_8OcX=>u?s7# z!^1Cg{yO2$p@mP0kFLgtUN@!d?n;5$m-Gtf7Ab9jcr8@rsclkbC$>!^j1w*{s7yh| z53B)m>ic)E#I7gI{hQiPL)nqXUY;XQu-@)Y%*v65xun@seX`}=7qlZ^(l|cGat5?Km_CJQO)>-E5~e{Y>)pB^EoOpx{wSXDR?}tdF|A` z)yEayk_~9}B)i7A48ZFo*usF?y}W5!KdQN2m8??s$lCd4@YhMiiHRL4?ezN_IS$a6 zbfwRqZbvYtNjPPu+6w$LtFC?6#V|@u-DsK7_V!a!3|@O+;Ka?%Jx;aZfALunMY3An z=%~t~fI8ivroJ976ptap;SqvL18LTpc`de?)i=Xn|L}> z;GmHk%3O&v3C;*8>Qtw9nGGYD}1T^cf^k8E6 zoK5lJh#0hI($Y-VaJYC+yZu8oz!a24pa<7eDHIand8Gr3T|OE~Q)WOO?k4v9{!7NS zd}R0uQb0}yykTvwGzgqW`wrjT)jM-En0k7AmwrDNyV$OV>>UY~Ej0w}bdmcmW=DLX zp!7Atws&+-WUH=T4-aufHRWst+;(D%9k1u+RqOp}I3CU785hYz<4!9j&3G~tF+wB(J2;aQdN#e%|IK6(lAj)0uc&xqdEOU4={OsDX8z^ zFc{8cJc}B|3w5ZJ&zrD76GqS9ZYm! zdz01Z=~I-!!s-!fuHrTbx5xDN%U~_RpEp8)s3(<~++CrIpCOIomE#QgLxpGD7?2_< zrq(<%zeIP-t>&}J#i^z?nx^O)M6#$^CwEl zJ_nbcfO0#mj|+G|xne1HyIt*w0??n5n(|qxZGeqKbZucl@9L3su7)f@(V1%~upLy# zi4W?uUKt6KL|(xqa69IK!QA({{gJWn7KbvjVTn$wY~-zI;=*;&>tj!EK>1&IkB$N( zAPP>Fgrg>QJbdM3&O$h!B1da#WgA&`;alqRMQGd-FmeE4R#u!4<%z`8lj0sGC7Y}s zR9nZhM->zwUcXi&!VlXg$BCF~abb}LgT8B^5#&>#iv@8nUR;{@+FA;~iWsk0tR)I; zJph)E+`1T23o4r`+X}P%V}01eo6JLY8#tXH@0J?Y1vt(RF9JRnYn#Pm9as=MT6cT_ zM6JP_2QR9Wxd|n3(AT4TQUGWjaUjNy&=X!xOHk90XCkhPM4wAfHOnisHnG=r3Ij6P z<40ir{2ycZ*`Cjej67170WhXO4cXXUpQuUnsdyA4a2nte;$2uE@9D`iJ*NgELuJZg zqwincIIwfQ1R~2m=M*QB7hDAZ;5*aHsA`P0UNaK-QUT`Rn5K$Y9tO{6fmeqrU%OopoI_3MHi;2a;yI3*%;&rFC1@Rk9?+H!&(a}MGMvdK(@7(%FR| z4{sG$HMDi8r|7_yRwOtRh(S>b8O|0`;v%pbN4;Bag8~ERLQcAsm$qvDz?C!54qn%%53)M>Ut}rqW@bMAQn@jfr z4({%V@Y5NkI@ipZU>a-hv$~Oxf5a|_rP5XNS#(&zw9$3EXr^TFNG=q_X?a9r^`i_w z0wE|aTGc)pC)%An+2Wc82X^YE#gnZj zaAjd?QXzspaeI6EECxn_wO?77#grn?ZN#21WL58RBb-{EyLDN~G4B5ERW~zpiizGr zQ3PGkoUpTDlRO~!fwj7z;NULs3e}`vc4%%r#5*zG16YwQK`39$-5P3cwOUQQxzOHO z1b?H90}u`@pA~HyT0|*v?j1l*_4V+AtqJe_B@dbi5B?}il)OW*IB>Bd9D$o$@0Z0F zJ-t^vDnLoxGj8ne$V?4x8+nBvCK!CmzE#oWHlbpRl;!Qq->|)JxgN7t%pI614^^%J z%|Bh~K1d!_YFS-s&0Ff|RF#4>{ldiD(-Y4~1ZoE703P}`trEiuE$+})ehCVy)30Pc z&8;l~f%2w^)C&5MILgu4)s})IQ?dtpe9hd);(d(rYI(YC$%!za*ns^T7!B&p;%ix- zbj!&_%H)<>@}dxjF&J~Yx)U6cEw4Zg6v|*q8+tuG*~AQPU^F-UngBl3+V95L5t5|8 zR?a*`YrOpmz&(^Pk5oW-t|lk@`dfz845)zJ2YWd&F=Zx}_PlJ%qiYGJR+sR;d~5Hj z0c)2o0rKqP1{V)^+sV`a&o>Vbb!UD(Fna5h^jsRdiVF?X0!yQVRW-;)V4EyS!Vxo) zGix;Pw*zJ{ZPlN9YukN^0Pnl!=HhyKAL&{%BbKJ)by{;+rYZD91%&$E@*{s=URIin zWNdEcAnJT{sNyn2x z>MkBX@-64!I$GWsJw7}B+RJO7f{RS%dR?qVA)h~$dG_aA(AlJ3(%D-;G;^Vs`b&XA zk-jBI#ww_rw%=HRvkH;3KUvNR8VL<9uwG*wpNxp`cUyP&fXgRQFIyaa)$ zVs{?Wn{9uw$bHbq3y6h95$C2?)sxFba~U&eSD;n>y)~T`|#2J zqtNI^P7MXg!IgV0eQRqAwa0^ti?aH)miybG%GEr>HL$iMP>bMB zQz=hU%p(o+6YRHw{z4iUzzS@23ls!N7cYO|IG2_uP*UfPbE}hv{jiL$actoe=ury! zNsCOT9xs}5aBF^(8*ss!3+TbnS9uwokUrid+0cga*MB_UP~nxQGR1B!vXQ0i?#a0t zv5!BuL3PqZ0RQDLUL%ptb7^}c(N_-rL*-?@;d zqPU+gjpKj~Xpz45QDQ@LMH$^y1+F#5&XFdiV_1E>72BoWeq2B~UYgM_dw#207XG+N zuWa@WW7(D=Fi?8ce6&Lu9t)xnqrFrZI#U5~fnsgms{SV1!{D+qqG?J+r$P;Gm4Ydo zrZ+{O{(7#$2(Wjc!0F?W)`|qe(^2uSkJg;7D&rRw4TJXOV5p+Hm3}8DCr3~deaGVV z)v3vOXz+0E?ys{eA8ikA#51ZHuOBher=`1IAvb@`rZ*>0cDiINIP$-*#_Py;>Xc%{ zo2%MeZz8k4Zs8mG;f*fO1@72icaN1cd0s*XRs8L@Gl!rXnQ0v-S1)2?o+%Kkfs-vF ze!HZ__Qdb?i`$F6YkZRix3Dg?K%BL3p}eL2g&Mw6$&gxF|800M%7q^}F-?<(0 zs@NqfT0WlsdEMBZC%3z|(%>;PaG(U{`SwC_mE|vS`qTFKd6-pSRNWoD;F9(IUrj$% zIf|WKA6w#j;@ z*Iq>QQx7cBeFv)CvmgMhXnJw=sI)OwUYG32Jj0@Gkr0Z4J}`#?3edtbWi89^qqqQ1 zrFGWXoP6QM#%<6$2^$we9r(zS>+RU3aPV#Wqw<0NPh3ghy4eRjyo0)-9ut^v!hVeX zVndTYE4ToVJ_e-~3J&tF+g}}8r+%7m?v??R?dN!On_gmQAv*aaf42(~? z8qq#9eF$Gu#~5Tnr-{A!b*&{uz_fYknL!HfT$bX_lxORBO{P7+-q;T#^JeOMpQw0} zGGbz{c2rkOdo@j^rMA&u?b^FqFH;S&esU}^Wm{@P_4-6ue4g_5D%3F6dhx($ZQ|^= z?%YO5z~Jl7{fG-#T(=pA>M$_Nd1j24Xa`N?9*hNKx{t6dWttXxQR#Ke=ewoQ9SQCS z<*LtHaDOmhU69UApLNy#@6@*#u^(E)&fWu0M)Z5h^i$II>CMdjq=3w!+Ir?=477Cr zxGTkPtyw=;nZPn1bg8Q%t`a1ieCYKMcnO_vDtlK_D9y47*OpuxmRy4tvLntxXdctn z4ku-(1O~Oe?r;{wkMr=@e=!tyRdNx4th6<k_UqfYSbcBUbByz$(!*G0s;J0nT<42i0dp({s7BbI9 zM%r}9$j;+m7oOr?y~o5XqL4+Nocm?}1eIbONOnIXkSyi)C z>%1-d8L6IANo5nbLnBZwgs%od-xoMd!VisE;Xgy~@f3O*b4aPyK9|eG+b+QNgtaDB z*KmEIMcZ^m@i{UGP??oLqfg1d6w43(%Qy&)?FFmoJCe+7)O<*NKN`w%yca>DCU@GjuDQ3fE#1>`qhgpFm&}}pQ$@wq$>P11<&?LV*}Ip?oM2Y_ zR#Dn{7bkdP z=>&gfvyH{TAycVo8@ua2Yr`JLiB%-)a`&DeAHxB$*6?IVrAfm;Leu5{djY~iU(Nc@ z1qQ5$%q?v1?@yWU8K|SCriO7ttp~G$2db5v1speUAsQ_E%bn`y`9|Vt)aoTr%__Bsf@;hM+F@Bhhy;BPlrs3P0IRQTY z#UZN~#tQ{|Hjd9pqOu(pZ1fT!Zenz7eai($EY9Rfzbso$k4Mo1#O@!(3&j*CkY=G* zP*W7y828lN!76TGHbdk9T%PtuIsO@RGp6%xHugF>bsLXoOy?q_Oi9&0i)FQ^lF_ydFbz3f zUFE2;{PsBPlX0%QjkB#DHHQ+hU44KG^QwU2iJwBjsLsyoye;7_O{RxY7qN_&<*atgiXH5CK*^%gTU-{b1bU1Tq;;IRB0W;u@k%w^*zA>r2{m5eF7glAP(^jq$9J{7yuZk!l!u0@o2n#P(<}|Ks2s!U%WJR2=sxyTzmd1| z@bZkJw??!MVPe7W1D=-E-G?rzTb zs`ajz)-!sjfB(;8EDQC7+24}oe?uM^{P;uZx^K`prNny7`3%UnUR`P*^+4_;x#-x{4Y_|%;&wXwUeq;<*LGpxP3Cp)eF+F zndatnN~)Z3FUdKTBIWRdNj>}>l$^Ca+jH#|@K+$3gTyZ+L&%3RExl~9-N(!M44#rF zX(fdzhsVO_Vh4$h1Uky1rpfi+kZPAV@?uw)X{hd$$kFm3iin9H}{c8e( zfaFLwNXO8nba!`m=g=bE0s_(k0@B?j9V0o?-5mo;2*SDjpLM>UPw!f;fp@s)ndjMi zU%%_xG~VHsju~&-wFDIir7Fu}IsvzKPrSN@a?+l>{NxEl%tWdnel|7it-C2PzPjY? zub;0hZpd?HWEF5Pv0nO)+#% z)VqR7$?lmQmVz$@BnNHshwDF^)8qB^E(x>ni>l6%y4~@DRCw0)o;31gNp_Nlm;)n3 zg09yMDhFnaco_M_&x=f(0|}b^zPq%cgnrRrcJuPULqKg~CH4;X-d0o%PIyMQj;R4h zP=c@&Rkjr)_S#_3bp)Ce)gcLd^BqI<=3fnjM;BpObE%)Ix6ko3`%k8&Cx7rytaVkJ zH!l}&3dRFd%Zpxp{Mz8PB<4d~bqH0%bx2I#NJqNfABLgtwRv&qF}oFyZkVv=7LV?_ z^Cbf2N5DVgMbU7-e-19sfQyK7^6cT+BR6wEP1m)MTtS`gGuZH7NZ^H?FhVTh8y-Au zR!!CK%=@X{>3YG?S~xu58~qC5-V1l>bkUOSNfK3#5+?)fVQcdq3+qUzCL?8VuhHi1 zFpc-~5QVq|#Qe0RzCDu|YG9&=aw)vQvrM&{=Z zvDWHuPV8(bG8J%?i}w%rDc7#rI0-aTKG+I4h~y|2w{&#oymvf48n#IC6Qdk3_D}Tw z^-E=?BNNn^v8E2^&?GnhvGzMoLHw|mRsk<^zPHuq9m2qHsgbH1H5gT_2HEBm9I>R) z+%~bM*Q0n}$S@@466l)vNb;QyL0q7y9L0NGMx?Orj!0%%Pjt~M1T;c>Vc#LOBM3d;26>K zf_|#gY7W+&3&15|I#<&t%bvV^3JLh7iW#s)J!Hh@gRo%8_0}oI*uJQTb$DpGiV`X& zU$l^TFP`isiUmKH%qx2lZl~LxFw^y>P+tN%)mDwuoxY0DU z?$j}0BG9Z+Tm7b~SYV<1)uGMhyl5X*VWn$718=;Jt^_(TXQQ~A6NX`=r~Fl1v@Kwd zDYT#UVt**t7lgjdWmd0_Q(A%)VOtn(l(oi3G^kDY{!K_tLcJ>d*b4WN7wIjra zH6fHUyj!AXZ*S-KXQeaSn$?iXQ5Wjv@x|$L$M%b7X6EKYKnV8m+E}fN&_#rf z_z{LttY2;JpA;3e5qppVStGp@mZ%}5!~v0bf+yRLHk~nLMzFWGs0J#6xev9Yd0eX?ix~POXE9|W#>*z( zQO;VnEiq@OU6*)>{`60(-uGyEdFUdpm!@zYI;))3@0<%%FGIzaY>9`PIkY^pA3CeQ z7dzx`o|Y zrU)bqa`d>GgXE6~KK@~H%_TXu!$}y4ZW0Nd%Y&1DmgrmuaTWHVh;|Y7-N-dxkewj+ z{rE6Vh9#0Rvt_GFS>bF^&>N>d?rwSEN`p~yfc@HZajfm%@2dL!Th&>R?_dDNlTBA@ z>)*38_h(UE{mx7|!Mxr--SlLx`yWP{o3XL7=xp6+#)InO1^dhvANT6?cw1EXj=iFy z!v6BFwp|2v7Si|T*PW245c}Is13G;M?6VtKVOh*7Z0%8Nuj%=XP;YYb(aHY0bxvSL zE{Ebuo$0S2*4$kM9(dQufu277tH=GD>j}F%*Dyc9j+ECEV$hM@+`tTEf%MHU;j8!9 z_7A!Q#Q<~P2tz*_-EKC22vl0~BW=VbKC3syHToiY?F)>Ssi8Xdlk6Wi* zjg+p0{%d@FrR&T>+IT6d_}5b)?&n$BTaQIy~3%x%Fk)<)i z*5~Es26M^T8**Uup$9+f#w_Jb@m*MREA*4;nYFw7-L7~91axDyH$p6pLT6sdYRoAR zB690DnU>8eU!1MNv$b{kXmMVsYBScTtJ+-MkSOIoWGU3rl+X83yCn)3+rd6`)scbpaB0JwcR1_o)y0vGgF4`152`YE!RGhUJklXpdm za6l#RJ(aAs#WfF2kT~k>WgKCQeSA?PEIHqW21nO@Ga!4Mub`C1b9wm8mtRd=I+^9h zc>ngi)jyS>Dos2wa&vOmGW->0@=8~(Hmea07v~2y61puUcFZ6W9IQAB*13*7(`{SZ zyp#Or%2jQBV$XHPihsu zCMm?E_UlkRy13}m)_&CdP}ydqjkhpjd57_6wFhBfi@h0n2el=I_ZpinX z`#?;s=V;j}YEOxAn+PzdP(pO->Xy9juK!RA08akLE@^HJV%I$PSOm~_Z+T+stKagZ zQ76Om?1<35)5{+19prn8=PwZ?)bWy39*})g$6HW;BYoEv)rariQ}f3cScSj`5Ata; zzh6x?MbKKjz8NH`wa0 zvqs+f-_07|;D_(*R*ksw?OnM3YFym2aWrIO{B=DyCqTV+RoB!-E}L}b>48o9nleYJ z%A4Xpq(zc63X0FoosUrMkD4ba-=XP-23vR6e7Ia(3S;9JCC1m!a$Jxs=;6J;P8a$k zZf}jR&MLH3WgJ$=%;}ZgY))5xV{X}fC4Sx?_Kd8m-qFeRl|_2Kj5$2XJ zyo^CdY-y?ujtOE7{;C(B!GxSoF)iGj;sNp~l08sdx)gY}ra4Un$Sl4zc~^*xXp$Lewi^kNjnj=c#&LTE|($p)`|6$cg3o;V1M; z)dgkfwoYR=EheLR&C&0&Xk?*0+%-X0PMv>$s{d=1KncslD-XS{-X_CpbWPH4t~iHYGb zFY*vq%b26YJ-tPzn>4pPJ6O{jk$4vfnyV&qp`pLt%fG`3`2HF>PsgJHb2)h0#40GA zPqnyXBqZP(>by17&8sMh@?LhwI`!KXd`&`3Ex_@PlMI+V*(YWPrOAA4;75DZiKr+j z&mrlb*Ot!M>tbRwv(;s+I1NYu>oS2|WI8c5H?$sbd%YHdbv}93nHI6~oAqlW4?${L zi_8Ah%Q^+5b}SW}L8}CkpaBC&QU64meSs<^B{y5w#1vRoOJQNtG^P0V-2LC{b|WLw z*X!nk3F^*^a=LMDH$rj0fA>}PzT@s?XPC9(=I)3mT3qD!oi+3aby-n(cv+6*6PZ-W zzTGy)!9DO{EV-=o0L*OG$ohUAmfltk^0 zY8oamE-YwB$u9+zGT+2<+<2pjZro2dpX2#Mf`xaVi-ti64ETjdwa1T3O6c1B^ROMW zvyYZ)HeYhlz+LnLjb&SW?JN!z}Bfumf7X6){Gg|<$(h&J-S1wONL~yp?{NLx# zZKl7K}6jmf44^wY1KK zua?BOUfwsq?45Hfkm!Z_Ab-5R{{uIsOOZ!CsCaE=Z4MGW*qaczC+?x$C}J#IJJ>sx zlS6#+hZytuDqbq38~e~o!0o^3$i??4T>YJ!ORJAmvQrr`huqA6o<0sPJ#g(x#VNr9 z<{!`u{{%ze+zd0{z?`;tvckFwM|o@Be_s;$BnG<$P5KJ>??vipv49qg^dCiC&IYiq z95uYK$owx1(T5Ac*pCwx&i~4%p<37dyZdCwUmi9nqROS^axPKytH1f_HqX!0+D{{k zGsG#9o1nrTIethBL9K3YJ@B9nLbsDIdbzH$_Yyygu-uW7881d+)0GYY5)fi)_kA2? zp(~OwwWtVk*!$xuF0s;*qKc0nX9_}0Fr4_lCQGJB)6fP9I4#&$F04;l7s+tqQ?YW3Bk-0{h=B{*FFnr5&1 zb(V6L{kQER8|3{BWtx<>TQ%iI7XO&^hLtEo$e#W)Tf26k0O z{tfrik(hLhi$>3oj~8Eur1mczFm?~icIJ*!4ReCj9Pr>S zQHv7xyQ=-UqyWth9QFF~jghMmN0}};_LH1U*y9_C58wre`SM=%vA7x)*0$YHwW#-l zn`dq@_C(4`ib!zV7^PQIk)eqM<~-I|q}i)S@kyOd$C1Ww=<#Ck6cCB1%b*c384hzT zQK6``J)9A>G@(h(<4!br&~JY-kHfn{Z{!=L{1OXyxg)dQcFIbhU149~kuF^`NBWlA zkh%GtgEk=+xuBG^J=3bNU+RMpv~Q!)@#pL;?vhI-O(B4$(?7CMvXn3yHYO&%dFrZI z(A$U(AcE`44>hH>n8o99&f{F=;U+(nLL?Cqb<9MLu_@8Camybk<5U^(hOz}D7fHP-9M7cmCZQR4&#EfKJ;vfNz}$A+4Z^vvf;7VtaOryZVRvvg+@rnkh*f2^S+rr!V8)Y29cJ-%<7l#m^fu#Y-_>el zPUpbEQ@H$MAd~E_W=;xUcRBGKWzx2ZpnMv|GKA*jLtAjjP}`n^8)|7fI(BXhnm^?2 z$!0}4OL}l0E}D{u$44i-4Fi#JERCE;JiI(OFP_f)EjWP2SqLcdTs;stQ6#8`*vzGBNp2SUuNaS2U6-_8GdA zIW}OGF36v-7ChSr}l3~g@y>LF;Il`KK`p;RwlHx(E7#|o$^{s=)@!z{{=*hPfB(*YS72o zy^eamC14;bIyZG$=M)LcLdncTYIlETO&NOqJi>9n_aRHh#n`-HbH~g4N2(l+XSO-d zL;fi(PL_?0i^q&ptWQ@iJxk2(;bD}+_2N~stW#B$w?~pwEEpC&)AalVqx0=|4t6*5ub$Z#H|@RtzdY!~(^i$Q!f*LA;mExq zRL4S`Q^7!z$;n4a5;N>D+Dy++)G+SM!sT7}Pg*Ya(ktwIz$H$WmNoH}A%5zG_6XW| z5u$x|p!p+Q)f$dggbg6fm2({ZFQN~bU9Sq%)yJPZlD zMET$3=o#XJ7i=6V3sK&0T;vkw25i0J;NzhS#qOzP<`ChcCiDAa%aI6=3gUv&c=Tcq zd+0G&l}FMW!`}Iyx2@tU)8NEk-G5K#il56bjm{+^a_f6Ab5as&ZPmj!Z!jPb<>%Xr z4Dt#z@;A3EWR%jI?N2alUWlHl)j_h z9gC{9b&B3kr42kg(jByaaPrahcXFM4_0V^7L1hi8Jgz$=H{(FgqNB%OCm6RgBxPvf zZ|XBKZB*^-kRXG&`55xI-85qdH(im|b#?LA;}}>mH)iQ;iS7p>pfq}p) zvW<4{?KN|8P`Hagx+3l9)bY*P({Q^4c<8_p<^G*Y>no|V4ztwVXria@FY#z)9I(lw z2;85dmA<G z`m!Hr=!HqwwrN>syLELNHL!L@sd58?T#sS2-1J<+)%xvF3jvn&0=@^|Vk!Ezg@ZIrbnL^n9k!}Vtag;Tu!GE<$p+$#B2gjC!jgB6gM{O^AO>F$L zsBbSRX`)iLEjzVqo_#cNlsCgKl(#S4*TYAB3W@^Ww%4=ejj04eElYnCJp8P87cgt0 z#m}Z?fR3l9R|$`7{Z@(rh71R56j2^T4Fw?)CvDCF_P;VUL7|M_d;Nf=+a=jRODY2h zDW<5WTFlQ&@DTX9cOE<-g%(-HSZb^GA|mm9tI5aU&d%Sxn((r+AFRx0@!}CWvV|1c zR+F`#jg5*l7$;|@#P(S3Xn%Sf4xRq)q{aLoN%d;W%r$`2ewpJk&V!;>!CoELU0zLN|2U{sz7s|h3ik;OsWUPGAZdlnSx(Pp{^3w&Tiwxe2^U@B*h|0hEYj+chh^C(aF{6C)>q7)2d*wxCP!x&c9 zaA%#Lb8|z_mYAx(I86o{@uzR;(<|eQS=oN8+6`_y*%)$c;;3(Lr@hmEha8X^dx?cu zSh{PKs>79+&mt$=**p#8^(3C*mM@$tQhU^t(fIXA)X(Y3X%GWb4N)NtoN?i91)ph<*EP-T%4<@?748qsQ6`nbNTVn#Ft2!i9|cUyEk+CLq17VLkp z(&ESK`C*}>NQ3I>ZHxqs8>F3&D}?cBvb+1yVhe87EL@JNyUg7k1Ik+$u-Moj@TMqg z+%jTobZRE+i7FoE@qBNv-28N$;Cpbn&e}=Il<$w6n` z_GVxnECsym-}A)p`Z}&~UOcZFd2};MHGf*%(2^cz^J!&Pr&p6Zj-HO*=ONqM4NMDj z92dW7!L-&F|*!_HSEig}ehoa(Cx)TU9B7B@Ip!`lYQY&4s~3 zQN+KgmNj?nwAvA+v)sRC$P7AiKJo%6tSYk;Z-atF)9C2m*b|AN)(Q(d*xPN~ow0-W zo@%K?BIqT~B+;2#dCO~!7Ocl=o=z90hL*s*sfh>5a}9*Vp1^kWve@@i=~PQF_Y1eB z(k^NaGfPa}wiFBg5Byf$=zbtVmw*#@tiX7ZYgr*0SYcb|+vr%()Ry@(S(#IZ0sHD^ zC`}II*A=y~Bt6A{>lc$tQ|GD2LjP4Wk7P)JVqFZ?qgSWQ*#ZKPK!IdaFVL?`e7G-= zMhg`?ayN?h2H?w}i)&IwF@roW;Nr4L z5f7!0C#5DzxY!#`j@@=U0%P}*PuRbGI>2>m&7Vk-C>4!>vJ%r zqCNQ5m>f_Zh&o5&^Iy>A?Cj9Tk3hB}YIotV?4@=pzy~V*P?F%gK0ut-%1zjj32iK* z1E*?kWf}}bL1GDV5Gp9+)|23gNHgXJ@NhSPKboh1^giw zmbwJMi}Fb$MXW}qRb@?qs@pMIi~;hsqtLRV;M=wR&EG&!6K*D25*;2UT3Z2g-JvG4 zB4P^+sQT6RQ+3cgPe^5iQZ{PRU$20H5!TUaLY3O-_1Q{?|Ne#~nTS6uK>8gxb;7~H z*GSm)Ub$?b+?Vx@}0e}-{H|uJW{`_&Ia-_hp@XqCsDUh z!NWs-&l8dwSk`@6IT0g(|FE!(i=VF$B|Uyr?w>QLoby$MaT3ViUt7|LTLl8w;|0N6 zgyHwQ+SGF3%pX}cy(3GAXpO$TSlb*_{%+x7@%IU40PLdW56!@Pfv zEahhnyn8wAwyPoIy^JY*>o8?-sQE;LYyux?uLC~_oCr>gRxgZZ<6!o54Y=& zB`SyuY%&a7Oxu%2)pn6CtZ9CRQuDR{rn*S37LrH0R?R9d?DsJhNm-On(_TD7+4L7l z_>n=qF9n=PcPqbs(LE7EhcFS4#&-LYiKALM?vjOr9SBXMAn_y*48sF&@TVs|OYl>Y}9%eYx;-4d$p6v3(R3aC~Y?cdS z@_LFxYMSF>1n9pJTkYtZpg!oxcbQ4Wr32UvG+nFY?n^T7}} zzvfh#mBWrE(D!8z|$EKWibgw zY7euAeeutR&?5Sn;0#Hj<+h<@L0Pq>IL>xr3oW<{3)o4a78$6rJ;WF|wT6iEzNfyd z(#tjGRg3XV+o4Ie`AY}KBc>u`35fkG_n$j|4hBHqWvpYgv+JmSL63u$(Qb57 zEqvi=4v^h0BZI9pucJA0U`-tznH%FpYC}nV9dU7)8@V~z z+iBiMa~8%Q6tn3a=}W12Zz-YnE|I`KmKG&=M2o^pAS5u<3NgY zTl(b`%QepoBJ7*90>K?KXntNcR{*AJl@IyXayx;6#ZP0v8$c0dg5U7W%cs9Hx97nSqg>MXp1se_B9Y`szh#H zTSOmf7=(y;GX@Ta8n%X72L>Lue_$?n^Ww?XQm^vdk!)vWsX`8aZ&zLa@&Xud_`7?i zkm8CESb*Ox;N9-gFF32+a)d9^rN_ob91UILcDq8borJB(Za zjWU=YQIjE{UML~|&Q!_(!I!Qixbeo&I&*bA!Z8JB83OXi+M+_Kb9faGgvmslVuAu~ zqm=8Lod3K7JV}tAp(p!DY8aJl{543pCwF#Ug(h)?t|i+1MCwsKEw$v|FplZJw()B- zuiv?RjW0`8au|R8@5;<;jDyL z-i%h`Vp1hBb!&iNM&(6KW22%H+W1#B|LTV6M2}?%5XCB2;b5+Y)l|=SbhQj&v*xrB z`!E0hJa6<6O8hVhqhtgTGBTgEp`VWBfC+y8Pp=+I?37D`E8-?V3*GZ z*=kGeZ?kxfsAYgW53EWnr+LMScP*oiD2iiCK0f{wwwCwyebGxophZ0tCn7V@)8>a~4>;O1H0W`Rl#eM*FEAPJ6NHz@C>> z_|wOaQeQml^tfc6H*mUmG%E9yAKS#iHv^2lJAC4Txa5+mTU%v{5P%fh$UcQdY$ndd zCEfjDYvGYis=p^MkMpIS@vTz5J`svlPhB z1$Tnc5vHgm< z_M1o72M1ua#k*9RR4pxbrz!6C6Vf3|*`&~X21rUTdrS?4-s{N3+n8L?`)KXH?Ia)U zTj?oLzb;VaY#~X-jvNW%p7!XV6(TcB!FkHv;mZAU7P0VCLYzjdIxkqmkaRptMPMIicC4%j2qv7ToG-&YDgN`!!v-~Ug8^OIIHGo)zZ|%) z&N>79e=VQxkS;bN&%@CDZ-?o1Vd(RMvmW;H3@`ZMrE!r>I{*I2E_=dNQBON{1CS>u zLe8<6x{*<;4LW8}s}!cBWS-yi);~de{@5l%iY~YKYbn%m?q==6cYm=nyA6wc=d97u z;=*SqHBDDfw`6&Odl?%|lF~YZ_t^EjzGk+gh>A}PS>ud{R=zyf> z(b&|cCnb|4!H1V$RuBR_nGGSlg;Q}3R*KHrK0*OZ zK{%*Q=lwey2Yg;~VmyJ@qOQ`|7ZWc8f8ve)Zv0w|)=nCkgv%*3p%f{@0IQCrgOi+`u6le&)H8z% zuf?A%cUE3qytn9}#%p;3v+wl6@ONGwAYlh~c4YqeO9Y4%IVRLar*219*bwm}Jo<23 zmHIYIq7#35o`ZYQpeLzrQmGk8b^IVNxRDX=Tj=$TkvI-b1D?OvqcaT^ayn*Lw2~`S zToJMAM;UU>_Qk($FYdw=j>uQ0lcUCQyl zo1F~(7Ol#u_#(Xf*T{=~SXOPNb_af|4l5Ty+&N%~N@;2eIYGwK&4Y-sX!4UeD%+y!Y}bUX>al#Ns4L|pZ7$5sknm@#ut;xbz0E8~TRNzP(@R|$$A`#N*#05I9WtObW6;kA& zj4!q|qW6xNJ5%r>wlfofl<*1(OMX_kDBzR}*};{=jSPyhCOx)jze&6jrG& zSh+*iykT8ZUr_*Lk3`h%eOtQlh(vBj$IJHh-+%0vVx|KL++*B69145mA2;9qv2Q8j zwD;V0Zz0L6WCadjaIpd7H|^6-u=NF=VkC?|xB|o$2hS84J_Th%QZ`m*_NMy2$W>*! zOxonpS`F4@Bn(`z6ehY)E`J}tV|Hsd%DA``LU^1R!czz%9~&fR=91Nbq4xrgT356rT6A4k6*7e_?g zIQv~W{h3S`JLCE{t8UtG1>cuJ$LCI>e)~8;P5-C zG>lg*CEoNKrU5sLhqO`H$!dHYGV|}>cp@UF*yIvC+(I1D>FI_y_lICN&v`tlLtWg_ z5`>MJ1z7@o4BmdedkZd#_nTWZ58mEQ=HXideA%Nw{ahz>$Z>k`v5U?H3*ZbJ)1!zt z4Oj;O_Vh3Vh`?JeF8}h5?2bm=FkDr)U@cfg8uk-&@}F*dCUj!3HZEnZw&dcrl^=A`AL@z+W=7 z3wF+VOdMSBlchYhn17W_8&y5H8OiJ0QSaNK$pK*3p|HuX44Pa3JsqN5+4`+^+Jia7 zx%f9Qg4lVYg4BpDnuh;18P;-OS8v>GMZjqEhlBI$H#BgM79mDgb3i1^tXaT>JZ}DC z@*43HmSByU=W@2{_Gu9dMB+0YY2%@xwyiXG>}$=!cd8^v z0m;0&5ijNxILI2IBEurpl;1Y)Q}!D^N}A7u(Zt7%?JQGpq>uYllwc@;1r=;`avYrl@I=jKJMO!X-A z`*yZ*@?ndu)i4v>1r`w!&f61zT>6A5ff`KvG}Y2nQv`svk^P%LK%(~GriV0@Op2{F zU1s6%%;BsCA{7Ar$utqx>WZ_4vxPk4gRFKnYl)OI>=ZYe5{P%)v4b2tn? z>?9tOvzy`tHrn$ysd4~TJTJsqP0ytMxjdTK;WyvpcO_8JP!U(q(P3Xa&lDnQ42^Cy z)inSHvW!ewZeA`CK|gzsf5q7k3z@HozGT^?bPQ0Ibgd>7F%Uq0)}YA1KRd)yIL<70MvMU$=Twq*~@_VzzzT z=FBKkWlS61xxg6Ml+Z|V5J@Ptz5W~c$>gJ^Y<}TCPaM#vcdvt=U}#-X{*$R_WEqUu z?2c*vyiELWBjw$eaMP-iIn9SAt8$$NFr&0xsupKrekvlH-`m*8NSh1>?!Bsw+NB;s zn!~5k(@-Ff>@N*@p1uX!))JL)V+M6&rF3O!WUT9;)3X<&YEj0*u#bygr$r@skE&+n ze?ooSToXG4y{AC-r4{EnBXe1=cHQ>lb7=v2aUJ#rK%# z-&def&M3?5#7MsAr$XgVz#rcOCTqeMg*arn39T-l zTU{XVS|_{FRWQdrINdh|(4rtFd1bat*I0D_CM-1#t+O z2W2I6o(5FXJKHY^D6%oDkdV?6C8#r`@Jvfp?a!#&yK|K&SXGodYqr(zx;9GzbciBV-BcqQiG zhyubaAvxKWU#b#T790wScuVv$GA5Wj3G7;x)bQFx$WL2uof_wt@a$T-w~?^926L1s zjc6bTn8eJ!4aNTCnQg-=4Q}?e;}IWeq_nkFiHUSnG_fC#SgPQqj#!dKN%3-QWDLMc zwjy(fw<8Y)m>OA9onq@Nx%PX2Xm9yWQ+7U!X&KTIeOoMi@5UovkpHoXzps@;4xgIe z@2*fG{0pzFPsXv~ZnfGDf90#%{t9-W#YFi;`2@^Olt6bqK`&!-M7 zQCV9=@>T7)Rlz@lRcD{a1sAnz=vzLHcd?p-h?#F-x)Sl?nOM}*wCMg>OXhEky{xe@ zH$ph<`y2l`1Uc3lSpD4g&6Mm@0lo-vlNXx!EYKL|ipM zFr=(1PI+Ap{;Nuh3?N`YOvkkS(Ktc1T}^4o3JuO_x%j0&4y?J_S#lE-h@+C7ix5WN z<_sc?aXhc#3^PH#z`*6%#`?Aunu5=cF=k7&?C$R2>FHQO>|CRADxtBU>^=xXRaV>4PKX8zVa|N;Mr?? zg`sm%QN!gy`LXiDIzg9%vJdIa}&uTvVY9gkI5&~rz(&beQwT=^GZ#BfX73Kkf=X4imE7@YLiVjo(?Ie#|Q zy(T0qAqFDrF9zR!`D3{&GZy-@RwQu>u7{%eV9#UY_J{k5>|mgU#$#Yi%uV&&TO>VG z6z5f>(Y5MSp&*FE`E_%qL+}aS4!+=b_kT10*)uE{`=uQa3%gUcjo?|!)jH0`b|IUI zW;_P~#ma+q#=UEyama=g{FT+Ijk7H` z^H`8_b0Bav=XHLEIAA=rvvd0_(8^H+T;y=e6QHIYZP7#s3V=%G+Dd2)3(7O4@yuLH zGo3YsWXB(1K`mu@-KH4(13C<8uUYV5O`}<-V41ObVq`Dicxt}dV(eUCBF5>HWHCc~ zJ?5G3mLVL9n}BKWIiUbllpa;pAZ&Q3vz(kHZssmP!nt=@MuG@I9GUt4oAuxvYKA04cRZ z#!YJm{!GO+8n#076(ae@N$<~1usLYx_5t98v<#gPaEO9}yf2BE*W@P|L~YFthITo^ zTp*Sr;0&YU;~U~7Q6(58-lRQoCAH8^Wk{9VnELst8Spyc1B3ldxxR9D^Q(7p*P_OM zOJQ4?t9ZtL26+1djBhi*OM7a|e+LPwGe_r=l6kQ|z^tT%5@q-{$obp{Ug>LSBGtvk zj7CXoYnPdor@}YrQBX*c<74A7&gJDA{W4%VXAL?b3G$(%nW2*?u9cI=2%R#N zfhhcX@8%+YHqg{o7L4tVB7?S;_*HpE6*-n``>=9^4`IivENn63EUW?>xqLxJTNu3! z0bYN_nd$C*O}k$`AY%w6)exm-{`QuVchgH*)b~}-Q6%dPDx;AwA=Y~bubEd5>FcH( z#*CW%FvdjeAa?#Y1o^5+l-$4NIwf~2fq@nh3ivUa965@GeW!|w{2N_D0*tKN7n&SK za>f15ib?_-T|7K=tUGR+9Ag%B8@z*qq{s($5muv7i&8U;9k&hb!2uHFYEBkj@zj1P zq#*3Wb)kG3_?O%a9Dt}Hfl+y5^D&C<3fTxRq zpl{Z!rWE4Si~T_EcS>qCteJb$rRnK@KrFD!?jvRU={e<791{slI!F}|Ix;3 z<*aG8qk~t6kvTKQVgDTS0OSc>B*h~!Q#|@B1?64;w$tNG@om^;LMOsQJ9}p7m6~!w z(aP@rZepkVEu#2f<|apFw|#f{Q`T9FLb!io67GuECG(^M|2r|+-%=5XgX$SL+x7BQQS&2-m2(IB zmPq1{7Ey95tanDHm11-VK-j+qD+&Udd&QuvE!qkBFZk_$l-`y*-1J@JD2lVj8F8T) zy&(5Yg7@X1*(3v4=!QxeDM_^^`Kn5xIFT*oN%}s-VsuDh}mnhJ_k`RlxbTzSf3gC2lDQU1N0)3+s z-L*lSQxTVbJU618;o93dNRXh%G;7DFecsC^zAQ~C4Ku~v^H_!#BQhy8_~e9e75y;m zn{awxPVggz(ed7D$8-1XgTg60)`(V$HI}LX7QJNIK@lu%whnNx=l%Kmr&W})wHV9n zGgt+!3t^Am+t7UIJaGc4c#$W%7?oacLAd^#WAj1) zRp=L1)FJ$s$go~u&2B4r^ys~XJUWQedr>$w%^F>H__nmj)HjgKd&U1^nsvmJ=`n>d zV;)P2n{mGJ6HXR%Kw&{La|XJF{l2sRII(@}Z^Y&+AfUpW)@|6G{<9C7*CPF~wN*?u z`UBCpVEltegS(!dZuiIn(dDnJw%vP!CnH-I3|fr9LA1<1vaGqfVB*XsSBTThls>?T zF;x0z3V&o8u9i>J&5q$+x2E|MB&;x3RBlwa{W;H=09!s_s(r`2E6p|A8+!hOU23=0stgEpQCgk~q&b+4) zHue>4!dXz}UcW!Tw@kW*SxC2H!;^*wR zeO=rf61s-6&3V$qQAC5r{Jw+dA^Q|Brn#%ZQ-1tCbi=u^#+nxG`P$i;&o_-8`()XO zJfaH!i&~8ST9=_q%=KIFqfvvTR)Nrg{QS4<7$h}5*FtKno0YJo^y7hW;hg} zqa9vn^QtwH4x(5)+sfxC4G-pc_k9_8oqByUue%o$Gm#{_&~Pb`*{d! z7&VT~z-=^*EQ-SYEpXDnQAtBTlp0oCrWqkU|Cxef18Gu?7H*{rE7$XceTs{rx)c12 z%lsb}ehe%*PMBJ@`oE*qJ`*3ZaV6zVUSB^~4;$1c!otMr@okll-k?7YygW1>RB&c% zR>YFhon5z{uee-mjbB437c5LZdbt1lc9Rl{`tBX#BfH{HCj4uvS73MMS?G5-d*mFW ztDfqF_3gQ$*hgU7-Zzk(&9u@9r6?=itLZOm#f&$_cKq;;Nb9Jh5jM*H{lECHjrk0F z$Xn9G1KXok)A#>j7IZV2o`pY3#_n@Ov(;3c*uHY63EwqG4S$r4O~a6SNGg)s>zFct zt%wU`%Ora(&B>kLJ?8eI1UowuhRi)dx5vb!_`^_2eF8O}fs9hB?cV|pr?%T46>;-z z^QTh{+)JZFXVHyuG5Vq4Fc%zR+l%u0H(jA48sV0~ycj42Uu+}YNFFynPXWalI(M(j z+#DhIOVfDq{~hSJJplopX==mSL~>F|Huwh;VPD;FCtZep0Rcx(u*(FswtSP}5m9e7 zGoA;jA=?Eq5_J)tNXp-dSp~k6P34ou_8HYlMUxI*jX0@ua=_C(Y*YUGHTr zee%KexQdEvu*nQ3)0gET18zzK>8r@}wd|`s0}+xKH~z5R`@h5&-)Id7SF0*FD#C*Y z2L~q+(_!X*R~yZyUj|PHvm42#oyUHxbuETZa`%3xN7&wtO-!U-^P)fd+HTZ7HB*q! zA%W;KW@TBwiil|H>r!W?`r#T6erWW^nmG^ftY7c-JidonSjgY`72@!PyZ#Cf^mf7w zVi#(+{AGgkVKp>csn_ zYd(>s)r2-ovZZf2);?tF`H{J`{GS5-eO{j)?zzyA>+!RqK9=$!6E$~N382UxSX^8n z!A~9dj4dgoUADg4+Sb$u0@^^Vtojcshh06%}% z%4!$weW3xSpEzYiCNxg=JR=&LmG*DjNg}`=8_Z`Rd-EP# z%Cm>f97M}9t*MsXpeQo5XKIC!`9WVg4%6I;kk@8}|GP^z6+Yw6#X`~3o2X*Y!FcMz zhvQ0f@@e)k5}*5}@i3sMWK|{7R#))~tRUC^Jcp6v!<=THp406A-pGOZ%a0p7{jxgF zHPpg`LBjRo{Wg#+!iFK(b~iH`_|bqPa5Q2&i610Ih(M)eXH{oBrSDF)w2>pQrNld1 zj1~58SDPR$u$_v`TOnlv5^dQn-$HJzqu*PTN+9jb@&?{~F+thxt8Q6`8mtrxqOb2sDf|9pJ{%ooKT9rYScNWPKR7SVU(0(wO#X1o=a4WW+hP+On}W?y zS8cgBDZ+WB2UnPnB%UfqzSuy7&qF>fqE(|Yf1GcIjw{~93YeE()@|ZAh(L}$te7j;OZ^<1Hd=NURjE4!3B$jgTUd#hmXA8_Mc!8LB9O&EKwSS}dk}oRhZy;5zVRfVB zV-u5iBh)c?IGDTD>MJdaw3*@S0*rH!25Iu$$FC&QLe4(@9Km6uiTbQlB6hqprm1f3 zYK_gDqsT&Yf3P+~vzV7(lC;xE-r54QcyIt9v}lkN93U$@!9fkl-}OB<-Z3(K86rrp zFe2dZ)$oiZ^!>yt#u!YU{^b{j*#j?6M3-w>~fG9W^fsmw;-T2Di9K zYeTTJ_-k}=ZUpa(0T-*|%h$GZPJ+wtEeeCh#l<7Tk#wAvejH(b2@J@)*8+4&X466R zAn(iqzA05xz?04WEl%Az?Gim{M^z@1pC9*w-&Z@yXP2!!ov!`vmGfdX=&%(>PEYsS z&bg3V<}+HgX*mSFf*)VY7MpDDT#S_%3HVcsr@0X0)JIH6NGuja;U4|=^j@h`G^NP` znLxueyznTNPC%lv-dq-pHScn zrvw3H@23nW3?(CzF*BqgLb|-FeZ>keYH88>xBeKRzkiO=-&hmYo`USYA{v^nKbV=Z zuAk#4X$^@>TyqQ~)XS-3AdB@i6SGIDvqAwpG*Mdm=08WAA>4S7PaoxkAfbckpa`_q z&`wfZe>D>xOtM$bHiV_2GE7VrycBWs8yQ9` zJ#%TM?C2$ZC|WGYZ{D9}yDhqT!Be-DO-2&#i8 zmeaZ(F_N58jq;I+SKrPfeDnAMK8=}!mlpO&)uF!&!iG4wLL1a3gHgUK=GM>iKD^OO z%QH-4HJ$SKSUSg`$6oL7F^O#kcA8wY?%r`!&6jrviG=|@N!S|y>{l!DN2yU2dh=F{ zYix9#j*>F64;}e);l2QUIL2DvH=^HTV-{Lz+Cw|80+2osvJzwMDv&MT-t<6htM#!Q zxt+Xg-`lKk0Os4B-ah?lG=Lp6V(>!u;{z-mr^m;WkO%VQWmF19J!DR)nFASVY*sq6 zqh%6IvT-B(ixMem6W) zFs*r+IdL{p4oA-G2*P#cm9=^5NArtJY>K|_iHvD#%2LQfia?)U-oA*QHEo(53pr7~ zWK#Fvb@TepZjak!INCu|JgomY{?BzD_DA@SwEIAZ`=Ek?i<>pJWVv1q3reZ6LHh9c zhj)<93%b)b)X7AP-*;g(EfE)eFa5q+nOCyT^*53FeefL8!7Y2Ygz4g%!{ zA>-m#V-WxCAnZpI@m3f{6ymP`AOl>`z2*k+7}&i)C*pvZn3H=M<`8#J&)%g4{7~dt zt}ns2$%3J4|9Ce5k%8rQJIGF6I%0fB$+O;pT%9u5+>w_Q>$`Z~o#TL1o=-HQWcOpSDb{Ik}jb_JwlsFEd&m=Xz$3+lS$=ic<};Rt{UVYZ8{sYJ|) zxq;B$?tA6GJ>`VP?w;tQwRbQ-!N7Ey%*s&laoC+T*HHa!^AQb#I55ovLN*1#VK&gE zBAVZyOx?svA^XyV!Y4wzIIMD2!A`DK#fRA_KE&ARVRSu|U0E>;kf;ww+6tJ0h$zX{z$?wddF?}s z`}u zrx~UO@0*Mj{CFadR!x^h5Ljh}*kz^BrRHlcLPVN;_%z+X3M-@@lBgkz+R5|lfZ(`% zDI+AEvyXSP(xsL+86*~{dIE-6Aqwf4v_spuD$XyQp$wz0uQ%oU|G zHs+cL)E`=40m(>WQ@n-0H|@&Zzi{~HILgC=0f137DD!ML z2j$9418S6?z>KvMJ3yvJnZ_iv@c;|sN!8q>(i9}wPG-kP@irfp4<;sq-e~~fK+d|N zzYG=mwvavA>R~_kYUg~r z{BHYO+CP2u6e^w6_FsMYXMVX?Sjs!+bbx2AmFcv-`J+nIPgAxp;Ih$@AouG&NojK%oRVlI+V2L+kE!)Y5bLDe0*|kkv7UEA;8Wl&Y$CxXWzU1 zTc6sZQgzu?fm$k#Pw>5CWUlbls!a_ zU;V5?Cg^s&zyINipfuB~t8Kwy6H+m$)l<4I>7zdjxsRIuZE7Yb1pYU@l!jinNV>i5 z4QBnTibh*KC(CM0Qv`za;M=$HS5@j7$%JA*sL;bIVYf$z<*a{mLF+`OQN@#ix3e7A zqkDFWw@t5K57o`uzlg2-C35|*xCrpC*`FQ_K20rxSc+)|dblOSij;GN08~~(l0w+8%BD%i_LYOQk;eMLW`Zql$X0u1#k|8oh`GEni;RZoOc1O&)*Y*86fS=aEc7 zj`4rwL?P{A$&?bg!-?aSmB}?N!e>7u&uP!1-u?HuHW}rV{l;qrZ*yvf@Mqk=m}@}+ z&5CM}?}uo0RZ{8Rk<~=na#Iko&=7#shZP<(0!)_hpoRv1cY)=1LBF;ff3G%@O8M9m z1pNFN8G)qxcVrInh76qg(7aDDZ!~UG#)FL5wETp`&2I~-;x+h>>yuQ))vWM4qs$;z zjcp19Aq9Oc>RUpf*u%icbfC@jpYQEkOJUFMouzpfdv4r=OLQZv;siC`+NTIo)~}^x z*0ihv0Vg%^2Kv>3x6*BMzx`T&HdG%r61_>eL|>0g9$s#&RMHdwO)!anO$#S9Om?}= zwBZrq4SGq+ATz&nAJ_2}?%}W9ewZTqN_YSK2E97HQ#Ss=p=GMoHeoN+apt^;HqK@z z8G>3gGe`^bZ;71+`QbD%;LfWGk69vpsKCLfnD6S(sSe1T7N`Z}HOl=%aSajV9>8o$ z4fRrq)=J4(0_h=@2VYOc_QevuTTQvX#b=&C5PRg{4!@rC8)P6kpi)j_emdfdUON|F z6>R=53DAAJn`}hWeFvu3D*hb?a>oKUVqC-MQ?nlvWXaLN`lQ5pO*PqR&8qcYYOR$8 zMkSwH8^fnnRllBzfxSa?<@+V;DR+iUV-mtWgjG(2Jl>TORuiLe@Nb=cAJOw8!G%zu zX1AMiTdeaKS?~hNNFQbfQZGS(lD8u@Se7o|b`uhph!>^AC;IhU1R4n>qeusx{bcrCr%3A;$rAqU(4^dOwh-0YB zP$o5I+>U2T#mg(10D{Owj9xNSfa<{rWp!CNg*2SPP&2lY-pJIXABd>yof3x*ho*sf zmi%7lYhxu`ctRe)MbytClZ5x1loY*|BhI}yQ(yL=$haobRwyN8?8XygPKeO5~|1Vm9?J>#2&P92*hB)Alb#6OVU^6oM zE^5NdyVAg~Jz<`3O3y;fgGrF)CvbOF=hKGX=$E}2d4@EX}B$`oM=hs?WK10!G?Zw?wo8LzszG$MC zh<%GjpL`o-o|}t!N%ByEjjFfKX57NUmE70isa{(v&%|`y)+G=rCiJ*MEDaCo7zYlg zC|nrm-GGuZl}QA4SX}`XB|ge;kMjjtl6L@^-Wv{`9vQ$J#JW)V@8OiU8~6 zKmAUMJx7}H*m#y4C8pWUs87!K&n)++GJBqwS=KStz?a>wcotv2bQQ`~Y%u7v$a{a@ zN+OZjH$O$OX+bWDhr`>?6IlaKv*(Ay2qZe&+rz3Tg8@1Jij&hRDtFt5$3U=gsa#mO z_wkgSqiMCh`3ibqOz1J*1l?FpHa{n*a}y!X<@Di(MTfXblQ9|5cxR&ytwiGf)is7I zS!y(}ql5tS_d#V{2h5CO@%3<7a(Q5(YR@Cfi5=9w@SQY=0Q&t|Ykb#BL+QT8+cdp+ z)%eadCz8uxrnP5fWhA5=P)ofufFl(Pg$B>B9N9yxC}XkZd+4`rL|#;(9~#p9^hB!= zSQ={cNSl8wpOe1lzEA|Qjn&od{|YeM`0PO$z7QiN zImmI&=6{U3a(Jo1gE9ElNQ>%QG|R^MdGo)aBx0NRo#{#tE+`yDhAExQ!!Mao{QC7t zLKr63@89{BKC}_~dM=f9l&OCMKbx=GkZthrVBJI3uGLg*wj$Q8Ut!m9hETkF0trq0 zb-%l)m^?c(NOSrg3iCmn3>5Nyehznhn^>ik0Cj*3{msH%Pya=m=7#m1E+kaQIC3=n zPw1yJH;dgOd|PP7scWAajOWR`XiqV5tc*qxAXk=PgLI;{bJ&t9C zd`Pui%Cgvp35vbF5Ay19p`BP+ccTV-$bxfLO+!UQPrEp%{jI#O6z@&RRl`-p+Nt zY#M?pjRvh2tFtHWZC|RW351|=7Tt|zAp-`wx?(`?5^qG#b{fJrl!V&*e>Lb-Y)~dh z#;3_(NND%r{y3wNQ&a#$<8Afxo*O5dOaC^V-cBt%KPQx*-4<+Lgpnilf94`p=7H;(qM2q#5&Mwuq3Td#w7hORow(xRSPH~=R8bq0O7 zqM~!7XlMaV2$_GKR(FIWsZ$2}vjL5SZ5bJ9s>+p=e!AuUZrOEv2>JL9V9))(7USl# zt(Syh&pg7%?t1t}$mO12YisvC$o_$>14|G$7);Fk%e1;{^My7B%XGo(O`Bhkd`GeUxV7dQRgg8yV8>&|J3NW` zRyeCGcF{-K{4<=6hnfT(4?zBRp&@n`2DoesNA#ZUWtyHdXX?Iq)_#6u(;^hEh(9Sr;?iWR02`F40F>@^gqV8v-rTQfls^JnP8 zBtn}Ks~j^!wV1c7K2-ev{jVh)BMngdR005;r$6M5#xSF5foUW>O$qDH5AntU`aBBD zX!%hyHzB!<7A(C+4<>~mQpzJ^l=L6~2#5&TEu~4y;^ZV$?^T6d9Gq<^bb(>1&w(%? z>+^^<%7BR3G&L5QIGI@lUjF>dF~9-k4bKiL+L}LJyzFk6+qF;fQ!c78f zP;>WD>>cAa$q{D_oG{a$v8wZ2gMP7Y&?NANZw9opcY56l0cyEaEa9Eci8MHv^b1X- z4+D?=CPfhSoftE-{G8$DYnRt%S=-i<2k}y#21Lluk5e&Um6%yLlSouu`b6B&qd=wl z=YexA9qJx4GA1l$Z8@c6vrrkx?p{yLOXr@=W%_y<3>hy$ENwXxvPAp#nNw!1=$}2yAPIv^Y{ASLSTWL%x(JEm| zL7D$J;UVgJm}f@zBk)3-ezl1-7{;m$|E&<{1t5uym0GB`1#Ak@#qW(-uhpo!5~?G+vvxfF zxg4OBTs+6mXVXr|pNBVLw5UXfB6p7t``iR#K)wVdJg5{NLWT*BMWTkKaEdgHUSSmt zMxCH9Fkl~9GoWEO75dM~;oyygJ6_e()NXuCG5V&00u0^h&j`4&u}L;imof~EL&p!o zn3Tz6m^X>A#7z(#xxS8z77KD>qi<;cbM*tDKK1vY1&QK@rckauqy zad$Y!yWjh9p*-@zr~@X*N*$RhC(Di3Et*JOJvRDqNir!IRqxO~$K6B&aKGQ`l|mm2 zId~HMnVwd%;q(4!;+1DmRbWuT@&sjzANH9R2Vnf6V_YQ9ggPZqCaN2#b*Hh^C~=r4 z@T9e;8*iOf5qU;wtd&MIDp@%Fza>IOc!rQj8E3bl;Jw zPm}9I6%u|oap;7bPtX=c)mE~a9mr2Y8k`_*1@x2bM}v*v1^apEU7-c`kVF{KaIMzl zuWd*xNz~a2XpG<}qzC(xB?f&CN1c8Fv;yxhG_i(i-VhIP}1i{y58x$5Bu_cA01sVWgRR7kX*bb1^3nb%#$C42|i zVF_!A9->72$83pG$Uc6ZJ0-||)(?ap>XGk6`BLk_I z9r5B452*|XZjL+Yniz%N|J;n1ss!bBpI_31-`u!%AB2q`tQe?1BncDvt!G7m4XCgN zH`=GQvHb=_D|uwwQ+AQzLEu7Vj7PpuRXqtsCU%$(#jSiPRKW()&#)SfxQR(5B3T76 zhUCG)qhlrwa4L_L_0Of+5-$abc0)gd2kv(rg8uM|n@8atwe!~*F)-M0cLA*kw5s68Jk~u6btkztrma}2?$VhIaVJI#saSAKs$F|Xz0hmHT z?EYacHFT?x&z+$)j=79O4ka^x{NsvY648T$sl=AjylJ3oyIc@$-u+h~RF7lhgRkl71+Bkv)3X8tptsOP+lnupZpf6hSV)fve*pT>U4pG371G!qZa(lA5t z!pI1)8dpN8Ny2#MSBO`5PW}tQtr1L@VL-wksA4~ zTF9GIs>jQXx_UqA^ow+R`vld|v0MG?&^C6*p5K^C-hb#d*Y)G`^Q-G4=LlxjIM*W6 zLD0F@W&2j;&-@0K%y@l{E9xbgs4iopUeIwxUjT<%%21uUl!PW+T)v6= z_v?($G*@Lkk(0Om>8F901={{6Ydvw~`{iuw2Vt0hQQh5Xuy4D_?LRlRe%xH=$!4^Z3{YN@MzvY>|3)wIx0+bwCuCj=ougQ-lz62RYFwc^u3 zVDH)Z_hHn62hz9FXetp0(Ee@z6vf|m?RQ*zuK?S!e!gy+UBVjILx=O%+Z^LvjTe|( z^xq2ii7~dXzTQ`|94d7fL~9p zv{csq8k=M`u|A+`c@iFkpF2*j>C*ur!C`QP%GSWa4rRP|*<4JXR5v-P5sj}bGyO7R z%bGlxDsKJUyn5Kpg(!qAZ1U)r#rx+ZP}ZSZr3r|{tLa$CAoJ0w&<2CV!~e3ZHu>X5 zz@#~Oa4FdTbzj8dgLnFm1feac9nmC$yx;@cs)OVqQfNav9vP%Hv_3#*$HPTi#RbeO z0*KJRV`FjRR_SkPT<8+ZU@~~6anmzI)9vNhAZEH>2~-uEa{s;T!y;Msq%)nJ!v#;* zxm_Pg9uWc=Jh)8anAOSVaEg#5Z;)R+gRCM)m^UK2K?Vb4MWsc{6A_d2BGikC`-`5C z+&J*b;6cI-6DWhDGHI84Y9aN%T1`wuEr{>unYp0bJUspYdKEyHMk@+H>sIdGczI!( z$Q!0%%j+iD+8H+aym@|_M$?{OjLVk3l)Q|s91JXnj`jrTXU_}_45dLBAMm2b0&l-W z;06}pch^t7U0ygm&G2U=6+gw|8v6#|`oZ^;g~L{271`Q+$0xZBc8sr#jWCC;PD(|O zu6U2b%gUax(}%-Q`WN)AzSl6?1Xkb*H{g0TdECPKzheVO$-LwUdtNwoK}(RNh1c}! z7xSm#&&FT~y+d7Y4bja^01fw`YX85G5YiEbSyZYE&Qx$CANHmPk1r*kj=MSRVuQs> z9#~N(+!??`GJ;E*&j|$?p}=Ad&dYig?6MYmFgXD4lM5_#D-2uCkVbHR+tS43r3Ez5 zA-afp^JuH(h>AEODS9yJX=!g~gL~s|9Rl3&2w>`u-;%)yVPG($#bHdq1yXgmb~Yy?51=j77e5F5^s23tP`s=djck4)Hs|D5v>cLPJmi5qkRL*2nGNpc z=aKa$4GoyS2ibSP%nsHXrMPIcE9S^AZg?B2-)Au~Ei6RI*%UkEdsnEEk)7PknHZRu zq(>{TBqE3leHp|X(LPNnOYe=Xf6_v_Pg5QP#B~?qV*YZV=@TTpCn?|t)W0w(Q5aF; zyEiqxX(xJ|ErENF^TU=mzjXIr6n=fb#k{CcSt16j8ROqF?o+@ zkbp)27Kb!78MIIN*7=Y%pINmtCF#8=a4SjQ0!skTAos>=;3t?PKkYA-lC47##JG;x zhsGwafJ=!jsoR+SzWN-wJ6)oKtVGoVAam3g*krtV4m6C*Tz`tE3vfJU@9jfFtEber zqm~Ih$ERx@Z+t0#T_&GiwKi#VbQWxR{MtCUouRUi3cdM@q(;Qct{^}iJbArSXW_7u zlxGk)V{B61#&b7FD6&R234mVAu`w)nL{@G(o0w6FnNczq3iyoFUO?(lBKnt1EQ{Wi|kC@Cef8b-{2*+6Y?o$B3pLD_;&3b z+75#m=i)9!(wU+muMD~$=lJ0Kh?N83Q0@u1<%9RXAK%$3m%N!&TaT3bfIF70?{p%~ zg8@Hb@bsQ3enI|$?Ws5Y`PrQ#X9C|9VW4ZGID>Z0*fJqU@{nyc7eS1WF5LDG7xv*n zJcGjD9@!Cfm^0d5lDDzoFelQbJ;>G-B*HTMt=LGDhV6F7HWCt|w4l+9zypt4lFL`C zJZm_`Z3umN{^a9{@9Fn34R0sXxvp(D?<&q=HDwB`Y4>@LcYq(&1SW13dCPAq6#GSc z)dzp|Y9ena@FM^y0vY!hZn<)57IZj7z>NZ3O^5if8)p=q^VbShc$k0)w_N0V6gFKtg7S9DVd!( zxD+iPw2RF$Ut2IDep6kcz|+d|iN@2MwVw zMJ#BTubN~qjvILg(=VQ636}bR5w#{|B3lrEZ7W- zj(=osHx~-rQMVZrvUoMRHT2KW(6#R!*kynIy}k%Sx_!Qxf&$mZcpTUi)^&OTV5&JMrW%C^ztQvOl@k(z$DtQ95qA-8=z*X{pLo-yFRG~$Xaow^_ zfYHGsqlmaV*NOj&*k{0T@+p&6@wN-Jbx$`e4*cV;EKij5S;KG}WK9r~5FI$*6f7!c zIF!W+$KgYlvF>NlveuG4|GZZBySfPY-B`c~F7Tq~cB>eA8 zD08Z+n{E9TFJeEICX>d;32(g1)<}7?eXh>Sk)^Qu?#_aenM!?@YFzmw+zhh0Qcw_o zwZlhy*6;Y8{N2(*o!qlgPTo<(@Y2|)xBkCl7U0*L7l`X#!Orz7!K`KSA&+v7aZ7hM zib6M9P+MSBWeFgX^@z_wrD+buBm7g_`jQ7lm6fFU$)P>$PN~Z{R=AIz5(uRQtp0(D zGJ|n;%O|#~+^kdUUKtFyf)iQD!Gm^gY(pfuycDc{>Ho=eZ}09zq>;czW8l7{u2?N8|?+1|mJ!zYlbe-A5#bngXiyIB5-$r-pIgBtCm>sFlDo-VUYBb)}#7J_b`GDh3Dn|K5p<1 zsVKp)fR$Jd7>b?ja?8@IgyKV-Aur+onV5805ZyNgb7~`#nJ6H}NJrq%N_}+&YFZaffcaZn z_X{S5xA-VOaV6m@$?(A`6Jy*)YSRi1KjR>hl2S-q04K8+W3N!B`*;ulWxi`M#1cD~ z{#-5yNCx&5^~Xf|+^@**Nl16AGcc@WA(TAm5*d=mcVuCZzMyq;gD9+Rd;TwVh95Xm z83OEnUS9L?@llXcn3z0d2{q$Sm(}<4dokcsX}Str_#a^#N;Wpr;~{rP ztha{=E(40;gG;EK!N&s^nvkS0zBR5QGawScD=hn*j|zF6vZxd$iw3xU8bPRkELHGF*DbMPzpfxJu5z2p+A)}^JM8u<3K-B2{96RyI2 z;CTb2XHy-egnST~%8e^Jzr35CJ(>wCc|zpw#p*E(nV$8i0NB6cRLDZTQwkTHTZ6!r z0w#5>1uOE2rG==ZAdHW#E*Shd5&1Aty&QmTk$}%lrT(&F-8NU)c|LFHjdn1dzPO$P zA2#ci>A-4F&(r@HE=LzgqRb&MkPi+{&Bi*)z_fn59K{CH!CGtPKg)0NGT2uG4=Uz= zEZrohgH6%;Fc}_Ja`>#h_wI!q9x>@x!r=GdV!cUPH%7t=1tx#ms$Pa}FwfHjd zjK<}@b#S<{)RtqS$GHvsTr(9r0G{hJ#P{u%bXM(h26H*#LhaSIvDk)p4#n4RBgGBN z)-7XVNVvg34z!~pJJ`~gpihw=@}GgRJLGf{@-#ZQKL34F$DD7eQJ7WM-7m5(o%B9; zdJzLuZrENck}zhVc{9v9K~lSfsi1VXa_%(LE?*1A1jEx&(4KaYCZj(E>v_PM+v)uB zNZr!-thaVBw?vPZEBZ>K#NAL~l;^~_VmmVT>#Pf44=$a!?xb#rkYSbRM)AG}n*+a? zX^C+f@Xux!d;F}XIW#a$O8NoDaHAim2}7P`&ZZJS79lSla7wU=g`ta2iU0ElQS`#*EZTu z(@zyIe-nV%8+O3JmDA$b3G88Nb~oW5J(cbQ{X5ONOvn;m9%RQ=YhzEX_aM$|D)Jw zb0!7XsY386f)()c)mu-e?#QK;aPmB%z2~%{Qs6(|iS9GUhcw}bYv8d(6FQIbkeNXY z9&oczkRToDS>!DpcfJ6=3=8%hy~K+Zox=4d>*QH1GA?~!9spNC^(&Z0>k{vNQ507H zm3BK)+Cfi{K$HP@+BHA*u`XqMT;MyCq)J1Egch1ZWb z7p94s-2-ru0#SZ z__Js(#+xT{_Bpv}9dU_`>7#QLSC2E|4nN zjNv`vfQGu;{(D18A~JUW`n+HM_Gn^)y*heo9ul7`nK1XmlTPd?Dzr_t1kewSRix_; z{Pp*nrKh{0IDlIKF2G(!()odyi*&cQ<}F73^D|Vvldx7%5peF&6GxnuT2Vw95anXK zyyFOq8+J!eaf-&mz2u~y?P^RFhUi9hb&!xNDLPX&*NuYt(z7A91BiTsM;qX>xS+71Q;?F&WnK>{PJ+!r``E zti0g>ZH`yp<45?41=T}HJ7UxY1Uo=J3Ha&vK>0YULH9b&kATqZ4ng7 z>eZoaJQ>siuj6>iK{QtzT)>nw<|lt+kEx?$NSvVooM-5JEg>F0ak2t5j@`~zM5DO0 zlHT-dIx;};CZW{1^r&OSg$hHIS>vQF#S2hA0SHHcX-XCOf1mkM!F=+kL?dVz8@MPxm}ajMgx(uKDCMkpDyEQ?wD?Oanr8N}}1ALcv!K48tPVtLmpg zSIeTNlCHsB?zH$YXegd8Ak%U(z(D_uaMl ziM6SQV`F*vB?3Kqubn5;QLZlb(LikYR#BNu7_lHKtXw?3-g^jdQ3-3(%-JmYbE7QM z>FKmoQ~{$Ju^xyB=W)zdjfrt7-02(0O84g*1}+_yCX{bOGzh&~aMm7p)ywg{WqbT3c~RQ!ow4*(9hQ%C;G+0qAy zu+I^FnjIgX(C$myWd{VY>~uymZfc+eDRXf5#2QmjDmB;G*i7x`Y^f&sgCaYo|(K01{o4c?c zsrOaizD>Nl;(0i)h7f-Abt9JhQj_bAG8{i>VL=9FbU*$^Rhn%0Bn;-$T7t zew0$K&!H9@Yy9?0w>g~D8+)lGoJzQ zamCOxW^9g>ucz_i&@%o9gtxKY<&^O;gnU2}Tugfaz>rm3KU9_XJM z5m)MvDhyySQNYE`G&ERS*|W2c!+B6t+~bASd5}>QRa8nON(1}v$~Z<2t}tspSxeo8 zyYY6^O+%UtEUg{_u>+z0Jf72gYxJj7CSoQZ5> z!bHpa;XVBt5nl>yrV;Aj+3;O$BOw#E2-~sINswLX(9eRKt-d%3>ro2LGIuFiDLUj) za?C0^`1kVAbJ!v4sp+gok>Duw&6k>qpU-u4_1kMp@`Q*eIuP5`Vfl)p9Ia#4@;&Yb z<34uZo)A1Djr*leyi*;NK2V-tU3svyF>9Y{t!u3}jVBa;In{iahoLov*rw{=y4>;+ zZ#CgygvzU1b}l_?3-K)fT3W0iJ#Db-Y2wNqmtO<~bA7QYyHI&oMJP4$>U zTs7tL6giBRGu{r;AfD$@Kg=jXIoP*Oo5SfnX^(vDW{5(-J!4}cLz_xLhB;j@BVEDr z{L!=RYK@k17*UuESDhGlT#?=LNssiM*^l`X4~QtCoR9PojYgDmUH?_ z{Q8u&?jtT`t||mKF^Gga_FOoaFwoY`Pu}D+yqKDB$~KgD^?6@W!Xd5go=N9x2WcEt zD@Fzm{u*}xhpSm^sT~0q2z-Uh00jYISm`g%+p-6p|4=VDUNs7L45?&=W9$D6iPeZW z{;G53_wX9eJRu3^?yN;^K7TT!7xRIQcg=fm%&YAJH-t;|YEQ#vspfu1$A?yvjY-&z zT8d|xJU_IbAk5&eKH1sBDdD4Mjulxb4?69s_0J(gI{mJKj%x?CJ$6OP`qv5={l&3lN>qQcKB?04I;K zw^)}cwA8w$s5Iwi%%K*r0;Y*0s8B3xr?}h=M zN;)6ycd9T=ViJp&GDp&*v0Lbx zH@$v&<`X!!j&l=3Na!z=vThd8q-W%}pcU#`8Zhg@WpV zE)%1T2cE~PH2=I%D0)z>2r`on3c;v*+j}e4wK?B;2?7S^8RE{?8H^J=Om=o&+gvxr zU%jcU;H4rs{Y3IYkvp!{?reH`y7KDH&iNOs%wx|hO&>dVcua1yOAqxw(9SAFW#T*~ zd2!Tsd)67S-=J!RD(q&U#BT8pSb>LN3XjY3=*(sYGjwe zk#OnxNs{+pVaXR0zo%@oikiaNl9tUyThru+WLepj**w3R_cri*dUGXL`VF9yH~)y9 z2?;o6<@}69)$W{1OEb~oH7|RIFhcQ_WtJ$K9JAHLG%6-f5I(<9-T}@~Bx0|W!%60m5Y03EG zr>&`FdVpwb66l$|DP!;gjyU_>GW|>BKnv9Ks=5#3DQ9t10nW)TuzGFwA3t0Fjve2< zDys2`E(hWzCwN$b8t(Z{?BV-SRuHU3C!B#nfGEDCC}Sq~UE5O+*LMMY9 zy427(8_@5>T;cj}`%m)i%~sw0ebBb$mKlztp*k87>RSRr^P%e$~QU4I0g4zq#&UGT|I!CNA( zp}^;0EQ;}=liAzSPHMg*XjOK}KiG{^=339LM1pSiR9-L4d_mqkERR|b1g zWr00(zeq5n=s@4;tjS_QAWkI=`R5N-Q#lem#bxi`Z+AbUcUa*{HHy3c;swa9-XoR& z&lp#fB2SmA?d}7#*!e!AwTK`spu-t=s>HQxGMb?y?mSy`pcnNfn|)~0Wogi;uBg2$ z0G*Lx9%+qd!UKSf=;BvPf}r~MGTof>c`k3*r|l&mM?x!TeuxBQp<-fSD@U_mHqq!P zn1zqO)fIlg5 z>9~LTTrhC^ycl_0UXkSffyCFYeaX45sI-s%l<=oOSN3?DUZ0TRXul{%4)OBZY@ee{h_?j@Ne0!Gp3r$AX4=0O8APA{g^yczW5lzwz> zFh2Ey66>HgVv)nM2mi08tB#7Y`?`t<(w$Pm5Ypn%NJyuE)R590(v5($w6uVLbhorf zgXBm#0@5Po&;tm+JMXtXm;V^7S@Ycc+G0m=@k(d z7jIvm55l>ked3CqHSAV2f7`OFix=U>7Y`QxzWRDNVPEScC2pXt(5`^j$xP;fr@n%c`TGOW zF8Ta?%ma+nmoM=n`XJ#l77c0=bZp(Ck6c`89!$s;ZOqswY#(1w)*KO>(ZSW0QM}jr*TFOOSe96GQY{fo7Z;i;Qt?-&nh96qVt= z&3fwr^s>%oH989vF`ui}x}c!k)vwg;UMWDjH;*cD^fN@YeacV(OJ~vcjh+6gw@15r zh6=xZZ}z`JkMX_AHO0`4$V%0$(ulwd5vTbsP-?98fEc$r5bGQ7pBoKww6JNjt&!p= z2`K?+6VFYx!aZeT-%)dS6bMYd9-d07#6HTYti1M$IujpfD{(%4XW2F7Ix(JHruTY{ zoj6}SmLqnL@u4MuF54x@$@o;8_&YtLAGqg-46W*1p|oQLTrzq@#oBp-8x}W)BBN># z)ZL!N^QDb}SI`6r)+^Z4E}CR$YyuP{pv%uu*AHxmuPR1HW_ot+bwW+6XMKwey#}^B zOoDygl7LfFc8y1g>2?DLvm^0V7ivuFEMM-yj%vyh=Z=YD z8|pxPp|Jq^E2{vLL{K5|fGSVCWaR4QV^dE^AYVLcJihwF>!lVy&SW=M@aHs_jAEC0 z3aU3<_0%hA;{ZG$3ul-#2(*sSVfu0|ngSLL0rjh# zb{n{b|DctT`IVZ0c|NNW(IY7KN8shPfA!YPMOPrhc#8?n!K_8HPLYhF7MNeumwJAU zx#poCt-$<_fZz!TVjv^L+aJo+Zu>T{2=k7QM`m1JKH$Iw*YAk$hmrHnC?_#>2}%|^ zeA1-N-SowhRAoYT(!iaED@-(r9(F5|lcQC;B>0%b4uTrFQZLMdatpd8^FscVBc7?} zE8@V|GDh6&T@s4039n%nl0qZfk1TgR7vtv$F@+dErv)M`B}9 zXFycaaKV}9#5n+(-x1%yA;+g6aApV#8}=dVQJPwcCw}PO+dR22*%-`I8!{R4a^W06 z|G4%%~VWFmqJ(9=Ytyqwi7}@ zOZSzfbq~`6x16p<(6&pv@*$1XW*Q)`-2&+l4NB~(U@4~ey+x(O@e-c6=MQCQ@+(2; zN~WDmmvX1TrwChtqiA#9Y`d!B$e69~_b;Y;jeOmaqi=n2d!C8GC-Y)JtL(9E^O zl1oP4)%nVaoZy?9-Ok_xI(qrlXNn0wBlMPOAX-+wuik_jC#1N(UTci7gFJT}Tbqo! zg->nQq^g$+w^xOSgMo^~)tlgo_{eR_41EJv=oCSCZ+Ew1A$DOIXB-z7`It3;7;6tm zoDo75CDV4zc3xt>)nkI{WU9I&RB@nyPKm(;IxwQDm;#Pi$olII>0c#Vz~H_Rh}*h^ zBoi5WrSDkAsZaOaj-9pbg8%R%m(VF;38d7;&rI%K=N!ky?d4~;7xSHBCmR;Y)N6yO z-J_%3+NVJIWeo%NTaB|8Xm^S74SVMDhgb?-6FZHzivYlS`9k6~&R5RCBBTJY*mT{hyPz)5>H!R!zRj7{Si1ie{<%QevXOHlI zUwQGh{trq9xX63LJIir`m-vC#64L{~KZY*dU|zE*N~iC*1S^H1_x`Y3h*ZI*wGO=B z%96s(NGsX}mgaxI587tHiR?m(R9NCKVd$&1I^!d}2Wm6h_m%n@YN}UBdi2Djya6A&Qt zSQLLxZhpu(3+?B}=SB?n2j$jynL+zLF7FWAx3+cGNw=3`KTH1GSrX}7bojTsEZuAT zs^KNUKJB)}g3u*l{*!&${mawVuXrlbBZwbN z{jf0I$$5Oa7j0P7n;3j^d_q7(W(PGWpS>9Va%biIr^hF{WpR{B#4Qrfj0s@Nl~G6F zfb$V?Vo7f|0rK%?&ZE8G1HSfEWt@U0J}8o)M!j_dW|?nD)_u?wR#feZ zMbXpN&hOFz3-#)15=SA_$#fC^fQ2z{ni4j~Fu1g72b#DY_JzFUAGurmC@@julg22} z>>RSAh)*Q%0JNJZWb3K1n0xK5?KWfLqlYKzh10xJe(?!Q!o{u|RL4&GaR(GwyeVlUJ+1fB?lfqu^@CDj>09r@bJfQvHbdeJ{I>RpSy_IK#K7iSq~tbN16 z&d1%$LE|K6zzEnWlm2U?+sR{CHZ-+qH!Yo>ABxemL*Q}O+JgsroxV!^x0^59xv^&V zz}t>iung|b zzkFVkwG6st1AwKFL&vQl=ZRZe7bDYJ0^(D?MW(jh2(Py48#u}j6hFT!@D5I)3Box5 zLPR|p`VnPjZ1AfVAD^Ebw|4__%&wuykw(qPaQ0z684~k*YrgCxDV=|;;hO=!5^w6N zE6_cK1bL-xrzl7A#JM;7d&yRSjp56N&Js&fXG8UZ1E3saM;jWg2hiQ8=xJ^loc8@- znXp_r9|U`;E15cZ3@7YBt&D6hQpF!JC=!jR0#fsOD2?K#x4*yT!WQp7P{8JSR23m- zz!Wh$uqoeb5AWt`fVr!eiBHU5*pwCoD^qQ4qlR9-M687va3&{NOK$wm@;jWCEsLbv66X>ne`GGT|MWplbQ;^hJ*azGgRPOh+prVWB|N9%p4sR{4 zH2%4{n!*2`J>l6o*;Dn)MwmZ-xE+(UdOFO&jGaCaT%Mmr#8o+nLAJn=j=;{3;m53F z61x}ml!Ebf9PMjep?6-ek=B?K^2Jw}zX~^Eq24fHUUgqTow3VdLzi_|;W7zqq-qMx zVD=?_yrHQ@p}jPUVF*u?7*oG^v@aSGyLY&6T|8@%2weW`fwcGXeyCHaY&-l>8OOt8 z*7O1FLM=k)gFXhFs4V;_5^lT#xOuDd)6bjj!NuCE(%f6^bEfCkrdqz`+OG%9%4ZjP zWYOi~vh){<5U>pn2?b-b6+pvwv6Zx}0R@2{bF%AdmGYx3!PVQ)_1Ak52Q%<;o%^iao6Dx?^RV#zDn^|!}{T0JpNuN2@ybd`L~|9 zfwHvG5>w{07$kLRuZGG&Fg6}5%|G{f+c)6eh%kU`FK`}f$q-`#d+rx|%i~m~r90}S z%A%2)4EHf*UY&or1IS@YJ^gw$GOd)d$w^DyO2EL;0V@w_rZ~Is}T|%PA-mhNz zolhysf2z}6cW;x5cEz%5TzAbc0U(jS*y>1C+j+&S2>gaf2jXj|hc@n$;6xZqT7ff; zhMo@cyZ+;mxnerxOihhng8$9u#VZ}b_)@Qb4%7V_po||*k!zf7cZMtGUq#ta4>xp< z-=bHsRZS?(V*bmW)Hb?em56SqIo+14GUhP>2dIi;VYOd+Rb`qNuU7X>UV zj;h*~Lz|1ASSgT4Us}Ah6DMnzwri526|#&BEgS+r7(g#Ic{4k7t(2dqTdNV>lh5A= z-7M6kR2%$U$;D4*5Dx1mq82o^CJRu!p91`zP=83V zZiv8G^vurly{@PI+ChUU^HDg}+}kfpcy2CJmmI*fJ0@G5H-74t^13F-v0Ost&u7KR z8H0JRIf=ab9gV0OYZCXL!hG@B=5<@s3E-mEHDT`#x9zgdCYSgD0PaVC9zihh#n)K- z-I#)_ayaal-+@`Bo{8SA@P*$0YXR7H8E6k%5Y#REu~dTbwfdEI)s(;x*kvVte!GQV zJ=N^yE7e839ERl>@mxiP05NV``)AdP(Xzg0&DPn!TlQ7R9kUh8nBHt$ybb_b0kUyf zmL7YlRjAd;%x?qACH0)>YI1128q+TUD`Rb4|4>=b%KuD#;qG*MGt|J|@=^`H0iqY8^-0+k2Jag+HBuk-@_4dQRzk~B%M#im8FaQu0>RR(O zo_62y@4<+Tar#yJFpetPS1Vg0+Rd48!*;{RRQqh!&jt4i9zDQNjv8#J5LgpAGekz( zTOfp0MA^6CJH>mDmLn^>4QjV}_PNkbx2!c5wuks%f~4YNaM--XTrh z2MtMihu-$tY*F-j+vwZvQJQ;~RVNgzbVX#ZRusk6*evHHB(;XIHDfTg0u&c{o>i|e z{Hq63W8UsYp%iy_1ATah>bM*AIGFG-I@sBwMfaM5gpwdJy~YX3FdLb9KjvJUVWhxW zJ0AgjL#}ZIo8BgZJzL#B9MC^gszuJ(or`vV&7)we<$7^V+#OX5{~;+k&DmVRElX@{ z%4!GFvdzxj2j)e=HA`%eST)f+dUPRzf~yQr^i1tA;a~*N(S~$(PeeV*wFGx6O^V`<)dBO6xD?=J!LI>wd-FF0^@^r4;TdtygX~{KGpwuDN*eLCST~ zTWtQjrta*`_+-$;jZZ%6U6(e;Lb`QZ!YS&hsoyToRc-f4%0HFjwM+5ZD}ni)g^=TG zKmBW}?d|>A_haHS272UIwLMogTsO5;lzV(NwTDVYA1ls6Le8qVZn#uc&loA=#AnJJ zvj2sbzUWx%>F#t2snJ|WRv6>oJCyPorMRu0)xGkWpEE`#=8Kp+mbCGdWR?5_V=X2_ zHwQnm{~1+Q)?A$Di&+C$=!~lSOt1qq&+%9PobwSjWwnXdmd5LR%A}K0C>rt%&oE$CAP_<=OzDaPctt3s7}ufWhO83YZ0~ z6R0Hoi64qtR*M9<9V&GZmVGmZ%zhg#vn(Z~P^r_tqK<6CQAr!sr8?k3!__SSuHF!! zk<+pQ;KJ9-OUpI`ZaQ3&ajUY}HOpKj*S8Smz1>4asCvcC=m|FISPRhBY^bsV+ypeS z9q>o19G=L^y1;`6>wSIHB_qNs!4L0TJuylPzHgMSOhnkA%~3aBe_B47|KvTWSep7( zBo7~QOTJl1moGN8^?seKB=Sz1Ve;fJ|2NBb*ANi$Jd8My>28fOF|Z9#@7DkJ*$amK;!)^ zN`*>nX=}f4{wbxr)#5Dh?l|v&oiiXxw9Avcs)YZ(e!4IpRbdYZ2T#Uno+L&#&_rsI za~6|kY;6r^sJso60nh`;!Uc3fO~^WtIqIjDoYn@A{suNpzX~=F4eg6M^hrGs#c&BA zU=%LoXt1>ibIlb61GKMJYD~b2CknRLyzKTRDWCqeSfr@@pQ;87&1Ut^=6Jt4~j z4A4OEB_zVjbb!h`af!MWGWI)|=8%9yI6ut*^1NH8P%5KiMcv;C_+X+otw|$gT-o~i zhaV_MRfbcc+kTVPk@Cj?0CDj{1q*L3Ge3Cy)%^X?>x%2J^5K?5 zi(UJK<6&nXnWbgmN!xa`Xq8$`#L7J+61Z6dhNUYD|1N5aG1ZQvA2hbEe7r}Ssu}!3 zRE*;sxKdKWt@{gjxrAOOrCS#cQcDiVS}H0NNj+0qcrGEUN$LJV&bn3O2;e-KcSNxH z;(=xi5KcUfM%TVYy|vst$=;dV{`TCS9pBzQ?}R?v2Y%0Yw8+6DQG1u*UZ7*q3pJT< za-gPjg&HIE9R+!t5S^D25G5UP_a5m-JIQ>yEMFj4! zq*>$$1GGuUnyWCa93&<#;Z)X?w%ysIS9V>lF$VJBo0xG?sW|-&>iT+6uc?}{f8C&E z+_Q3E(ya4Am~_pOji*~uedyhk`h^6OVl>I%4Co^p>JJ^BIO_9gSr<1gE7O>;vu;fp zXU|D7A2MW7XlsjKZwzXJBh%{4jbPhAD<0UA$DN_AYsH}T-7=X06nc^+-|t;}@Kz}D znr)p+o<7D%{AIf1b59q3Aslj9dYzvg`HMrk>Q7L$pXuvja>i?3cSC_|ifT&!6~H zL7`*MiP36MAg^20Q^B}D4c2o>qI>s1dQhP~CdGZsP+Kwj zoBYEILbiYaliT@I^b3XH=ZVvw$SZD!g(7E!yL)TupAhaZ?N;3FA-N0NLdD<&cWVCn zd-LxOnwFM&_N0FQGub#BAa`2DRm^Vx9SKN@@c(K^KbsL*asa##0Gm5_ZqQ)+K(}Ou zd`HG$n>Ub8tai{_B754}M-@FpFX(K>uVs;OVGJN__vE z*atfu!`hxJ!!NEc#r_Y-AKKkjd)p_mN?m#0&Zld#4M+|tA&E|&8}?0s@LncGdctp} zv|uwx0j458IoTP>r_X7HaX0TrsKO8wh0XzU_w+Qn1}cF}2+~QwPnKY|;bL~v`sQF^ zq2)r@LC{L(m*3Eb^&Zf!vPVYxUVVSu4}{YE!=JPROLW%$rOPX7T}*u^7gfCSK?7PX zmI$@lDq<~NV7&v%?Go!`<>CcFG5?oc#IO_*5vW|N;pU=dv;lO~c5d#XH#F3Fh!q0Z zgL#UOa^tOltIOX$ou5MxvMeuoJqnh*2h3{pP;#z(ED0Oi#Yp$!=i2EeNKgg_cD80H zN2#}D3|2Oyf%SjoR>-=jTlTJAi|Wk?+|b!k51-+n~lt4arlP-tmsPXL~<1(ORI% zkSC9x=F#g^#^_%!3a>|VwzSN@^XD5jlpdp02ePg}&Dak&WYgyKlZ;4GblQE)MJ&ytiGdeN~H5?_`|F+1s zC=ZWeEVQ%pI0{VZqr2{2I z>jH%5(%t6(2Cd!7KZ5nJ&mw@04FH(F=NroH75-yp5=3M`eg{83HYkbl#VejlWC1|m zCAGxE^RPxZ+_T+8@+R+`eqzLt~1_t92>t+dXY}xVBE&<@aCV-IKfxOuSadAz>P7u z>TGnl1imr0dA0N|Dl$tu=7X!8pAk^ePE3CL=s>GTM90+Kr@PsTem4#tuiv}3p;|mN z_=g4PZGEcT&)S62^c;)c^lhi}tEIMWT04xh_1(L7QUdq!dQ$n{j{xkvYU3E{^7{FW zISZKPOH|%m%$0_p8s$x$E|ztQ1>Hzs{QXY$g2}JSq9+x7J?4=TMu>E}GH&yk4f^8K zIfCpFBhHXuIuguKPzH%ye+%^1&`ei8JxNkwQyyIRc}8>fG(htFJy`MrQ2X$iT{Cly zvspmCf?HS+Kb@}6&>3!}t2<4%6&VZ4Ngn$jUVn0VojL(YgDYj&tHZOOcy1_geQdYfm32_Re8R&_3~#f5fJI0y=|6ZqNYou2kKZ zf5ffKJBIEI8bshO$Dd>^%Mq+h5Aa2{LOP*ph%KGQk(Y!kRcS6%GQ^pNhHJKo^y_~Q zmVP>4Gel25QzVyB&h_Ss-hSb1+&-ZH!#1-D zX}ML(sUd-f6{hluVtnn8oEal<9%(55KD(OXD9P9oze&+ESZ}<(VAR!$fO=oNgUyS3 zm+pTa^l-Ra=xk{~x%SL|0?jp=MPp$Eh6s(Vu9{CoGgnrOnt5e@`3Eq(kPwTo50<~T z6(@4n^?~}U_PfJ3PqDz!`ru7l8=DJ+r6s;tH(?jlw02$;?tok;H2avJo4W}pU>dJL zdLYYKvioaO)ce8-Hm1pyQC?F8hImto=p_-NLXs1upf^kix~gGASnF;|aq}G~(g(QT z{usrLT6?{`eeoPzX@2VKW<0z{lXPaAJ#8(6HA-)ZO0T7K0E6_xbXfuiIG_f3X8zinQ8-iaAlNYyB!z}*A69ORfzOsk(u z?(9gvxq0QLcrH(gxd!B6wpLl)^bT^65hE7jg?a_``53?c#?U*D6Ky9|i@Ms_V^mqF zAsi3hm1;MM(;wUq4*9X&O^lBTjy@5orye~y?(-zV!pHU3nC`N`5jrSmGNPg)vt%bA zW(h4DGAvr9U2A`cJ`cKqvTEcfYg`024lG?ujckeU#S#h2zkiITk1V-&70H|bY!G10b+4p`2i9OkL z2R__B|89@apCp@Z{k)e6oWGzBuK`;++rIFMua}-IUJjTyT7nYON}TFAkQ~n?Tn#?gt#k9ZtnS zw&8a3`cUgWS4it$pnp4f7{)ZUf*7mh;=&jEv%<)#qh7?91v%j8IBAmv69acF!OZ|N zYT0W$F`V$Hf$Rqg!Et$=+89OA2Q8o7Et};8-w8mU-lgFsyH(`4)a@G3oK4xuXFf&& z7klJr)J*@f0X7FFiKksGv$srqh`JcTu!1}CGeFLgZ#=VOJhQX1Xq<#dm3VVY(o`Oa zQ07Zk09ry(%dQ1M&Pjn0kHv+OD0WCwsEjDFpR$`Gi%$Wy)YB(2z(KJF-Kf=q6|@tg z?F=}e8~x3JG}sFk-brZG<4(bih`y+ta*cTddF^}d64?j?PKOQ7_~4ov8;ed<_DZ+U z286lC&5^Yn%*L#B?B|gH*fQXX1(KV3-w!}QF2=y_O(mTCBNqLXxmI5#5!ZD#e7%)= zD*LhL_Rq@rW84r;&gu2vX;lui`Qre<0HQ1)t>%(M?ED2dj1`^H=zo$dAZn_)@GR}y zo}b_Gc=s4cvi3YhR$N@;YHO;I&t3VBSQa|kset~@l~Z1Tc7R`KD8c?)(m%gdU62|S_QFGBsO8~$;&QsG_ zsAOxWT9tNhl^)K03`nYSo+?{R95W~p>XqIC&Ym+1;-47M*8Q+{GH9U1`R3;EhPQTn zqT&76w1nHQKP$}y;boAvGpHRc7LMZcDIPp{3y!$yj58d<92+e@#m|kmPG|>^Gpo44 zsUv~gn}sK)zH2eNCb;>#uMk2;X1XoT$0*mJxcuz=QUQ_A`v$dFKy(*tPwLU%Q39GO zOuDUbQy>9=Bd0Xg$&hT&A0^5^%gtdAe!1tl&|(F}e(lDiy5Pbl{7aP=l`?sU|3pxVcO;ZXeb{N&U*uE(@884qi5o%#rg zrdfcb2^B|(R<)%xdTbZFS4@DK7dfm*V=LtETSTGk3%S2iK3o$g^dR5*Zl}NWb zGNKH6gjH<2F*Eer;*-a~xE)jQC_N)X%PSY`!l1rB;{(r&4ogekrvvkjf|Acf+j!23 z-LrtsD`W^M0Ne9MA&QE4pUU&aXK+z0y9lAohK5=Pjw?n%@I?C=Epi4etx9u93#nrb z#xZzIUgwNhsW~MiC5l4jsYOa{f5ry1O);XlVz=gkkB_%Om~y>?PWKJEumPbiCP*_A zQyU2R8qk*se+OAPjj?O7w<}y+=r+Lhspw)$ynS|OTf^5O)|eL(SKP3nsPXVO==Vh& zKmx!FhX^YxFW>gHGl6mn0}WZ1CV#X&jJiZw`YgoVC)S}O{^WttDv(e3gC=)?C^+fj zSvGkO?7DPuyPNsJ*V&^vHzw(>#ZQ{$ng(WyFLiN8Afa+$d2(w7JV=83;ReANZR>WR z;>|B{>!|S8k4gdZTA+jITjdAoWkgPQBYQ}fwnJqIF*9iyx1U837I=0vO?+4dW~|29 zIkH|lBNA$W>Eafm3`PAE4eR|C6u|2cKWH)wW{FCuC@+?`^Fu!R)Cam5Z5=NF6@9W> zCYb)&7=EzQW=k|;_ui&s9I!)uhWvJ!Yzt@4YLooF(~NH>Ipui3;phG5I6_-&l=^y^ zFoRdGy-q7;JMCfb9R=sOD8SIlxc9@C@F~|Py%bN_W1Qp)8xGQ$i`>8%X6|HXXBj1i7;NC49Q}Yzq7Em%zZ!az5=fo3cW7&G9-6v6@kCPM z-G8ZbfA~R2X2*`j^AO+-c;DkTvUxzSa@Im^Z zKu9rsb3H_FJK2v}V*RA6?#pUdz~Q9?tQ1ka?0{S@QiP3c!;E1Cx|~|vY0*v-URmi8 z&WKOPe%*x?;PU$rWbPsM1>-s7lf7*QP4$hCib*w{lk! zNdRWypoWm+1FcQEb-f`sm`)q9NlgpPQ{WzaV-D(f0Or-kwYB+Q_8xLQ3j{hK?Fzk- z3>ARWRDViLDAX*UO=86nC6?~ic%cP{f-U0U^iH8sIXH;ELidvfwm3DW%ol!jsL3|i zY+fFYiwT8qm_Ggx3I$@fX2JI^09*xz034@c)tb@rV}*+@;8te9HDUxWti)fy80AU$ zCRwzp>FfF>GSz*UeCaAgLqq**z&5XKMZJ?Z-5ID(o-k-N4qvHrleSf(if5mPlEuY= zCuyZ!zI&+zYf+b=?GpQT`9u70%9ffg=tx-sZtwJ2tpA6gy4;vWwN9aN`Kv{tFb>k3 z{B5H7xjLtuWhY84V&WWiS^%1EZGn-yYZR5~=gs4`40Np%%1(|C#9wmK>EIB_b4ssc zB92pd!V}kQK|;sn%LrjEAODTM2l%JNfx>axzX2XB{4YaH_!$L?-FHa5xqP#|yvLb`_biSJ+TC?_(6 zn`eWI3<9>=HZi8@cQ1$u;0A$qT22eV0_NFlVdhEE0}L0YrxfUYrkSK#TAP4lRzA=! zAS}?)gb7qyJ%dYcz?fB7MZ?J`mlYT#B*g)Zs0UAFo8_ zsEh3jjjWo3>IRe|*)=Z_fpb4%zqV@Rm+0`4DVN&w@R0%G)MW@g@Wg#svv@B>F1g?) zII?9SpYCT@4j7=ri4e-WCv!(JqF}yFA{zbp3cggXsgPrWAxVR(*^AsAd(O~19W-f%A~RSmdybI%_*qZEN8=dl;Mn5 zw*jwfoR=EeY(q=e_1)MC6*#}KOQk7otbL&Y1=Kv)rO8z361p#s)uh{2Q$-d4g+A@*`mhE%s(DEs;Fu(W&M|4YcL7x{YA)qG6NvgPayJ9T8Od0J< zih~?S!2s`H4+;t#+JKBjU`$KDc`ouTE(TKp@)b@-24+z6YIT5Puv(Mh+vks-gUO6l z^AapJS%fDmP|GO27(ker8T#pY4V#ii=g7W1yqX zV#=NJ-dZ3Avna>2RNr#J#q0NoZZ5o&)}A?<;l5l?xI3g+MH4RACCZ_}Qk)Jj1)N zL5EgYe(t6x(-#0WTK4tfpPicHVYt#sCAd(7fm3_f_di~56~Y0ZCBOiVU6(6Ep!!_m zuM^Uz1WvyS3W_u%HZ*&O=Vlr{p?T_v5I&$>`0||`Y4_f{wC|?8Jm6>h?cDOx$tMzd zbGqAg{P2n4vEexW*fN946wdnD>_KNLlk;`&?TY$Wg=}+BO=nge)&%}&{(F(Up9U*( zN?N9oHJNBn*&lN0w+EL#bn_on9iF)0`Mf<=d9#7{{6*y^|yb<9A`nU*M+ z1Hrqm!L!>SldYn)xUwB2G-eEqEvc}p7?lo@ViXdJRwxR5Ch{@R^DPKiPqUs$ANX-8 z=FdM2rqk;k%e|ZZbNK=SKFm3Y zm(m|7Hm=0&4Gh3lNu$zmZJJez79ktwjS|rQ23;bbH43(cAR*11oCB|nwkeR3cg5- zjV~YY`oU~C)0nfAK4ORWyKY$Hg*}YMVE!C7wB9DT2vH_omA><$Rkd;Ut9`4er08nr zqLnBuYnBp!Sp|4l+GdRYOmEH;-tD_Fed+(rVG2_q!ooQ>73=Zj`bFlF1v38wIlfl`EVvwV^dOk n_%E=hWHkiyk#v}wSun-`)#!p=)7INN;Fq$zhFs0_S7HAH6TtB5 From 26fbe999fce644de081299828d4a990ef0a98a17 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 8 Apr 2024 16:55:14 +0200 Subject: [PATCH 2/5] Update docs and fix integration tests --- docs/src/DEVELOPERS.md | 2 +- docs/src/README.md | 5 ++--- docs/src/qsa-api/README.md | 2 +- docs/src/qsa-api/endpoints.md | 18 +++++++++--------- qsa-api/qsa_api/__init__.py | 3 +++ qsa-api/tests/api.py | 2 +- sandbox/data.gpkg | Bin 225280 -> 225280 bytes 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/src/DEVELOPERS.md b/docs/src/DEVELOPERS.md index bba7e90..6eaa2fb 100644 --- a/docs/src/DEVELOPERS.md +++ b/docs/src/DEVELOPERS.md @@ -19,5 +19,5 @@ Integration tests: $ cd sandbox $ docker-compose up -d $ cd ../qsa-api -$ QSA_GPKG="/data.gpkg" QSA_HOST=127.0.01 QSA_PORT=5000 pytest -sv tests/api.py +$ QSA_GEOTIFF="/landsat_4326.tif" QSA_GPKG="/data.gpkg" QSA_HOST=127.0.01 QSA_PORT=5000 pytest -sv tests/api.py ```` diff --git a/docs/src/README.md b/docs/src/README.md index ece7715..4774c8c 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -17,15 +17,14 @@ Components: Features: * Create and manage QGIS projects stored on filesystem -* Create and update vector layers : symbology, theme, ... +* Create and update layers : symbology, theme, ... * Inspect online QGIS Server instances -* Cache management with MapProxy +* Optional cache management with MapProxy ![QSA](images/qsa_archi.png) Roadmap: * Add more documentation -* Add raster layer support * Add PostgreSQL support to store QGIS projects * Publish `qsa-cli` on PyPI * Publish a `qsa-api` Docker image on DockerHub diff --git a/docs/src/qsa-api/README.md b/docs/src/qsa-api/README.md index 6708fa9..22ef48a 100644 --- a/docs/src/qsa-api/README.md +++ b/docs/src/qsa-api/README.md @@ -6,7 +6,7 @@ providing a REST API for managing QGIS Server. Features: - Create and manage projects stored on filesystem -- Add and update vector layers based on multiple datasources (AWS S3 buckets, ...) +- Add and update layers based on multiple datasources (AWS S3 buckets, ...) - Configure symbology and themes based on simple symbols parameters Optional features: diff --git a/docs/src/qsa-api/endpoints.md b/docs/src/qsa-api/endpoints.md index a265abe..1701616 100644 --- a/docs/src/qsa-api/endpoints.md +++ b/docs/src/qsa-api/endpoints.md @@ -38,15 +38,15 @@ Several themes can be associated with a layer like in QGIS Desktop, but only the current one is used when the `STYLE` parameter in OGC web services is empty. -| Method | URL | Description | -|---------|--------------------------------------------------|-------------------------------------------------------------------------------------| -| GET | `/api/projects/{project}/layers` | List layers in project | -| GET | `/api/projects/{project}/layers/{layer}` | List layer's metadata | -| GET | `/api/projects/{project}/layers/{layer}/map` | WMS `GetMap` result with default parameters | -| GET | `/api/projects/{project}/layers/{layer}/map/url` | WMS `GetMap` URL with default parameters | -| POST | `/api/projects/{project}/layers` | Add layer to project with `type`, `name`, `datasource` and `crs` | -| POST | `/api/projects/{project}/layers/{layer}/style` | Add/Update layer's style with `name` (style name) and `current` (`True` or `False`) | -| DELETE | `/api/projects/{project}/layers/{layer}` | Remove layer from project | +| Method | URL | Description | +|---------|--------------------------------------------------|------------------------------------------------------------------------------------------| +| GET | `/api/projects/{project}/layers` | List layers in project | +| GET | `/api/projects/{project}/layers/{layer}` | List layer's metadata | +| GET | `/api/projects/{project}/layers/{layer}/map` | WMS `GetMap` result with default parameters | +| GET | `/api/projects/{project}/layers/{layer}/map/url` | WMS `GetMap` URL with default parameters | +| POST | `/api/projects/{project}/layers` | Add layer to project with `type` (`vector` or `raster`), `name`, `datasource` and `crs` | +| POST | `/api/projects/{project}/layers/{layer}/style` | Add/Update layer's style with `name` (style name) and `current` (`True` or `False`) | +| DELETE | `/api/projects/{project}/layers/{layer}` | Remove layer from project | Examples: diff --git a/qsa-api/qsa_api/__init__.py b/qsa-api/qsa_api/__init__.py index 62c279d..9775029 100644 --- a/qsa-api/qsa_api/__init__.py +++ b/qsa-api/qsa_api/__init__.py @@ -1,9 +1,12 @@ # coding: utf8 +import os from qgis.core import QgsApplication # avoid "Application path not initialized" message +os.environ["QT_QPA_PLATFORM"] = "offscreen" + QgsApplication.setPrefixPath('/usr', True) qgs = QgsApplication([], False) qgs.initQgis() diff --git a/qsa-api/tests/api.py b/qsa-api/tests/api.py index 218c1a8..9d06eb8 100644 --- a/qsa-api/tests/api.py +++ b/qsa-api/tests/api.py @@ -21,7 +21,7 @@ GEOTIFF = Path(__file__).parent / "landsat_4326.tif" if "QSA_GEOTIFF" in os.environ: - GEOTIFF = os.environ["QSA_GPKG"] + GEOTIFF = os.environ["QSA_GEOTIFF"] TEST_PROJECT_0 = "qsa_test_project0" TEST_PROJECT_1 = "qsa_test_project1" diff --git a/sandbox/data.gpkg b/sandbox/data.gpkg index 06ee70c09ec021077106dcef032a6763897f229f..176f041146fcc6a8fe15b971e197f7b68392bee2 100644 GIT binary patch delta 30 kcmZp8z}xVEcY+iX Date: Mon, 8 Apr 2024 16:59:11 +0200 Subject: [PATCH 3/5] Remove aux.xml file --- .gitignore | 1 + qsa-api/tests/landsat_4326.tif.aux.xml | 29 -------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 qsa-api/tests/landsat_4326.tif.aux.xml diff --git a/.gitignore b/.gitignore index 55a9012..a0f30b4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ tests/data/data.gpkg-wal examples docs/build sandbox/projects +*.aux.xml diff --git a/qsa-api/tests/landsat_4326.tif.aux.xml b/qsa-api/tests/landsat_4326.tif.aux.xml deleted file mode 100644 index a1ca794..0000000 --- a/qsa-api/tests/landsat_4326.tif.aux.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - 127 - 125.33333333333 - 124 - 0.8498365855988 - 100 - - - - - 141 - 139.33333333333 - 137 - 1.3123346456686 - 100 - - - - - 124 - 117.83333333333 - 109 - 5.0634858436544 - 100 - - - From 1e24908cadf65769daf295932a82074fa2994727 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 8 Apr 2024 20:41:53 +0200 Subject: [PATCH 4/5] Fix schema --- qsa-api/qsa_api/api/projects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsa-api/qsa_api/api/projects.py b/qsa-api/qsa_api/api/projects.py index 87f83bf..2a99320 100644 --- a/qsa-api/qsa_api/api/projects.py +++ b/qsa-api/qsa_api/api/projects.py @@ -215,8 +215,8 @@ def project_update_default_style(name): "type": "object", "required": ["geometry", "style"], "properties": { - "name": {"type": "string"}, - "author": {"type": "string"}, + "geometry": {"type": "string"}, + "style": {"type": "string"}, }, } From a4167b004585d6f982437c4a6b9614eee8a3e524 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 8 Apr 2024 20:48:47 +0200 Subject: [PATCH 5/5] Fix layer info with raster layer --- docs/src/sandbox/layers.md | 6 ++++-- qsa-api/qsa_api/project.py | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/src/sandbox/layers.md b/docs/src/sandbox/layers.md index 27e0b79..8916d42 100644 --- a/docs/src/sandbox/layers.md +++ b/docs/src/sandbox/layers.md @@ -14,7 +14,8 @@ $ curl "http://localhost:5000/api/projects/my_project/layers" \ -d '{ "crs": 4326, "datasource":"/data.gpkg|layername=polygons", - "name":"polygons" + "name":"polygons", + "type":"vector" }' true ```` @@ -28,7 +29,8 @@ $ curl "http://localhost:5000/api/projects/my_project/layers" \ -d '{ "crs": 4326, "datasource":"/data.gpkg|layername=lines", - "name":"lines" + "name":"lines", + "type":"vector" }' true ```` diff --git a/qsa-api/qsa_api/project.py b/qsa-api/qsa_api/project.py index 5d2ebc8..c48dd9e 100644 --- a/qsa-api/qsa_api/project.py +++ b/qsa-api/qsa_api/project.py @@ -161,7 +161,10 @@ def layer(self, name: str) -> dict: infos["valid"] = layer.isValid() infos["name"] = layer.name() infos["type"] = layer.type().name.lower() - infos["geometry"] = QgsWkbTypes.displayString(layer.wkbType()) + + if layer.type() == Qgis.LayerType.Vector: + infos["geometry"] = QgsWkbTypes.displayString(layer.wkbType()) + infos["source"] = layer.source() infos["crs"] = layer.crs().authid() infos["current_style"] = layer.styleManager().currentStyle()