From 10dbd5d68d8129c91f8f43a556b74b60c969de4e Mon Sep 17 00:00:00 2001 From: Nathan Olson Date: Tue, 20 Jan 2026 00:57:48 -0800 Subject: [PATCH] core, android: add support for camera roll (#3011) --- bin/render.cpp | 16 +- include/mbgl/map/camera.hpp | 31 +++- include/mbgl/util/constants.hpp | 8 +- .../auto-rotation-alignment-map/expected.png | Bin 0 -> 368 bytes .../auto-rotation-alignment-map/style.json | 37 +++++ .../expected.png | Bin 0 -> 223 bytes .../style.json | 36 +++++ .../expected.png | Bin 0 -> 353 bytes .../style.json | 38 +++++ .../expected.png | Bin 0 -> 406 bytes .../style.json | 38 +++++ .../expected.png | Bin 0 -> 19328 bytes .../style.json | 58 +++++++ .../line-placement-true-rolled/expected.png | Bin 0 -> 24292 bytes .../line-placement-true-rolled/style.json | 56 +++++++ .../line-half-roll/expected.png | Bin 0 -> 95859 bytes .../line-half-roll/style.json | 65 ++++++++ .../auto-symbol-placement-line/expected.png | Bin 0 -> 973 bytes .../auto-symbol-placement-line/style.json | 47 ++++++ .../auto-symbol-placement-point/expected.png | Bin 0 -> 377 bytes .../auto-symbol-placement-point/style.json | 39 +++++ .../map-symbol-placement-line/expected.png | Bin 0 -> 973 bytes .../map-symbol-placement-line/style.json | 47 ++++++ .../map-symbol-placement-point/expected.png | Bin 0 -> 401 bytes .../map-symbol-placement-point/style.json | 39 +++++ .../expected.png | Bin 0 -> 874 bytes .../viewport-symbol-placement-line/style.json | 47 ++++++ .../expected.png | Bin 0 -> 377 bytes .../style.json | 39 +++++ .../src/cpp/map/camera_position.cpp | 35 ++++- .../maplibre/android/camera/CameraPosition.kt | 111 +++++++++++++- .../android/camera/CameraUpdateFactory.kt | 33 ++-- .../android/constants/MapLibreConstants.java | 5 + .../src/main/res/values/attrs.xml | 3 + .../android/camera/CameraPositionTest.kt | 11 +- .../android/testapp/camera/CameraTest.java | 2 +- platform/glfw/main.cpp | 25 ++- platform/glfw/settings_json.cpp | 14 ++ platform/glfw/settings_json.hpp | 6 + render-test/metadata.hpp | 1 + render-test/parser.cpp | 25 +++ render-test/parser.hpp | 1 + render-test/runner.cpp | 6 +- src/mbgl/layout/symbol_projection.cpp | 44 +++++- src/mbgl/map/transform.cpp | 74 ++++++--- src/mbgl/map/transform.hpp | 2 + src/mbgl/map/transform_state.cpp | 116 +++++++++++--- src/mbgl/map/transform_state.hpp | 39 ++++- src/mbgl/style/parser.cpp | 14 ++ src/mbgl/style/parser.hpp | 2 + src/mbgl/style/style_impl.cpp | 2 + src/mbgl/util/camera.cpp | 84 +++++------ src/mbgl/util/camera.hpp | 7 +- test/map/transform.test.cpp | 142 +++++++++++------- test/style/style.test.cpp | 8 + test/util/camera.test.cpp | 14 +- test/util/tile_cover.test.cpp | 15 +- 57 files changed, 1275 insertions(+), 207 deletions(-) create mode 100644 metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/expected.png create mode 100644 metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/style.json create mode 100644 metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/expected.png create mode 100644 metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/style.json create mode 100644 metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/expected.png create mode 100644 metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/style.json create mode 100644 metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/expected.png create mode 100644 metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/style.json create mode 100644 metrics/integration/render-tests/text-keep-upright/line-placement-true-roll-pitch-bearing/expected.png create mode 100644 metrics/integration/render-tests/text-keep-upright/line-placement-true-roll-pitch-bearing/style.json create mode 100644 metrics/integration/render-tests/text-keep-upright/line-placement-true-rolled/expected.png create mode 100644 metrics/integration/render-tests/text-keep-upright/line-placement-true-rolled/style.json create mode 100644 metrics/integration/render-tests/text-pitch-scaling/line-half-roll/expected.png create mode 100644 metrics/integration/render-tests/text-pitch-scaling/line-half-roll/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-line/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-line/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-line/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-line/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-point/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-point/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/style.json create mode 100644 metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/expected.png create mode 100644 metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/style.json diff --git a/bin/render.cpp b/bin/render.cpp index 396742cff..0af2a340a 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -39,9 +39,11 @@ int main(int argc, char* argv[]) { args::ValueFlag zoomValue(centerGroup, "number", "Zoom level", {'z', "zoom"}); args::ValueFlag lonValue(centerGroup, "degrees", "Longitude", {'x', "lon"}); args::ValueFlag latValue(centerGroup, "degrees", "Latitude", {'y', "lat"}); - + args::ValueFlag altValue(argumentParser, "degrees", "Altitude", {'A', "alt"}); + args::ValueFlag fovValue(argumentParser, "degrees", "FOV", {'f', "fov"}); args::ValueFlag bearingValue(argumentParser, "degrees", "Bearing", {'b', "bearing"}); args::ValueFlag pitchValue(argumentParser, "degrees", "Pitch", {'p', "pitch"}); + args::ValueFlag rollValue(argumentParser, "degrees", "Roll", {'R', "roll"}); args::ValueFlag widthValue(argumentParser, "pixels", "Image width", {'w', "width"}); args::ValueFlag heightValue(argumentParser, "pixels", "Image height", {'h', "height"}); @@ -65,9 +67,12 @@ int main(int argc, char* argv[]) { const double lat = latValue ? args::get(latValue) : 0; const double lon = lonValue ? args::get(lonValue) : 0; + const double alt = altValue ? args::get(altValue) : 0; const double zoom = zoomValue ? args::get(zoomValue) : 0; + const double fov = fovValue ? args::get(fovValue) : mbgl::util::rad2deg(mbgl::util::DEFAULT_FOV); const double bearing = bearingValue ? args::get(bearingValue) : 0; const double pitch = pitchValue ? args::get(pitchValue) : 0; + const double roll = rollValue ? args::get(rollValue) : 0; const double pixelRatio = pixelRatioValue ? args::get(pixelRatioValue) : 1; const uint32_t width = widthValue ? args::get(widthValue) : 512; @@ -120,7 +125,14 @@ int main(int argc, char* argv[]) { LatLngBounds boundingBox = LatLngBounds::hull(LatLng(bounds[0], bounds[1]), LatLng(bounds[2], bounds[3])); map.jumpTo(map.cameraForLatLngBounds(boundingBox, EdgeInsets(), bearing, pitch)); } else { - map.jumpTo(CameraOptions().withCenter(LatLng{lat, lon}).withZoom(zoom).withBearing(bearing).withPitch(pitch)); + map.jumpTo(CameraOptions() + .withCenter(LatLng{lat, lon}) + .withCenterAltitude(alt) + .withZoom(zoom) + .withBearing(bearing) + .withPitch(pitch) + .withRoll(roll) + .withFov(fov)); } if (debug) { diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 3bdacf5e8..5357647b3 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -21,6 +21,10 @@ struct CameraOptions { center = o; return *this; } + CameraOptions& withCenterAltitude(const std::optional& o) { + centerAltitude = o; + return *this; + } CameraOptions& withPadding(const std::optional& p) { padding = p; return *this; @@ -41,10 +45,21 @@ struct CameraOptions { pitch = o; return *this; } + CameraOptions& withRoll(const std::optional& o) { + roll = o; + return *this; + } + CameraOptions& withFov(const std::optional& o) { + fov = o; + return *this; + } /** Coordinate at the center of the map. */ std::optional center; + /** Altitude of the center of the map, in meters above sea level. */ + std::optional centerAltitude; + /** Padding around the interior of the view that affects the frame of reference for `center`. */ std::optional padding; @@ -63,11 +78,18 @@ struct CameraOptions { /** Pitch toward the horizon measured in degrees , with 0 deg resulting in a two-dimensional map. */ std::optional pitch; + + /** Camera roll, measured in degrees. */ + std::optional roll; + + /** Camera vertical field of view, measured in degrees. */ + std::optional fov; }; constexpr bool operator==(const CameraOptions& a, const CameraOptions& b) { return a.center == b.center && a.padding == b.padding && a.anchor == b.anchor && a.zoom == b.zoom && - a.bearing == b.bearing && a.pitch == b.pitch; + a.bearing == b.bearing && a.pitch == b.pitch && a.roll == b.roll && a.fov == b.fov && + a.centerAltitude == b.centerAltitude; } constexpr bool operator!=(const CameraOptions& a, const CameraOptions& b) { @@ -134,7 +156,6 @@ struct FreeCameraOptions { 0, 0] Orientation can be set freely but certain constraints still apply - - Orientation must be representable with only pitch and bearing. - Pitch has an upper limit */ std::optional orientation = std::nullopt; @@ -151,9 +172,9 @@ struct FreeCameraOptions { bearing can't be deduced from the viewing direction */ void lookAtPoint(const LatLng& location, const std::optional& upVector = std::nullopt) noexcept; - /** Helper function for setting the orientation of the camera as a pitch and - a bearing. Both values are in degrees */ - void setPitchBearing(double pitch, double bearing) noexcept; + /** Helper function for setting the orientation of the camera as a roll, pitch, and + a bearing. All values are in degrees */ + void setRollPitchBearing(double roll, double pitch, double bearing) noexcept; }; } // namespace mbgl diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index 847c72a25..4a95e82ff 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -32,7 +32,13 @@ constexpr double LATITUDE_MAX = 85.051128779806604; constexpr double LONGITUDE_MAX = 180; constexpr double DEGREES_MAX = 360; constexpr double PITCH_MIN = 0.0; -constexpr double PITCH_MAX = M_PI / 3; +constexpr double DEFAULT_PITCH_MAX = M_PI / 3; +constexpr double PITCH_MAX = M_PI; +// This fov value is somewhat arbitrary. The altitude of the camera used +// to be defined as 1.5 screen heights above the ground, which was an +// arbitrary choice. This is the fov equivalent to that value calculated +// with: `fov = 2 * arctan((height / 2) / (height * 1.5))` +constexpr double DEFAULT_FOV = 0.6435011087932844; constexpr double MIN_ZOOM = 0.0; constexpr double MAX_ZOOM = 25.5; constexpr float MIN_ZOOM_F = MIN_ZOOM; diff --git a/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/expected.png b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..bc926174731bd9caf7aa5eab9d4b9908f93d252b GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU^Mr1aSW+oe0p%d*R({L;}6gO z{iN72t&MH*(u2Yo8vF}wE9MKRd}NT3nG>7E-I`9M3eK0&v(j1 z{^|atrf2cC2WpaU#-ys5=B{!bWZSJkV{5XI1`?&aE2d>&%))7m$Z|C1@#e7av z!F5}`jQ!k;_FE+Tk7Y)^+510lZRxuiA~&pTY99F3-dC0G}?O4J<(F z)gGziFO`oj@yPyjCqnXHkWbed{qz1i*opLj${pRBQ})}q_jYLlLz2PM)z4*}Q$iB} DPOzHg literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/style.json b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/style.json new file mode 100644 index 000000000..7af8e5714 --- /dev/null +++ b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-map/style.json @@ -0,0 +1,37 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "bearing": 45, + "pitch": 45, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "sprite": "local://sprites/sprite", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "icon-rotation-alignment": "map", + "icon-image": "oneway" + } + } + ] +} diff --git a/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/expected.png b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c38cf492db08b45c597bcfcac51653006538d560 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Or9=|Ar*{or0OmKc{4z~w+9`$ znj9F~F5Z8Wl-m-e*{Y~m()@v~tkuS0_Z^LOcVGHGlQZ&@UFgcgT>G9As0#}IIj&)_ ziMu;x$xO-idnYzWFj~y{^gK;C{`>Ko=@7BW9`j6cpMP0ey7l|*r!l9SJs2}?oqKuM z^6WLe(onIZTe*7E3>KKF3=9X(?`2%SORF$%n%{hosHdx+%Q~loCIJ5{ BQ(*uA literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/style.json b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/style.json new file mode 100644 index 000000000..f181d9557 --- /dev/null +++ b/metrics/integration/render-tests/icon-roll-alignment/auto-rotation-alignment-viewport/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "bearing": 45, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "sprite": "local://sprites/sprite", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "icon-rotation-alignment": "auto", + "icon-image": "oneway" + } + } + ] +} diff --git a/metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/expected.png b/metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7fb1bfeb8d2763aaba6ff37205402dd9b7959b42 GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVASw*aSW+oe0tE{OQw*a?cw+B z`?RtIH*zfbELO!M%`S57)Gen89#+}hjxHO7SpN6Ek<2M#h?b7l6^&$>d~@S>2IHgO zZG`*|d0KF)KUA61GP!^-XGwZ#?D;(=sZW#teEU#c{d;bo=lOZ}e;UqsT$7jh@%_c8 zXTQGw^5EZsRdz2sN?!bU^={XJtZ0qapp4s$t(qG9Ga|R`*!E?;hg>4KKw8fXwkQA+m0RJ iw4F$VJCVHeomJh*deZyCjpe|gWAJqKb6Mw<&;$VffSz># literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/style.json b/metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/style.json new file mode 100644 index 000000000..1bc7f7b2b --- /dev/null +++ b/metrics/integration/render-tests/icon-roll-alignment/map-rotation-alignment-viewport/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "bearing": 45, + "pitch": 45, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "sprite": "local://sprites/sprite", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "icon-rotation-alignment": "viewport", + "icon-pitch-alignment": "map", + "icon-image": "oneway" + } + } + ] +} diff --git a/metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/expected.png b/metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a22865b20631d4257f21fae3a879dc910654765a GIT binary patch literal 406 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV2t&2aSW+oe0p$WR&b%jv4{7| zf2oBTh%|{5YRTSG)RVR1c*SZlO~Rls>v)i072{0Nm`kP?PICFOeKeXAJ!|XcfIBTq zp8wg~U3ciW&7nC4>Ss8MiKHg=9DlrO^XAK;TlnmG+2cKwCfVG*TOAf5$98dk&Fuew zEMDn*CPWD|G4!qr4xDTzB{R?xQnadBn-?9JA4Gdu(~ zeq)(szjQ;-0@Lk$VsEC2Pu@H_UL0}t z_8ckes$6R0OQCEtRrvv{FSEGr`!<&jHb-H}LzcqVOr;7UFa{ZtD g9K?DL7!dy%%9UMLm_9vu85rISp00i_>zopr0KxXEDgXcg literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/style.json b/metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/style.json new file mode 100644 index 000000000..9007373d3 --- /dev/null +++ b/metrics/integration/render-tests/icon-roll-alignment/viewport-rotation-alignment-map/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "bearing": 45, + "pitch": 45, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "sprite": "local://sprites/sprite", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "icon-rotation-alignment": "map", + "icon-pitch-alignment": "viewport", + "icon-image": "oneway" + } + } + ] +} diff --git a/metrics/integration/render-tests/text-keep-upright/line-placement-true-roll-pitch-bearing/expected.png b/metrics/integration/render-tests/text-keep-upright/line-placement-true-roll-pitch-bearing/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4a5a2748b682dea9d652368c2002c2fb42e22a GIT binary patch literal 19328 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 literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/text-keep-upright/line-placement-true-rolled/style.json b/metrics/integration/render-tests/text-keep-upright/line-placement-true-rolled/style.json new file mode 100644 index 000000000..0767dc811 --- /dev/null +++ b/metrics/integration/render-tests/text-keep-upright/line-placement-true-rolled/style.json @@ -0,0 +1,56 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "operations": [ + [ "setRoll", 180 ], + [ "wait" ] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "zoom": 14, + "sources": { + "maplibre": { + "type": "vector", + "maxzoom": 14, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "line-placement-true", + "type": "symbol", + "source": "maplibre", + "source-layer": "road", + "layout": { + "symbol-placement": "line", + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "{class} {class}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true + }, + "paint": { + "text-opacity": 1 + } + } + ] +} diff --git a/metrics/integration/render-tests/text-pitch-scaling/line-half-roll/expected.png b/metrics/integration/render-tests/text-pitch-scaling/line-half-roll/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..35b29f941a2c1204bc68b474571ebdba4b86dc75 GIT binary patch literal 95859 zcmX6^Wmr^Q7ah90hAxGnySowTkPvC4yL)IPBqc<;5di@yk$mayF6of&nE5Wx_Y3~O zz4x3QYp=ETCrx!F983yK5D0|xMp<4P1VRLUMFgRv0za;NXC6QZ-lA{hWpsVN9p_*) zt64ur<5Ywc$|S2q_0^=|Q$+ATQnagh1dSG<;_w$lpngev^-5Qs;uH24ZFvSA-XlIi zimCk2TzKnxP_CunoKXPn*rH8dQ0`;v&5m$Do>$bLwcDV+bsgDwi~fh}qn)4qG^t$e zcMKN->mghh%P#MOVTn?(q|0%*GOoncJ91Ia@+zZWD($cb2a%mLo$XA^&&d+ED>^C} zoE&7Pfj@7rvpLVkr5KLpJ~^&r-etgh@T6h_FW&sUj4~(Y2!7Vv-peC8%8;;pxcbi@ zw$GTHD7ALw5=Y9nbNJmfZD%+ng*h)!+qvV}TjZp9tJt{3{Y0zL(Xhg>zBJ(Ev;)3& z{9yceyRHzJCwTFRF!C7*9N9CF%(&A_W*Q%u+k5k`={;v_Z=8bUopiBUw%}BQosP)m z2w#bLPn0u421HACD1+;i^aePB^hW`z8z~s1&V6S*_niJ@)20hCnVj$9vWX9yk=krz z{OfC8@2Y_eQ>J)m2W;s{0E?78+343;O33r~1kYoL`(BRDpPJ?Upx{t!a$fmX&jW@( z0k=2puwjnsag(lVZe&46GL!klvdA(T|1s+OyyXmYT4{hGeSAVb`N@H1b2+pt` zP9K!Rli03aE|Al2+5OshG03d5z2d=@M(thBiG+rW@hrI>l(C?U-7^sAijDp+V0cvo z+5AA3?wi!d5wOVle?&VeZ#mu6(H@b}9{>Bq;IY>iLi-KxhUSKz5zd2@4-U5;3p4)X z?{$2H-togQwf5#dL%9=A>Ip&{d{?ly3g0)m%xKcCK-pjYT|?zH!%{acc@4ybz57Ne z+VItZhxOHd)>PRz+t2kloL+kbY{vR7daw{DLGFC{2i>b`{TdVZK60C8ih`iqRsG!H z2RoMp7o+Vo`b`7*&;scM8Ez-rZM=^lVvpQ_6OsEEUt_|#YLf934TA?TGP`fpMI_ez zV7?5kN*pQPn}18A#j=rdCAt->Z9O4mE|Ymoz?-(Q8=QT=B;=;@m8ZU#E`4o}0vv|w z*}x*_4CDNSI2wO}zZ5a{y6;wk`+mVOMu~B2t;rw%wcN8>HtAizcJHSRh z6vhP}0YP@-Z{Mx+jfRN(Ziz z)#2*c>w)1-d3RtunmF3wr^D~d0|;pRDQ~%)UjOmOxr~oE&G~tDvpiiAa0}zL858$A zD9Nx|s5O6YoN5b-%T7R&16d+4_Hf6KAj#3Vtl~v?&`b!UfUj#mj#Ai77Dj$2`MS$t z-xmBWL5Vg<_p=ntw?elfB@T0@Qg4_X!8GX0KKDh>bvX_%CN3Wc+wXSOXJ;qF^m7o- z4}bXMdU_r_?{;u7wd0Hw=Hkq^!EIV+_2vCt0}@T}-KO?W3x)gf5ZKlgT6gC~e`@|e z*P*nhy9D5>Tx@4Q{InPzf0Tgt#?7~Sn!{nwCF(h%Q)L>o^M94*@t?N-xc>B+y8XAT z51h8R4R)1r%Tx(o3?f7rO_*@^nT-7-2wdlRpiPFdu?~p={pX22bU1so^L*QJ!Tvnv z4&=<+FRXgSR)Z;=r%lu9Vj`i5>xC+j9%nZQ=Ywd#70Dz$;(cH5(%H)|MQ=8&|LA`7 z15ahTbnk5{Q-44}_oK6mAz3+Lv2AA9hFyCStwSCJ=`*q-GG37x4^pQqekn5{KFZwP zNY>_f0;P}^XLEiDRp1%@N$omN6t?rjaow{K4xwea{*`R7uEWIe6^23v3PpZwM!rt( zM7}YRW-@kz-KDTel*Qo3)e@O-_`t0G`roC5=OmiugVO9`oAKQ2pj>CDhzH?(w+Z)7 zM_4~LsE5b058I{!PB`iMHik13u2ZUNNdL=zHg)I=+wXMRw&%fA<^Z(&nv3K5@u6jx zj(^3WNAoouS+ZvM-w-N8&zZtVdVz*Ov;CK6tVr|I>0oNvo7GmY58E~JpZ)9 z9y!(Gq2Uq~vh(BLwX>A8+;&A-u6qU2VF)*OIRYKZz(Z4G$CVi>G#P6Zv{j_;_hx$^ z{q}w{|Km@fI~`Ro-S$hlvQ z7@{;b&Ltvn?#jI&=$q8ifsHQO;c~6KxBZ`Utd19cR$p|yNoA_L91|PH6+5O2IQ|(; zgnUs{uVnFrR|I-_QsfF%^!4##yH&0hig42&y!)jcnrGt>6(W9tUc?8 z-?%r}(5CGKp*3&0u1dY{yQ1L~0`{+p6cfB36?C2O9%FyOO33&?@f`Ud@Vu%a24@k+ zJ-jG*;%Cq!RB8C*l0%Ibhjav-N&Q=<&h>A*YmYmfJF?e!ou!suARu^Zjc2EATs1O3 zN9i`i_!=)1uj7K2mdG%F6(0gA{41nR8Kc$(q#T6G zV2gDcM>&MATWG*%J&0MrVJ;0QD5D}E<{ZzI~JlWR%S@nuS;$Y80bc& zXxpZ~vMK=(EP7%3ixnyWXjfktTI=QFt zNMLSJw--B~DCjyuYt6b4$zWxXzTu}B+rf##f6-g^aybj;trBBWz(SZbKu{=a-Ue(X z2r=Yolp^8xWtfg7spsT+6orUv%nt8`B6MCi=*7$f#aoiQnQ^95D10zKFSyn2S?8FN z>R#;+UMpxcTj=&%*q7`NWQ_1(E~h6Sb4yex0mhn?7F6&1JB4;8E(-~&o0w9;=HS3j^@hJd=J+l$=EG;!_^3W%?WBD(6EAv120E~I8~ah z1~t@s6V3DFQO?dPi0IU$C+XnYc*KOSxG@QU<29H-lqNDWQms>eAJy%MjO!-xBtR&g z={Au+Z?!*BK)>#CsTSklfDB^nWQrtL?83PbX@Dk*w&64kHRSMz_aZuRb}k_|xA~WzkoqNtBu z-DMLRa*x<2g^JO*1x;)IY(7E#=+V0tsL~(JfzCy!ijIx7h-U6qAF4?|5?U<)GN<{k z*)8u)lQYtV?!oxcLZB+$0QUu&+UTUWn7J&2sYaKHK`TyAZXxV;ji_ZaYDazJ?WC>@ zRXQRB98>&Pm1jzVD-*%27#WdP9$~JeHjD=`^RV|LlpbnR;1@{qRSuY_!o{Cl=M8=q za4%F@h!O#TfmFquPDdkI_@M&2C*^yFK0mCX*FkXxYcQR;<-N!QdcE+@yTVG#{?7?H zyN5vgXSDXe+TTPCcqNAtHd8-A#ju8i7s3xkWI&F-K%C!#HEI@`J5p9nSLl#~8d;1o zup8iRsOg*^zFxxRze+QrYJHF?#;nQ_f^P$v7jSV!PHGuLYb+2z?R&0A74r z7Pom=_EiS9c!&UEO5_b0htan4+H3^I1!z-+lGqCT9xs|7O-dx_wd$Ifa^|GEJT4=& zW{2S2NcPaLgti_k)VAQrPjT{&6_aKzK|oOMI5aHE0ZF4wexbUe8UK_J9$AeNS|PoE zxFwE@WD`z5Y6k9`lWEQ~36fg!rbIFMSa=#TNIK5LD2*0V85~{sK%)rbU9De3e^g3v z?(d?I3~UCHmq@+(eweFc#ZmP)Py}-~K2iv~njJfMo|mz%gfD0|Ij>$^dAN2gSAlvK zMA2{gQrXyJqY!9GT8i*}(Jdty>2jo1=)fn5ze|65aoe5W@8u=V+G;5;b8r3%xIIyi z)Wbp@1#w?Q>}EF?6mD$HcS%-26~3xinP1ktAtp1ThMI3v$HEH7aH!F!j#p%k^1L3@ zR7>cNQqpB6(CMhf(jUh|&>xvS`ofazsilJ3t`4UA6>x!6zZd}~hZM0XA#6RYw0uuVk{{KGxz3TJQ z`IJH{h|NebH;zUd;oxyM>Gf^@8Q%3{+)8gpI(n?iFgqgBF=1DV_)cr5;|CDTjwIX1 z%#$E0L@X1MUhT-wg^#DEToV3ISz;L6zG`j3*dS`o?jr!qQ&LU%9#l|`EDb&0EN^Yk z>Zb<4p{CGK85^kH^PZIMmDK%q>V=1-a^csP|GL$e;87G-WHzl_qC(4sG@cu{y*>De zGC+>d+)WT1o5TZc;zj%j?j^l|d+e2-WWledH7?G2#(hnmH8`E{1-nvTe@_-M=|Og* z0`Y+^@_@n4FJSk-z?O7!P~W?sb7HF)7-QK&TvvXi!F7{)zE{4NZ2C;F@d0y1ynel3 z_Ms_J%*-0<@#F&7i~{!0|@2?`;>~i$x<>s+yDA{mdo?F4^bRo4YbP!lK zlksuXDDdb9%Tz%RYuRmH_wuiao*%9~Oah1<5xWW{$8LoG zVHzdiQ(g3I);PV9`NBiTkpJ8tW$uV!9@6Sy5TXo1TYiGwkdoLn0|4IiaP9{ifZ&GI zE_r6+?}TpO~Y|cPK`DCJ{%%wZ*(m zIbZQmrTMnot|*uI6)`4kl)c=^8Qe?V<~CCN1Bi)H=(OCx|hiN7<^?MudCaACfyhRrWo1N5wa$cO%&Y@6Z1K-m_PF0PFC_a zc|$$UDDsZ;cU94&C^lM9`AY42eU=EHU(6JDES6`#Jjt2P!FcG8YXKrA)ihDB!+{Sz ztFK=swd00qb?s7Ba*+)v(l$UMC1<;|8{RBAaM{gOWxb`>+&GH9juDLBfnDMqa-(8{ zHO=OQfC-5#sWfme$0w!zbAlW*O5K4OXyYf>`z)$p;W_OlXT}Q6g2ug1Gs!8h z;X6*EXoJ%lQb(}>8_=u7O?BoS!&zHFuIQ%Z@vQadE-<%Hn)p$;A%klj!u5xLK`%RX zuILtCH_Vv|3=}FA+9lg?*eSdiVC_!)=4zx?`$Rv!l3)x?cnS@55PtU-tG0F!XqKiE zta^&7id|9i!c*`NT)5CW6`R`EZ+p~NO1?BoB#VA~{_e*=_lCddo3Opi>BhdBk(?A0 zzkQ@`{RP?7sLOB1_jr)GK^H&O<`xw9Ka{?;y4W5BI2J0QOM0nSstMakj~aq3>E+q6 zZVBX2ES8_asV2QrD14YQ^6H#W~)?)2c?#`D9Z`_X> za(U?n31lX|T~4H0$T*nD>v|h zKFH^8j@~0qb33W1Ze=vWKu6UHT% z(=4XbvPsu2L~y-fA@d*b-u|-hA2=vWO!L291LoiCVFWa)ZNKYOV|%CWh?ZQp^h~7A z@EZCFrGfSO%~wqOHjb}}B}4is4LX71xJ?=2TgFZDJs#_G80KcJeXt<2+PQ-kA-tc` z5unKUfgy8;o;K)eav?9Y_LWH@DLO@Df6ZEU=W|j<*3OC?@s~p0!61I~Ze)FQg#?U0 z6W~lO)Nc0i2OT7O11@bUdbmboq#RWMh8%g15ti%yT^v`b?^>{03%~_R$Q=i@oVK@5 z<#&B|zgeu$K?s!z8WTwzw$AYKpbw6nJiqh9mCLROQ-p=Bg^cG)OkezY37jvJMN0ke zeoR3+`!>kKi@rlkE|1| z(CC2&K0RHY`qIEBd0jVt=l;AY=0moht=xN7vuQP?+3I`d6tG{}CkKU#7qRoK%Q>;N zOOmMJF?x9p7-@zS;b3&mZKDn%b z4pGMHrmL}1h*Dhh1&GZ*60XWi?Rfy6Y!6R|#}F_wRDmbs78iYpQ(m z{A5BYp$cG(E;^C$ni+W9dwjiKc!0NP5hdicSWlQ%#$;-sIK*OF_p62!t@}I-GqCG8 zHkI%Mwg1fPs=uBiyxf7G0yqCLK4-H_NqHX|ya+?ao=%l)^}x}` z>4fEhTus$v{Q4xdK0H91`Fq2qUKI^^GZfi6)^`mYNhnMdM;b`3W)XV6(=1S>!2((P zjTMja(b0pc)A(E3#y95SRs}^F=lBoI@Z3RmqlSSJmO7LZFZi|^_F;@;R=7Z9^5>sx zjZW`hG!5&fRd0_;=m+l#$n{|kOE?;y+dRh7h3Lin0IjR*!a7W~KE4pRyHM$CK}Bh* zdK(uCN(v!|Z*x4P18PF6uh3|G^N(wLaNTG1^jlP11jP$6v`ohpzr4mZNe=TI&Di#j zZ-AZ;53Ft6%Oul82FRT8C*Qn24q0*Q7diDfS!v;1?dvCA<}|)@I_aXNqE_W`iL&yE z6)!F`;4&qaeH;1m?EG8>V_dLCq^@MIij13ZA?R{%+^6@wzOh@R*xg$|tOH2dKa~0p z6*eOHH~l@I_-H&W`9EY`6|{^vay1UW5-#t-_18PNl=H#K5qO~@vF?drQdbjlLembK z5MWz zSO9cS3~sP+^9UI~Ar5n9J%sQoTXjc7@VG8JljPo^(mKo zKedkWf6$|vaW}Zgeos7tq|$F~9@;QNqW9s`n|9tL$bmU9TYrb22B8TN;?mJ&he-k54zNLv^(n9)gYq6Ng z7FAj_>tD;a1}BOGgz-G1Okv?p9z@RO4U!D?BE1atuP-d17lN$}2bvTC!gdx34&3tF zdqX8y0n!X+J@qU$s7RWA8$6HOM^7z8g?j|nqQiu<>Xy@ynJg%Z`m8~I7;$6_bDf^} zaV0dC3)b6hQ~Vej%3P5kH`FQ67XgkZV=zHt&ALYxBCQ{pNq@;Bo@r5jkVxk2Xt;dHFJ=J;#U1+pvJ-A}*O}2x0GH7j#f}EHI3j>ZwY`z~yG6_U~{cM(|k;tLO zUs;z2*^V-*YRts#&!@3`3X_mjdeb+|#Tk(~C=zxBB? z6Na2ER=L^qokj(mYSs}w+Fk$!(HhCHNKUS)3NS=k8~f0>aut2S^jiE+%Cw{Xn_J)Y#nYt&fQ)Bz&!18oT-Hs#3OMX)J9;jd6-VQ! zP9b*Ivg9hR(eH%4@?v_CT1b;_O8yF9i(_BJn`=SmP2tPSZKA^=@JCajl}N#om!W|| z&=CTEnD6+;36dC54Y&+%s8uJwIS6ddIR<$m$aUv`zEcvX}H>2>B zI=;z#;^zPKDc((3iKLY`B~!mc_KFFD`qDA-5w`XQ6maPBo~>+GbX z*noOJ&fB*_K&7{n={0|+q|5d*3k4-$VP-(4kkxn1Z?DA;>dPB&(qePc2?$}Lws8lB z4s(uIg1ukH9`T|$7Q-uk|Ez|FAz4=WoUTPaTUsGwPi}%f64EP}t_(Y*sO`T&IB^g) zfzUDck3W3EoU}}uJoGvJbK6S1)b~vx3Wka*&nuJHEK1Gjk{?GX1mRc(LaP`@_qltf zkJu53rqcSePNJ5y*99=(?czRD`>aeleMHaXF1&(0p8CXw4j|STw`jksx>H+f@KXfF zE!l@FawhXpG-6aEEwe5eT=B}?qUNvxToKcy)BRqXcZj#wb064}_vTymM<*?jk^fw6 z_CyC96(kVB3gCHe^})IUjJWafF)b#FG<0E~h_2#xU-1FtM0`U5(Q{!ZPnSqxVU|E4 zHE##(;rq7`*u6R-m@y2Aw!%!dlIzb^3YOEFr_NiT4ApP<;FYx?c2Ni%syyDTGIWqp zmJcG4VdsUwS}wg4T!rs=KocsRbeSiHMSfXdh3%%&yQNqc-x5ooI>h5L*gXDg%Hzu& zPGi~9wriMs=zko`$%drhggXS^=J>(y5w!AaEq1#91EwP{JpDlvT*r>^H`U~fRa}!C2|E8>$aUH^Lg{NPbLN@GnGr3qfz2Wjy`jrazF4_=r zjitt*cYrv}0DvX`TqPYn@5LQ_k^TJC?YiNUG_9g`SJSQa+Gf%*l6f;rT_r3$)}bSw zMw~11lt2A_Ou|r+7=ngETW+3DF6R3VBIOv@(On2*{+pra%cW{!e%G`5IbgB^n^wY; zmOUDgivdRK`42u{{xvR-m7t=l)zj=VEnM+AsSJ`Yqte5z!Y47YUhX#R_;sAQU5Xt8 z7*vcXe7AUZ<|^vBzfXNfBA(dMHd7Olw&MLB&F}eI!)ki~<6=9trdcj{w$dw^q|wpd zF~uGm-<-RVhDp+SA`FLC)!ypww`8AvcbO`*WH2m>`0P1->uWtJI=ug<>!!=v5iEsX zZY@>R3&ye9dhMOM%RK}|l0!viar~wLBA? z=$`6ctyC;vqrtBo;mxzODEV|Iw`Z6#RR{!w7enMuI=W^we-BV1kN`Ra0~yy)}g+>)=~)uiN_|H6vq)`REvSukq@s(a82 zhxk2t(K`i4p3M62T zhB9t$NSnt&yeOQYnARxY0l4#M9Pn89Qw z*-pDgg~n55!X;y{UlXz9vz0n7x@%X?m}nRECA-7!$&^pmfcweulH=aYb_q_w;%ZaL z4S~I$7l8g4(f%gHrDfW=RO^*2RcoUB$BGpwevyby7DN^giJHe)M-C>9$9Utv1N&JJ z7!cw~rGZqkUJttc5_GfJzkFriyak|?nZB0Ipde7>Gs6&h6_AC zeyL8tfGm%um`IaDX(HH|Qy^DSa6d!-2W$wo=^_XFUhBeij+?eV2Ml>H|6_anr>|e# z(H;qOAx*;pA9#!nQt~~Qx)hM8cXh8tgR-G$X zB{)Lm3BT2}(eY0cCPkJ+02`#+QsMz{fX;fG@fHzxgM}N(mumP5c zWK5DumBExR*0-y8m+_&N0P3Kas@+`7{n;p}|2IQGnz*yL?h>yrT?5Bqae(dxtDp$- z^daknQlWmvL3;DF&(B+|M_$S^=%N2a!!&QPRn(XNarEm{g-+SP>3A(Mj@MFBDsqEi zp{}B56u(-qG)?wxtZca_?!chpL@)a8&Lpuf&@|kq`9{X*5amWer;J+U*eypj9VWQq zXVUue0So3{R%aVRP3V_8o^Pq4KmTc>=@1*41H3vWjyPFcCS>7kMy!lighYV1%DHU} z5aJET0N35b8KqMg3>!fUYUN(ui-1eGmMHw;?uSef!W>1j(*!w@-m;3%D2~v|hTr{f zu}O9Y;bKdCaCa>uFBDfI3Y^S~jjGMhmee~EibsIL+lVv8?bb8Sivg4r+3+C)(xE09 z!+I*%tyHpMJt3_JQ}3se$+ZX}`McCj=@}Gbk5PdFC;I^qV`ycuy|EQvMB4cbR;5u^ zc#Lef)h}{I{u$W)>3b<g<>869A!^}Kra&O`^G9tivLi- z5o^1(Hyzk1d+K6T4^%KyQrD-9LoZyakAh&oXfOPd4>J4k11asIj$)e~+u^sP%ww{ta*Z9>`QL=gSzuze z<0V+h=%tq|b>i-X$>PUepGC8^zn5pJ?9UL_l-K?gW#8Om`NBl@?Gk>xZ*q2q zi%<)xP6S>}Dlyi))n(^Vwk?|WosfM$g{(lIk%Qr?(eEW_&G1rLaAkS7qqn3fa_;wF zRIh0;X{-%!5au7n%Mw7LqV0lX1uUNlK;3Cz74*^KR2H35;mLe6yx++b12)K~))ATe zf#wr<3Q&79xBPX$u~3afr{WX-i)4-`E|xm7wU|gVxLEyF2bO$2ihMn{qmA99HUS{; z7fr}CW`skkud(r{nMn!=!%zmb9jRb|OJU^?jhJ*#=?p={EFrNH60}1I&COi8sjFP8 z2y-WZ%xfRHGyfXRDF(5@Zn|$4bce$R8}o(FphuAWM@UdK#-x6qc%#PP>FJha#Q0y1 zs$G3^sOU$nv<^Xl5kj|>DGHIo6ck(S$n@+hP?yYW6<|nf;6C55{6?#d+O)#^zWvVP z&!Z8pFAbPb{K`^LCr3bYrIV4pszAaqbEHJ55f}5J1%6#Me~%r^efCwzg%coD8jq4N zWbH@S?Xxjwj?dZ;X1brI;vhq2*qfd5IP}9-K)K;Xd`LEw$Nq*9E+@KxlmNk`r2iMT zpLw}qS%@Uhy6$TkhE4MnY?UZwf;V5%nWPjGo;Le}`y+QFQ*T&eX>^#nHWLY;6ATW_ zjuG7dg1amv=Wvqk|Eq0m+{jbM{V!_3?`WpG8NqlFnyO@5)3Sizo1*F z{91Lq;ImNTe23?OdlOH!$@6zU^qI?BC8ULVZZJ3DU5J~CoA$pC6F)+>7_;bkV4SI#5AwSCZ8h;GIc;FHHJzp{ZT$SAaW+S8*k zOuokLcSK|KW*|&ZAO=cFJR*x}TMq=FGpcBe~6V;o1%#}PIxO=?Ut z{wYGoSh%X*SYy`=Zi^f!pkQ8PB5XtpbOt4uzqOAG5Oo|SCT=TRGI_Wd=K3x8U-RU; z0tse9oImj}0Ah=zp~yT5kTa8om9RB&G(j73O6qY3NSYwTb9^r<=G*tK4J9Fs1avFD zE5GQSglU9zvT+i|&e*|5-2du*@%Uz^clWX-gId|t|Ie7)G?q&fUhKg}XF7r^ftkXJ zxPjXZqS;0qxqCGGeG?8F4DP~b4uhz+=w$XbbhZbM&rHf9j{2S5aX@Ij|)5h_QtK!F<_?yeuUvcSu$%#ZpmFuIbcsocH1Dh zh>>GN5bdY31ToT8p{p!=?sIQk!_Z}j#Wn8uv*QrS%Zv@s&{aHqBQPZnXP{D>7|c2&z7J~|@e zL^taimi9b~SoXz_GI%JU{P(3BHGxFoCAbK?yDH*8_sW&=LEqVg1kgr>nx5G_T=h=< za#*Vz;~`EW82cCm!d>*-IOk<4^YarPCPvBVa2uH)8tkSa&%Yjj<2!TNo~>Ono({_l&?~zWs zE(6^g<8OJoSz!5CTm)_C-slSd;I?S`p3OaP^Gs}4Ai>&EtT>O`q#ozx6QhvCoF`D% zLQMLGxQrTC{Q0RC*xRoOaPGI^sl=&!VbM}St;$Dp z4pw((UdbvU+s~7#O8Pl7zJl{0WSQE?C9I{93#`~VU%Y7%-vKW1p1Y;O>V=OHawaqc zEb)@@9pyn5@AI)gm5jR0?;?f86P($`kS{+dcYgCr3U;``IId7>B@pH zbji79!C3d_Fo=tB4eVma7c;xzd6D}}Ih7>A+&IaE4pEvvUsxeEBOrvod*4%io8;nD z8G9?pW~!J!mO9}$$`fQKhbh;jr8oPAVNGt{L3DM5RrgmDti zTzOl}AK-qPXiy#~K~jc)cpJ%8X1bcjOPUYqr7Vk9lDnB(O}Z)PmtXjh<860@8wY&{H~uY5v` zr_OE@10k7pLPMwOcAf!l`1Wl$>fR7@-iVuNG(jkmw3V2_d+E1k6z`W3WKnlyaghae zmoN?-2Nbn3pP(ITT5oe)>D-0gHK*=nxA*Y=lja34QB`!&qeQ!@7=`!bHZW=7h?XhO zzmBtbxgi$qgU!7OCGn(I5XbhGDK&MQ?Ia$r@0l%NHS-2G7~tu=*0>pjuPc@REy2&l z+fjhk6$&s>Tker7&MFZQPK9D)1Bm>As@pN2qr0bs26W4@Ho3dahOXrJ(Y+9hQCLWe zA0r>~O}j6kb`j}D+M4?Wnm#>_#F)-J+R8dST#-&>=L;sqZQXj50l4405lqf6b# z)uaMbLh>xcc>%XwaJ1u~CfqUqMe|{+<-U zT8vwp6a8XE`LTxQU=&}=WkmW9jC>oNL4bk5xC=12FF&lYdh(fDnHh6WrUXQv%3L8J zFiw4&fe?EV_X6oUAfwE_==!-97ZlqH%p`U$p`$5)Cs;%-bOwax-{kyf1+nIRQ#Byn z4Q7)JL5fkXu8YBS`N|6eN&_tx-%@BcCKkTlBUNggd$?(TV@0g>Ia`y2-ECEOaxRGA zqo4^h7&Uy+)*LcMIKgAUk_U5xd5{a2vau*$4g$k`UO3B$0o3(Z0yj_=uu7oOI?SY# zX8k^Ge-D@=n1C1Yj1$DvMzHLAg^9v(=z!eCJ!?b~k&1p$DvFq8@5wryFoG;Wc!POy z2KI|MCW#khK#j3Frz!}D7m>Y>v2oKld+4raI|Fcu)~kZENRv58soJtOje*>q{~i3i zUvd6n8d%b30a%$A9YMQL9>Z>}t;M0@Qy6C`FtFMov`5<^g0);j+{&j_V$i^QB42M! zJ*AbSF$2Z@;t~9h?3m?kV=N@5WXsVNf3e$Oz62a8tGSIEVRUCiCpn+sbDpfE#B>MD zKjG?BO0&B4kW~a27Oj8(YxGEzxLJ_9SqpG^*@9d7RndtPPSDkW=zgX{nI?n!C%GU= zO!0j#QaGYWgkh%JX7ozPw}~&we%Q2V z&Abtar8h6@bZAtrP{0MW!>K!L(GDcV%0>ZTG!d!cK&KB038fDZCP0Wo1XoC_XCiVf zwLGOXe0oUh=VcXl{7;UVa4lp;Jh>41Qnn&TQ`GoaO~)Hd$0yyR`5svdLSQ@BFA@n6 z3B#9-+baU3E*&vI*nC-%dykEjB`UzGuFB^u0%z=Qd&VaO?p1?srqwXv* zjcX+D^H+W_7V+i4C?2dUh7!UIrM_+Y!o;tt5(!81HPMRxzAOf^>Qy$woFk7^bK7?2 zUDdR4qkalZaG0%y@Qb>oUkU6&o2C;@4{Ae>a^0Z1Iq(34aoyxs0Cs=UXB) z&z0b=-M9(GGWDDnuT(YD20-Es>G@l_f(zi~Uul-YIdIGzabnsGc0%0utLZG1^28&5 zR|VL!OyQtF!HkRCuZCU{Mb3qTVp|Kpupix^*%jC?c!2+n<1#iqA-DNP|3U}wS`Q5X z+cHeoNAu$x0~T!((ILjev|galube%~n9u7}Rtmpjs%rCVB(q-m%7k{^xYcPDDImY( z!FW75-?vu|#Fg8(2W6kA+Q(Swn+(d-seSf6?zYYW`{xo)kRPzFW*kRLA5sbP5`+#R zqM5@vEvEJqQKeBt?ARGyH2FfI>a8-nnM(yfCRfMh0XSQSSm|c?&D?ck8l@9q6l{te z`X-go2m!DSF><_Ldwtr*r<%@gxH*MPj3;^4Y43N)l0259K;sV~Kmbcgo_F3Ug~#B^ zJ1zDZa9c|=a!s5j-sa&HY?s)Etg#HQe<50`ph?CvAi_hCaQQQhC_(7` zB@4F-)d{s|%+urgR*Boz{Pt(DbPQ_f?V6Ypz&fb|>VRb<`aJ;w-Dx6ysEo|lPGA7y z(j2+zqjVt#^xJJ|Pv)ToF6Dh(*-ftOmpKy?GSZbTTgit$DH;(_*q25WwhFKTC7uVo z+6CP~+7(mJfQKPxXEU1C+sWufU3CiA((51a)#=egzgAWxK#>6LHZf=0+d1OS-nuQ( zqVzVc2OnX=7i{WY$f~MPj8LuF8F^4)D@fG)7*eA0Zv$Ay+x|8GZYUg264Fl-P_>-@ z^X_08V2^uNx2w55wdq(;Oy~2><-7l+;W$|xARB)8uZ0x#yWceERL{7;O$(^6j1C5E zzCXnp!U?}iql75Cc6LpCe!dM+_rFz-@!vQw4Lt8vr?36++uM11(PLaeH*Kzk_;2p1PTH7+z^01 zTCD=nU}IP)O1sK#_^ki%F&!^^mO+b;XSX8~ydW=p>vl1#DBN(YNODj5O4U`~7xErR z%mLN`qQ5M6d!CO4QTw7^hM z@%2+rPz*)B5U%6g)pH{teo_dE%M4|#n+~OkNLT9Gv2wKwYqvjpC-`)7dG&@EA?4Gu z@#7%lAQj!ozGY!7OPHZY8#lsL-(j~czvIIFKmpW4!@(qaxfKK&6g0k|$B09>tMi4y zEsY$}(O)RMxXmhGD(jwaW=muvAGE*Eb?j7rKS@5{Vh)Jv%{y@j3lH5#q%(@4@AMeJ zCHvdS%NVX%bcIGqnZ*prEdC>uV_UrY+pJ5I)B^a9FC%$U>pQ1^J3*a4H#$FN(j~&4 zm0Bi(BQcefxB56r)UwysnD`aipWLIwuV*|?r1QjXnX~w8Ot!svIGdT~xljXQk`arO zV?Y2sAEAzGA@4hdfvVJq{rE-^BPY=ryr_7(_GdLeAArka|H6{EiEnzytY(M140pLw~O4A%fe;8-$5efnf$BqOu&mm5$FC@e1q;0nNuXX_sdS+dD< zf{Dc2u%WU`(y=OdLclV-+TmwXvAGQwyg!J{bHmDu=tRS1I*KzJkG3g5+wetnjAtEm zg&F&~s)$?>b1gWrKxJ{3Zc!ciqK(z@??)dH~hEA(5z1XK1 zW3Wt7uSzU}i<*wiqkz+>J^u%hIW$w+FLnG#`FCeSY&F6-m_-|OUth%9-vR^q`Q`g2 zRPtvZ8RiY+SqOLpW~px)I5`Yz?W!uIP*&|Mz<-DFI)#?>Nt_ z%W6UAgs&3h5RK@*U;oFDqp}y>Wf=sInuSw&{G;i)`;CviPi<`rOdAnaw4NBK(hC*m z3(BNN=oSL^yyPA%E4RlIKuZ~kOvSYnu(l~Bxf0eh-(Z+CMH7F~gGVLA@f1F>fl@X8 zNrFB*JEHh>b3`>8BuOo`l4r}hd}yc>sB*{o@t0wpY_(2vmMKm9FCZJFiUt(r!Hu2@ z`(Oil$nQ>xSpJ$YV!s#929Hbh^9wXDwl{)9d`*>xQk%N2SnE2543QKSeC5Z^cvC;b z)o~TczSUa7%YM-QyY(V%XiC*q37V)h3`}T!3HWp!%={lg65Stw2DPwNk(R=#;|9b8 zH4Lfg9f4p(2ZOmqW)c|*w@uo;^fAS3iO+#5%zQ<2Vj_)FVr5N-oEiA-2Qm~nsw3kZ z@?f#1i}+kj{C$+)vP-phuMC1rjOrI+I2C`x%Zid_PPt7Urya3K9iBo5Jq2{^o%6z~ zplRXPW2r6wgZBiqjd1^Tlp|7>tfOBdpI%?C!Kpw7K?AsH?Nv}c-w=ETu$WluM&8|c z_y5!nz_PUBG{Ls$3BCp62pc&g+Zh9y9;DKOUM+IDySL{Le?(p2M=JlujsTCPwsmrb zp8jHCrwdL44#-tlz<$C%r^^trcfvq{_frVycV#V#T3$`43c*$RTLOHZW6H`6U%61Z_aQQK>;nqqNvzbF zp#2wc@HfWM8IdM-3|239O8c6@IjpUQr*~6q)2gQj*%;rApo0g}2PSJ;b3ev^C-21d z$i(uW^MaH7Hr8H#3MN2hRTxr7{{qbr=s@EzvCkEJD)=A@qxc!O;m-+8@mMlQAB97Q) zTJ%3n%(CD6s~&~XzZToIT@gPYoOL06KL_!-nMyut8Y@#qaOGt$?J0zrHQE-6rp|I@ zhY;$XFpP7p-mC5zB7Ekc-Tg%7d6>VRGxKCUc28Hk^SkA_-6C-31~4G#sAyC4Z3x%o z;VB;j#TdG5`0#ofn!4!!T7=Rh^Xh2AFQ!LJ^-0JIZpYMs06===16|gp=WEXa=@b#S z&99RhZxOka&qo9(-tF6>CHgAVGO;0bf~4!-HRUII|!`)aH3O! zfTD|7iHE|9`Q&g4C2LEHx>GBDwinoO+N8;8=4^6?&ByNQG||%t`Uqz=K%4|3sp$2fP;Y+g!GJ-~IeHma?YA{QIGYnXVS$ zIuLGr_pmT?l)3YCn_G#q_NY=y6O-VpP8EO`H*;FeNgOZweFNY&fd)QJ9*xQO$_nx@ z{>*|6jsgog1S}}oEK<^2mxOGSbr1srz|3wmQYbkDvBn_Vu0MKz+ zN%QDH(fJpP_bB7b?LW+77~hDsRvGfcnDTzaqVXYIO{5!)ZiVI2c0zp9?!!nAo!(g; zHW9(swOb??F}r>f7*heP6DDz}@q}C*Z7uyKaXz{kJbsFVJa(spA=3ni1*APvcn{ha zR|A`_64sAw>b44kY`;U5D-4pSSyiFpvKJQVwTSv!dU#nQuPNb!Fs)rdlIT{Yhl%V+ z2n#k17DOE=3mF7TAF4YJHV^EJ5$Gu<0URv#Q%&0f2drYkICJNOf_mrWP>X8GM68{Z zm&58_4395Ou{;@h7_q9_l%RVkuvp6hx&eCgY}XVlyes?Y71d&R5SjJTMx0fnbs1O1 ze9{Jt3G3Ig5!Ql-(MHls>IF#|kdsoyiz}mY%xWoEIi1H|&HB^*=XX_P@yH3kO%sp= zLAR8dnAhypSO@|pKa$6LZ2cI)yDhwqlial3uvxLBm(JN&Z2vm`u$PH+VXxZs12INv z64w&-il)@eu=6l$V(-&&;L`F>e!jxoE9bq@2|!uo04hSy2gC;xY~p{h98KW78-!ls zxtMd(X^WW>_(@lQaq!OnfUmKV}OC!Q`}m2c{GN>5?J zYfo7;9M5ZcGeR)Izz`WIi^0I;WXCr9OP7?zg@kszuno^_=|!%HGcBVWSl5Be?dV~p zA#^w>JpVepO~|(G3=rZ|SJkzpaR*^&IF{3S3F?)a3P4N^!E){C%NO9fuZL*_ED^;v z%MDHdQ|DDnbZi^X)V)YTfx~$E$@Tc<`xM`aX6a6q3e%Ha*y@0=JM z^r;SnXx@ErE*ax4%qmAYN__@fkY?{-M*bna1v!FIOu|$_8y9Vir76CIsG}7|3fo`w zMuEfi2$*E>W$?e~qF)a$*Aj=q69ZeUf5cVlNqe;}ym#?YgLyA)4r$AD+Z%39)0bGa zsR83w0jKrCS3t3){Ea?*a~fDK>7OBfS?0xp%z|FgsQ_s*P>i_lVPa7;)3+sTv($Mt zEsp*rN^$F8%N#9eG9x!RBAED1H)t|5H^I-9vtF))FjS1fTKzX`jcV?5_^5vruIrRD>c27wGnQKIyFG6APa1v>2%;&^TYPZL*%{ z-A^+ql1<|H@h7r*orrh!pGapLqF55Gg6S7y=r4xE!IIX^TJnOvzk^v%5y+@%v9H+j zztQ$x`El#{LeycuN1q~yrUJSuuA48>>tzCh`hHrB_}$T_V-l$_I?>7Ry^j~@&)&Ij;CZWC9a^oeeYE+5c& zH*t9M@f|6sziE^>?v5nX_|#d=7Z$mlQFSlsGQGdrBLgdOA+gg4MZ+*@*cZ<&x|~BM zAeY(r3y_B?@*Pp*DZVltgl#_~9MnAgYhs0_{VLNXwM%S+sE*6GVZln+&^}H~m`yXe=*=&N1f_-HC^A&@` zGWIYU^?8(tCNS6VefIcA0rW2SWyT}%qg*sB+61q*`Y(ciO_p@7Ckw7f0YLx0@}Hr! zr``Q|C)uIr`=XcTiqp}ub7GeJx~CIB_sB0dbjrmLqV5JDC2>W;TXj6H zJY*;;P*8AuWj}m{zJP7C1VWPuCny9;@g@;;pI5A@(zp%bA=Fr?PV&#CUs<>cC0y9eT$(}*> zFK&zJ??9H6PVQ`F81P4xj!*Ik?^1_c*3f=Uqvo=jb0~*-&rA8QRFDvB`|pirw$v|~ zC}6Y0L*ZD~@|irdc|+t9j|JXHe7KORTN>%ZD`I~=KGDydCg?4F6e4*{Q=Sp+`{Vgf zSDxoQjQ699!{sol@e)EGX(s$$PYfC$zY)ery@}5Stvup_Oy6HmLU1{a40prr2nnDe zCi>jU&&$6ET>8V+y@R-%)RXCsECy1x{jYiV z*B=5_g$4LrJVaYIen*;bwj{Zo<+xdktI2ugN=YVFp^-Zq+V{<{M%$QXqJV@ej)3aE zQ=X83=jNi__t*cLfMo`Mu@*X~?=Or0o z%XYXp;D6H%Mf>U2+ud0Wmc4wc-Kiok5EX7c!;>WA zQ))}?@2x{kg3K@D!2b6VoDkH}$pK*{J%p$hn2%>-iYXWGC=mF+;VC7RtRE+~CQ#B0 z`TDNdq*Q`oBCq2veqQwLOX4^BJO{FY0u4bJ?C4&6zGr@jxX1NhoO&C!o(u{zi#OE% zdcyT`0g3ugJQ+BTMk^SeIS{2}SD9ljyfa+#mAq^eor^N|KNWllU7FLNbF|uZ2j|>tYyMCEBQOtw_5eItJH3nR<4Izb5!$TVj2}tj9 zRqRcXC8BeHUXq`Y;0NM_Yw$v41m7P1{a3kJHVPy8uX^}5PZXw<(*sDLcPhfZemC~G zIlAt=cV?cNXT6Xn@L#-oeb6g-JMRj$E)S1x#>6GD5d0H`9>m(cZtkmqBs_fEi9Xz3k*YB~@p@0K2`1Dv5|&~+xC{s3jb%NGfmwV9 zI;gi+Z>l?BmwT}>TxH9spFD6sI@Et1$HdOe2%24TpaHKM-rLlEfoqkRw3trV!X zLpp9!NCQYAVw@pOS3I?8!|0UPO8Wm~>hVhpuC*&6?w{ZUMb^d>tOI?pVxQ+b=EY%Z z@nCJK*sSxMPv2Xr)hk@0TSenss{=bIPPQN$d5^r>Te!jq!c<#&(IkZ1BMOA@w=nck)0X3a5+bBnp`5KTC=SiUR>INC9#bB4EPHl|Vp7l=#t{4`}B^N2> z3NddiRxW}(&zjksz=ClAeiAnha2E_f>MfMh+AU@r?gS;c?odMg1Xq}D1`0)ukJ7`> zV?%>E{oO=h^D#stg(w91s#b-)PQo`34FMh|s0wi0qPfSB0Dyc0uqMm3JMEd)cHfPW zIQq;;;>lKOR_m5|jwaGDGah^cMQ7?6VH3~!JmAR0^oRY@j`Ketgx}dG7|5=7$g{)| zLJ(hqHplYV43LQPZqOxPL?%-FbIS((VmslHj*kPyJ_b^rgV}Z;Am6tCbD;hMqc&F@ z8xmC@G`%WVHXXmgA-<8TD~PP?Tq^;6yjaTu$PjBAiI-h~-aGB>@!Dtp(`i+m78@4( zE;hNE&YEm_te;Ou0uabkStmu1@&w6GVTQx)Yn_piTh}fpR+kBgd^bU;e>0{TZrNQ( zM4y9SLg+P^yifT*F+{g_Y4M2=MTiIwk`(Mt00B?*lz&er$b?nPKE=i@B;g$}U0(Df z1BpC*D2!n7n9E#hT()cOZ7BylMq*PHDoY$R7c{%upN@R2_yye1IZ+*?IliTenP@Zx zAF%u2Mr2?_w#?0`2IIH-D9Tr$UdDr3geq>hIKkHak!KID2xz)nE4B2{Sx4=Ahp2U% zz;W3e0Gt-iy#P%`#KEKcwLr6(qE?xF>N>meAl>@nJN?;;Ak}>3H+O>|_xt*TyOS4> z9;jY#G2YZCc;OdjJ|d_G-@Ya|DfF3OL{!2;jJ%}aAc1hJ@9_&;ubKlf(8A|TJ)}d2 zGbVe0#n3G?{r!Rt8au@fZK{D4i4>(-yBu4Uz%oRg5dMZmHqv%_;ZQ2!yJ-K)p13R5WF3Iiu|e{^!~GvqWe+*oo$$y>c7gu35edn7aii@H)T7)q|?PvEWbhPzUpXy zelu`1Yc6vz^|}`?j+UxgEH?@q!;ertL$H+mlJK$m zAG2s6Qmf>I&|OE+^RSJ$ zINS|r-P+#Has<7-;Qw7;Z3dxxL0&hQaRMk8g5iG768<~*Gzc<$gF`>(q7+%{j7wtF$QV(P)}ufmap_hso7Ncnm3~Z+Napdr z;3!4S1yu>%-jyelqEe@fFi0&!#X)i0K_4LkuhLpg`Y+m1DS?>6qXP2S=6x@LtPdEq zxx3VdTpH4`NH#@=$z^WM^GDdsKay0GK3&s zm%^gySl!PE*6L_$!rb^CBoOr6fq28WaZ!ZqGm@)kuUfrd&+FBC&tZTtauUc`TJmgU zW3VuQP(TvFPj}f&=GpV(|0XiW`w)cpoqGRMT%$pll@2yJggpe{$X6WsmXRddL zc?_A^f7yxlWxgR+CvHL%dCK#MULmJ)&gXJA)2($yb8+z@!u5@T7Ig12g*<&Pn{qrf zUdC$p!RCE3ce^@FGKWOOd8S{!f5|G4*hf(u_OZtVNu!^rdtm#Z#rDTkJjGSwWseldkBD12f~@8@__Yy6rwiMguB;3#ARdnTXKvLbLXXQd)t^-0`?e(t;#h zQAe1#g9m>Kv*^SWs z8O;zp;t=`M@uIs~N3ep$V9R`zz$+Eo781n@l1Wlt6#@+0=5rs*bDS^#umdpFUa8wz z3%+$W7d+_VeiNcN7{pq&j*!*Yg@><9Uu%Vyqlg^Y`Pv8{UIm0r%5|Mavz=xnQ#+vR zT5LOIfI7k`AX07n6R`gK)sR82BjmK*91F7fK5_fAv2O#|_5agYwT?c|+%eyt2j=9A zAy$fyfKO}L(Z%V;wf|}LiKXRW6pl&FYJ5CEiI3{qj{LOyOHDE1e67>_e%{brqC)FK zKaC7DaD+zBTnoyBE0pRB6wRHfHVNGk2RE8@^Ku?c@uApwi9g*YeZgNUnk&G?SZHol zLWX#!xx+cm-vjDtEutCliD>oXbK9{WT7ifc+ecp(?w{cB$5H;OGCd<<0$zta>*U=o z0ziZJS+~tWZ3Zw-(7tIlLBadRRrF~Ws zwwj~I`+YiBo`lQ=0WhcaSC@uLIzthR1j@g%ygaLokpwPB0SZaYmZq%hw1Ur%2YZ#3 zHFCjYU%JZg=vb5ssuRwrS(D_J0;d-_fXiO9Xnq^?%6H zgW6RJH5KP$o-cRehDU8%*Jy$2Rr2fo#>OO3+g2#if13VtXMV^8c^bTG&)Ww8jH$nx zQ&ZYIhAxX{D>U{)4=xG^6(yqYUmV2j-S7JM*X{1mi_%a*rmX=DP`9o^Oo@HsyfOLb zpj25oP!I;3NQbP2@E_%(lHgY2LhM8ar~+acA(y6Bg0Gi$u>yMnF}k!$Ij3hE6hwd; zS4+aC#m4t@QQo|~Mzi~&-)v=S7w-HHSW3{T=)^nMXHoBp&DKaoxoWoaen0oRb>;GP zK>en`Hvf-IFIR| z$K{lAwt^YAK2upORKv!Uu|U6H=Oh83Dhh~+r0$v1)j4qh*aylgAiJ` z#~Z;H3kzNH%(5QyMi}tuEcZoQ8H5tG80^*gk6CD&bSeJ`&88CY>Zr;~)z1^l+=47`s zQl|`PM9u$q1CMBO!X64&U1k*&=}5%`*c#p{90VTAd?K60NrE;k<`JbZj;Rh6l=9OS z!UN9pFsX#bUV9={8M@w4-i;Puv#C&WR!rgqfzw5WqHqC_My^q#=5+||4yIhNoK0!t z`^XKAG*`B?*X%+L+X?rwku(r(Y2krthA}v>aa2uh^o@#aM$U_|S+@N!9v8LqN&tb5 zh+s%0llucp6zZb)WZe4xJXe*Hkb_~8nRifZ>@cak&AD$~-A?5F5+|G7RfaST`&JyZ z#2nen`KvxQy%WS}Vh3BfOxkw9ef{lSoZW~__H?UpsOqalEXsEptf80VJ3nFpb~1^U za-!L?hUaW$*qZ_PAyM)_s~}Lc^qarOr0>y>Y^q)|z|iEKh{1GkI_;k&=p74)VCWPN zND}hpS-R)C+eL899dDSkx?bxjn@k0FbyE<-ankol-p(an1J#CKlIT4*FrvhapHjZt zC2&mf4k+Xmkt-uW1Bm&AMh@0*X%qi*Xfu^*r-@Mi+a5mIaF(}&)k8{zIXu>XIg}Dy zh^(qnz?;PTRXLWU1&W&xXCQ))(-Reeh*VHttwABo+VO!SP86ZvL#hw*g;F>Z$LM6$ zjZET?!{BO(0&D=JQn{O~BpZSd$H;;S+Vp;y#mb?1k8)RSM4z?_gHpQL<^mrPoqY+$ zXQ+CfEQ*;S8M!=sR67>m(%hRK_WaJnGW>7WMRLk@DG0Hwjw^xNEq6aCFMk|sPRV0k zsvbC#eEx(Y^)`YJOB^pDFGHuIE*CC1B{JYlMf_anK3hN2lY(VNvHHD67uDXx9)c1E zhrH0CiNiQ>zjETxel>8WSBe*lPHlS&@%4*&R3JCmbZR? z3Kvu4LVVCK!k8n^L;BrD0GsO-Wp1GbDNZ2v^ClsRw_0^ZKRV#_DKE70WS;kq9`m9! zvEr>$+0#8Zn#$%uk2p~#A(Lrzw&#CXk+9>wd^hdBX3X@C1<-`3Z>u5vPa zU0!Nh=e~*c`oqV_!ftv_x)-F{{@k1kPR{N8efakEMTc-{vrk9?v-4jio7W2hrk^$B zov$p`KIl43p?|U3uguw{RQ&zT43HbBFs$QG7MFP${DO+&A-!8YUEYxI_M($}eM0Sy zF;9}%>~Qt-%$&wsKiKae+AT^_bTTp)kn9(;QC%4lZ5lGLkiw=+zy3(a3ag7=7#fQx zCVhxH%@qJQO4wo-=%AI>?N#jtQNlls8&7#XBk(W>0x28kpZ&PuP(+>HJf5)OthDs; zsus*HNQ%jf_-UFUAFjk%AQwH72Fkbh~BSK?J9T9uz~P zDIxA3R9x#gQsKeK#LwUFs_%t_nV--9i74`J5!)-B91&<$q%4n;3epOekYs z?bj?nubvY*dye<9XQ8<*WhHSwJUBtxe${CC45O$=l3h5Tkbm!0FPd*}&V~FOPJXXh zN%~xt9yV?%b?pZdK2W9626~l`?F$C-J;|=t+%_%dZQZ_mI?Hw2{ExQ>S#(Qn-5bvk z*PhO90Y(}dco8IvTA_Ok`;6ZNzD@k9J?Tu25F|^zAtVQD)4;pq_@=~1nXHXxTw~jLwzp<|*~FwjAJt{q%nC=RI!MN%C2vf|wzU)T;}?0MY@0 zp?m1tB-=!bYl^I|zdrBlB|hw`w{OtqFmEbl(3bM117da6lqTcmR%BnnsyVJ^Ekp>NO2yj{w;O|{e+cl%}J2yVCF5P`UpAkeSQ1ZI_$I= zOLiOem9%>WPC;wd6s3pww)=BNPv7fO0w0;pMYmVGu&r{|)#AM07@2$zcbNHD+a0#({si;pbA6osCC{xd51@1Qmul4#_IO@3dAYoeU&QR?gVsd;fFsmn z{6`8Ll)G2ap`BT&`N|9kMpERG8QX!I*z*iLzs0om`M%#twWrgD4~JgD(ipWTBTy+C zUJdVa(+D|~{FTL29YMlBl{%IEt-Zg=l%u_o1no>Qo4aFx6cL&7PFyH=n*4|uULj#> z%jbsI60Ukt9^oy<;-6~VAEiVw7WLr*=P(7cYK@q1QKE<|hF&^c1g^d>qU zeZ%M5(B&&DBdZ*xQ+P)tWulBXh@TvA{a>6y@{rZtO0G|WY@^Ss!xhSRPicn-< z)Lh+3_r&G4N6?%h>UwQP>4AS9%w|6_i%VDp8fF2#iaoa79JlS;0ZG60s_0iVBye=( z-@40G-w;u>cSF1YvU84@s>gJG_nLo)>4~AXfNsxV3X7Oy-v{FLr!}y{=ecJWxVbq$ z3jgK7etp~sq7#gZr0jL?Mja=c&&P-(=A3^u8q+&n4w&kV4vE3(M((0}L-P#+j`%ht zIGqa{$7%861SJS^7SVQi8PkCNqyln$#4+=oSy{NjpxM=w;x9t$T z;TpOIbb@c0q1pz*XZH zK+~iG#6;rI?;c9Gk9HkS@UR|TC~S&Xf7ID_RsOywr(7K z`6x?0a6#rer?T+n5W5tQ6tqVA{SyyrDNdMw2gB{(hJB1Wp}^|QAJCtPxuHgqmZ1sAZ$?T(ysq{B>SlOqhGInJl+*u`Q}i2*1D&@3@Ml@#I4sWlHF17 zN!zFr-k^yv_`CjzQ4rgAI=SJ6F1k~bYB{tnXM*BpC;hpYxtCvB9BqM8!ia`wK)5W< zTy)RLiz+9YBC^=|v2TAuJ0+*RuVZt$UuD`Sg8r@(5-K|!ySDp!k1z@r2CwcDLkUr3 zA|=4OirutzxnNa}`ei&+VnIerMgRa+T zEujTsmy<6f=sm_m+fQhYJ8sih64-E*uZ#d<$5Qzv)3K2=t`Qs+1Y;L8Ju=z3*Tqlgzs3|P+Xq7E{BvYrwxD6aI20{ z4V;kpt3rDd=jhTF&*ZI;!}CXfAFy7X;^6+qJ-_K;{C9kH^&l6*e6~`}6>8qhrB9cJ z5FzUV@q^HX(QO=@Nb+#kOTi$|t&sbPlJ_&s4YNN5 z`AlfW7-$LBP4qoKpDcY3eF|#cfx7leO)M~)Xt{RH?cbymGytJGR6>U@z(5N!FJ-p; z=o8|0{RY!Fi9t=PgM%dzYCxA>mW+*WeCB=7KCMm`MF3OLv$J8e5Y!Vf8~RPzz-E;< znPrH6xLf~d8%bZ@OK9mbuj+wUDckF3OBdP-ysY4GOy%CYqIQ52z9QT1e(u+%=VHgd zTJ~dYrKh7DN&4P4%DU!fAQ@g5D!(5&HXi$)-u#*`r4bX4wlUqheL3FIE#1NU2-ZUi z$r_&qI`P_?EVnrif}Js;&!ERM*`>KZgOfDWkgcwu5LEtPLZ#YY6{!dKl`I2#pL-=? zVQ=`tKkZUnR=O;JCAs_q`&6>eeKljx`>Wm6VIf$%Q=~V)q5o9FE!2OxYJWKj3VL=FUwg0O`u(ey)f^c@>4jNFHvS;u|j+!-;2I z-f5+)du(h^YNq_iz!Z-ZnuM9+I!b7>YQ>dUG@Kk>YAHlBL^CR35u^Wa(#23g-QHf^ zis<#H`WUu=MLKo7XReLL#|RgXS_}mLDk>=G6XUuFq4)0qR+x@`5WN5lGkPAEqdelZ z=*xVC6(8G<8f~#~{jnS#(a<&PU2b^tj2Y4avr) zTUz>N6x~EB7xFhP>0TlBRpGPf={~Z3a~SX1UwgCW<8wyWrsrkL3-uo3x^e&6YX#sL zps4P=?i?SCf$?IgfCY#CaXTu&>G_Q5$3gaeikeb)eEs7c@Y9qxu)Dnu}+Rg;l zk4ifZ173K(F1m;((_dRZjJhATqZY>1+~~phn2bbZpMG6 zmQCU`HA@Y*2a5^rxeMmMnmA~a$7{bmZ+H$}J}mIUm`q9;a6gYyXe8oNO@^oH6%F%% zx{k3gS(@{-g#cB~oT!MRuGcSsW9w6qlE*jT*n8^LV4jd1*JiqYe^ zHaBx4jgQZDgxylpeNFDVwj|RZONRSp%pb#ct-?h@RDDT&@kP^E^rir)BvIk)|DHaVlZ6>E=y zIrj{U*KMLSq+Obsx4K6C%cHQGHDKN47hOJ6-v=I4pmuuRXp4z?{tw zL%Bo@KqNiz!*NAsYxLBrgIphbp*v^{G-qL6k-PI*g1}rrh}SndN4gNNivI%pX~d zza^78fTK~s^B^15tlmyLghCFRS1cnkCo8~z7uq}k1=Rat^LKEvB^@_kR%m8EP z77;oO3Yzjhs)POj=^HFN_~~n7_&6^sS`%Ih{ASlhSLy>{`6Ww)0BtVY(XOb)-ki@s z-V=oIQ3`Pz8yX&u4%GIyO=Wm-gr{IW?StDd3{O&uZ;3~Zdm})_sSlW%CWF$g{W3l< z=FfKKtqI3EmC2oJGLUi0e)Rl@z3ts9lP#OqwB~DO+t9fzbz)hmc>g}XZ|ZGwzZ_}T zHwV3p#i|^uh55vvCm^k@R!M6bzlBpy_kjEQBd>>sca}f2o~+2bE@8 zb2F_y$&-s<0JCAkmO<5`K_!5Z-$#MkFGs!ht)Y5(mZSv;H80sPu@9X7`-QnYz9nPv zX&Z>{rBcOJF#_u19M9UrlVNs5b<%DaWzD?7gtlgY`L95Q4@muDF-@Q86}q!?Wh>LD zQLg&O44U;RUfd(^?G@rTp8MvK1Hi8)fNg~6&S;k4Rv_4@aevu95I)W#S7hAv+>UfOiq8Fz?+JBK>;< zb7+Fa;20#jQPd0iB!}vDQi&>;kIKJAL!zT$0A3t3ivtZVfvFhroF*BLFMhU)IFujysd&y1MHNKbl&N@B5 zsTqMh-n%yA!L7~v!i#$o3k*Fzuh)HWD*-4^9Y!!`?d#-o z^pi(MmG>PSn?AT?=VE=I!J4PZ$M#*pE3RmGmBxP}ukL%^*UpDc$1GSv!IBug?T!p|3K0LjS#W?wy38IhsH!!T zOU8c!cdj}J4YVm#7v?DVK-p0`w^6tC{&$be#o}RA_$}=(_JJ1T*M3?MLV(G&Cl*^4 zvVZ-7(6x56aPFP5MB&)g9KX;4M}Oc14W^KUtUoziq6mgKuZGV}+S{&GXaI?2m>IiTfGd z#~fJd>O7%3qYI~%GMxsFlD3V*%}JSdRy3Wb4PiJ4vuqo%l)UAq^7pIGaarDW(#t;g zCVOo=>&@0FD9C4nus=?Fs%-B}8{k*5CP5SFjZ3iZtz*D2Pouz=nS?i-dQDb_9TI%;qJHL@Brc;YT@|r*hhGt`cE_XAnR5wL zD9CpxhtS)-MH)-eIR>kz2Mh_D%{usWm(>=Ug&s<{ZDd*g$w^lZM}okHuUw(f29teM zGdVPEA8o+Ez+ly=#_2128Hf0GCYuI?9b z4NfzJ+WaOt3gdX%V-}}M>9);~>frvTmpO8(#?m@rNkySOYTi=&-wCd^E8{~_3aU&o z`yDnv+k>iXFTcvgmrj046u$Ed@z6RgA0~0nBH5HO^#vj@I^t7r6-fZOa%O&4vZx@k zFp7VOb$a4Qul(Hq_#;@rZ~WmNjl@i@SdJ@Z#&4y|EMN`&QE)8(7szogjj|JfOqN7M z`4#b)b59?n~F?H3?DZENM-qPeB#5}R{mZ!@^}v8D3!7L;|Bo1d<7`P{Z<6}lqCNQeTlZc<5wc(u(q#d5 z{j%+ZAA}m6EiZ24OzcDWURgCVVOp*q)Ss9cSlhUD^*<3EOf-`D9J@Q4o|d_CJEZga z7Jeq?ANF-Qt3SAqRD=n`pzGD8gTAp$QcaHqigROQ8@Nm3<{$LqCGB;%zx}ZnijR}Dm?*Vgae-ang(EGgtjW_Vulm3 zijem~VVn>nhuP<5KmH$M#$V2wj=A|o+>*G$UpmqJVO(rT9S4&EGe1EKU!S0ud@y-z z&zA3+!vHEbK2`Yi_W@Uc8i)|I?WW};)Dlf(s#-ZF@vjE)PjmrdRIJK zIrz;-G>0k1;Nfut*1H z3**JgreWoKK~pSE*X8R;>tf~5J@DWoWN|OB2w=+T{OWwlF!yeIeCIVeWD_FDp49JF z#rEtU|GN1SJ}4d=G1E7Fh{5^$Dl{%3Vsf3S>PJ5t=imWf+JUu(ex#9-Q%Durl-Qgl zI$@hAch^gWOb~o|4dSpN9_3Lrt72c=4$b?YmWF-6Yu+=MZrM9jU}L&NgF;v*#NM~f z2jW$zLO8w<^!jN{QW;AkT%T3EH8A|odjJwE^kk9AzR#xZ!I8Q~!sZ$H$`#Ya93=deQ`9)jw6T z^KHYH@<|4=^Cq%|4If17aAg`*%to-6OP~gPf}!j2D`G zG_Jl2)0_}-C*zQG=+IHk>WlN3#n(*;$;h5|LWaJ>L_16F$NONdPwb9*y1nl1q(GMU?v-<|_E1zN5A0M#>-@ zN8!z47-f;dnx>n@68Og#Lm<2mP+AJuHQxmMqe4&T2)xi zmZ)~R*`ADEvBGc*RZQGif?3tR3$((H4#$-;e z$)0R$vh60jzWsiG>(6Po*V$`7>%OlG!g#(|U?*rrT3mm(t-ZL(3%3iRjCLNOCcVWj53-DB&s? zXsIezIOViIW|Bh*btn70k*`AQGm^KWX3>>wgQ9-h6k6;KuK5xCM45j^W=#+)_aBvM zhG%Q%el>ff{s)}FA~r~4F#YGngrDW_fn>VB(|E51)IO+PS;zM3uk?=|>_zSEJoH=q zgr+wAL>JZ$sXyNfPaywm1}mkQlsJFhQ>xfNn;Z;Df@Af6Qx&9LO55~(!Me%nup^2w zh=R7^qc+-I@emCZTC`M6G{|yAmXG9Mf6t*2pFeVnsbs2i12r*kSz!5NhA&pNcTb|_ z!=$#UYs#}UQguv=2Za_1%Q}}VclZLGVfVT;{gq8VICD+ftci2LUD6zkEo3;%)@b#& zPAWGU+{o_hJBWWc96_sZSP$JZfeM9g!#rw=?>~Rs?!Y-1g}~!};%@U^bmOSkw=vtj zm9$Q;L`+O8^B0vnB*rMTUosOv7E_gZ5Q_yy!qZ-oJ&zB=W^j#@!jK4FoB9~G=c(JH z+kfY0u!EAlq%Q*e*MDo*(Fs*#qkZ&LtK!eN5|cF)$Pkw$82pE`=^A{-(XwylO@U_O z;r&m#U=j|p+A=pM6n%CK>jZPENQ3V0iW#3N1|~DUJJ70vogZPIi1kyrgF=5D-jw2Y zDzQ%F>P(wi@T7DcBT)j_$Q$aqlUkaDf!7WXMw5w#);iyFDJ@~I+R98GodpF}I#w}A zI+X>jyE$n5tAnXB`@gSetM80e4t+JBJ`IX7+7~q#Z@<4}X1?uX1N6YCy?=d-vV~H_ zvky=blO#j+5_lL7sjMxLLaThu7dhAi-_+p5v>u!_9Rxk9*{;D}(Vc-#?^Ay7#_dd8 zbSh;=EM;SQDlHNVfm<%n_F(5WrdDb3#9YWtc!OY#U8)Y_OOTDkR*!l8cPN*A8xQv# znN#r1qQNDhAS2-u@H+T1+WA5Pq!gKCMVE>?*)H6Vhz`pus_RbF0d++_ck=ugiAPbb zE&cpaG#7oDiSxB3r!%h#$+a%bEI8+(ut-22q__+#Pqm6^Kj|JT=M~H{xOpz45)_kQ zUP$Xn9(T~EP5|bK3Jc)Q%DV6mS{6#DkQW}M_;Mf@%GzmG0Di!z; z^pP0*KCUF(Yje_J5MfAk(j0@ODsvVNCsckDV4ctqB%Tcqq;-NE7J9;0D)W7x9lDzyhOsnzpNO1JbzoAL>9kDlz%4&nEv9x2@*BE z!TZ!824A1RZ@xq!I6gN%+v?gOKB*_09{O>yY{+xW?(h<1*G`nc(q&~tq)nteJtR+W zYBsCT;Hj*Lv@$A5Z$}c@ja*@TzlhwPZ%Noyj8fpx9#2I~acV9f7Sma6ITiN~cH9R6zur zb6h6CzzmsO&4z5%pM)qm&?~e!7mCK}L0GnY?&Wf!%f}f26OJ`yh$>cGC!=B+w|w=a0CCQid(<_OF#O#1^*>+^K-+G)2HU`E=JrlPG$( z*!v^YbhjP`7O!;{WVF?pkV4+RR5FEU$xJm$4Zl6zt3ajvPmGZW*)uR2NZI2{UmDjB zIi~@z$gvGCQfs2V$yzP^rj}Av8PoIjX(!iuD1lXK*)-*UoYwpy@`xq=D}G;jgUv|3 zGKymvtk6^|={RJ@bU@Xh_`8xmt7wTibVm%y@8u({u##|*uvI`W6ox%L5RBpFz3*M9 z8xJaQ5B}-GDHTg<1E^d)m{3_z$829~EJFB?i|Y3Sk;-)}jljP1_GTLdPdKls}6|+ou7C=69&EC z1DL#E3VOZG47n|u{p9gS!X??u<}(bB!OA=DtepEHsztYXuVqsx(aEN=R@XsJY5G?_ zyKpm+J8%x-H%#9j9_ofdil9-NX2a|zOUf})Tb3`KKNA@w{tX=TZaV6dx0(}=V=2_` zY9=ZIEujo%+V`g^H-HJF|NTBP`8#cF4`7@!tB)+rNSq;u@`thq95BmmT~_alWj9&C z_F-nW$yTTK}`d9x@%{54y5x;58S(X|;c1T2_W6+_MM?3Q{>Sfe)pXA>Y zU+!!k*aOaxF)Xs3Dy8`^TpUUt>fFzt+U=FAWl6lF!ODYU-|j>b+*rKz2ZiTJ4n~V} zRsy?*BD(4Fm_b*{^{5TbJuq8`r;9&o&X!s9HkwQW0660kW~V)f?Kv45>?*#JY6SL- z)^!BgM|+wP_++4)u-H30j%aRXbl-&RrY606!dcwyI!i&2NLw~I(gdO7f~4m~!H#;^MVZ$eUl8&nH>Gc;Lm4K7F3x@~aicC7gsvtPGz8I(3$q^)SuyM##^X^{T6OKw9BBD-LiHf5~o)UqM9=xI4X3 zRAJWycqSSENW#)fR6P@f3aju8w7>cV0&{(zQvjbD9F3TT(a1n@xXj&L>Ud}GrYi3D z+!^*(IVGEw+pfAzU!46J>D7MUc`8$>m}v*t@M@&nm`MLo)FCAKB|1wscozIl+}Lod z4#TaK?BA#|)g1W_|4_R%P{Y;Trz4&xp_GNu@JSBl6QWF&q83dZ*3CFB_t8uyhHDee zbIz2bn}xfX7Gm5H*^spC%_s?PU3`xq3p9|f&@i{+$+2d0Sn0LZ4$+YvH0j`Kj`)A@ zK#4%9;vzBQCF$UR&*KVU156+aka=>e@|(N3ASg)+bXhzgl2dlVGfZ@HJWtA)Q(cbD zk*`XrC<%=XsjEgTtxrUQIB-t^86U5Y60Q%Aka|7qbiGy+mqDl>mrf^X8@aaRkY@bd zoVonQBYs_{IF^>6X{gKXNHxv~TDPRKKUhbpn z^;F%`?_tdAFE5|68rHLak-gYj+}FOXYyqx359sHFO`D9 zzOSXO?-#pK=EPC#?!#0cbP2+C}O(F%4)# z9o68y%`PNnJyf#75^rTPD4hF!i=l(e0CqJva8)RAH?4@WCh z!5~Jk`1lS@ZwZaxIQ!7j6Qt31=C6bs9wjBzp&0n7*foA$(i7^+@G`-hdmPNQDE@XB zk0cIyl(OO`%8Z6YzzGJ!24?q_4<1GaW+~2W=i`Rpx(!vQ_nLy1h`A{SRK>Zwkz<1f zRj1{br(d>y>4K)F8Kr|9<{o%f2%M+BKJDL3-lAA#H+t2}a-GEVEtpoyVHi!P4=YUH zPAnEQmq4sz8=Eck%ooFF^7UpDCcvqFQwLd#DA+R)Hkzx<>+2_=@*B_m;C%KN^R?7I zuj({Ob*IrjXu89EeHR_TPjs?B!i{N}`81HB*}I96p2`(eQx$k7=jKM^iQr!kriBg0pvmr_ z1|h^ayNaNSu@jN-Ipw~(H8!;TsC#8|{uBMqZ{UmHAtvP`q73WPI>}-=dsCS`-N23X zV+8Zbr5ii_&HLdiK5F3BVeVL)5he9ydoGpVw4v5|S%r}9zV@rZVYux75t5( zDP89m6-Dnn#3KJmq&g44J5@-`i8=)rKv(B^JKT5IQ{$w7y#25_l~5QfFo*e0mNLt# z>`Cg2K&Ddq#XQ4-?mk&AVFK-loh z1`o2W5rA6#NR!1hhQvG|btr*-%&(rg!ehQ`jnVSI4Z?(I#~QdI-@y*`{Ya4^y9y0# zv+iSzd1avx-*t0ELi`3J58oslBiqO{A9sgX*}cYJ0N`hmO}VvtQi;!dne zW98CNw2iFtV9tpoz(A7euQHWa1oIcX^>xq%{Ex^mSiS;>9m`!KT;V+ScaSAfi<~u> zwA1}7oNuct4T=HPjg)fAw`e04=o`4`COsab*yty-GUTInZ8Jrk+&U_v=m&-+yaiS* z)fT%6P6XB-p*g~5Z<>nEN2zPSw+W>_M)=#PqN^Y>b>d$JI2j@6Ckv;x^nkWBVYXED zq%D3h>@~_*`FS%NE--`c%hjXsVV{R$Zl`Yf*QXNhBe1Ka@AO0$z+OEQ=mJzp0Hobh z3xAzKeX>b+c+*$MGLD_8-&$3?$M9dilr|^TN_(!XiK;xK)l##IJDj0>kK)`=Yg~Mo z#Z0~{_(4FUzW#BLaPcW~j5Upao!NREl;5{7yj8F83ec_o&Qt5bv=*6g z^ndrhB}MbBYu!lVEc*#9mK?^0^ngJN8E8uL=6hj^5j$oE8Ec$z(+w2`!EC_&dMmqMz=+C(7RDg z3higEHZm$kSX@=(t_!*Kmuxg)Zhg$uK{s({BK757coyx3@zibm=$QaALVzk{kkN&2 z;M|G#d^vp$99^u=ltBGGV>Ey5sa2_6sJJj29TQtNErA&Rd4N4_176@j{k$}Sv?8>4* zBfR5My&=WK1!P7L4xNeV*=KUKMI#=e)_U`>!@EbVaF}fC$>U4{Zj+8okq~3VoB)>{lx6?JGtLG6rDjqFFF9nEY%nemz`NicZuBjUmc(|9Xr z4xTdww9|diNL{>ZYfGjRh*d*V{5$(4JR#4^|3&HmE=2f(F@gKxz5@9zH#zVbj*m7I zi7%+SJ9WQ}X)5h+)U}#*I$R4`khGQGN$IZ;CfwgYHad*SLwMZm+L2nCzTWB)CA( z5gQ^mPCCk@)QZ|NQGFk&#it`o?#)j5AF7x`Pl4e~MXB`k&kCJ#VWBqif0tC;wrbu+ z-^nDOds5JiYZlXS*q%cPP2dT3v!Gu66}BD`;2NfP77K60o^~#6spx;i$q!g>AI9L< zFG)ezh)6z4o`DT!ZzQk4usk?C6pzpjp)~OC)qGT9D{z~P(?vfL`cN{7!kxtEX;70*0?QLx{qGHp*;0lI_5}21`S^w6#15;%@QIH2=5PAM z5WOze)bAe&^4l?MS!7D-NJBT1o@kt0fFrjRTpeDWPaeV1qgnh&`|0KziKoB)#Hw3% z>;TxC3zjY3y7`v2;n|mL-rZm76|9w-xPw5eHOrz}7OWcUw7u5Y;cuk)d8*y@6Ft-$yaSsX~>}-91bT-jn(e6 zYRo=C&~hNHwBBHma{Q=8U0o8`FUQ{MUDbmw3%;3^HmO#>MvP{$n)xl)ONj?s9#VB! z?bT$`=RvlQHcXpF^aYC)$nNf&VZ2)*zgpn48UA#>6dAQq;M~s-OC5HaicunTUwXC? zStfW?0Jz@5buf*?2&%6Laa>D}>?iiLWDZ7q_F47^ExxiNnUe7)7#sm=;?==Flefy{ zY^~70{`?#xFGuMYbM85v>V^?bchr-9(JiVu0cvDoT=ff}r@N zFH4*QS;Kx{Q*_1y&EtTKKA2-c8YI;e+R&M5$Tr=>p@4}#P+*)O`z<@V_h(C)28;}n zuA<_IPB&}OePw~m%5ow!1I)`#)Ot)$gjqj?y4GTQahI`4R zBlY9xL{#<>&ki}3aY3KsC=T<)3-{)fhkp8r(zIWAHe|VKbL;T*Ai$s2k(I|L)}egq zoWpJ%V>MLVeMLV{dWs;7I7eMk4gHgN%eE)^;h>Y=gl~O~V+S3JdDxBj3u_e9!5CKv z5xC!E6a2~WkhqtGj?=+EBtO!l1u8JiDat;ZLbV(aq#Nn_MmtLx5jwr%n0+xfF7YGf z=3Kg#YoL^qGD`~&)_$o}$~L*$>fs5b?akB3jS{&^^q+TZst43&^To~P7JJ21!UY1+ z!jMdS>Z-eOBTd-f7=D^FBpD+vb4Jbu?#olfR;e?fs#5wwsX@wUVIS6>E!c*8 zRGSC0$fs)1D7r`UE-DctzSA6m4CmRGik@Y&Nu9>$fY-GuASUvq2t?sga2CbdxukDC z>>*=A*%qX|ktZ%;x;N}$R`pH+B$*NbK#cDh8FjS%ng`tB#OYI8I#N}w`SnZk?a?O` zfmL!xnl;}^dQE-RZ0L&M7Mm}>|45H)6Z;n#($ZHG(KBw~Pg%R=(QzZXRuwfWxi;CD z%@{a@xQNM2)a56Y|AHfFV&&H6Aw?#eStlIKd*C4y6*b=7=SFYpJ*d$Ut9POL5&t;^FVtK+jw&kVtbJ`2HTnhF zk^v^C*r)5u>tz$_e55PYF}FhaVi*sL45kDMXUN>ne0b^Jo-)Ceh1eK&F%BFhV2M9@ zD%O-!B(T}<9@;9o?mOJTfqojmP(9Te%4}+vK;`e3vz(I+3UUPqFw12bm8Q&$<5{ge zuN|qt+(x#RLc>PZ0RiWm!Vt3-^lyko>>)-5T-7?`r9b3^fyRTz#gqQ1Ju{OQP4a&a z9)_7apyHv0wyBTd4kKLCnH`|S;Z=7cBUsqpl~xtei@qKkhxJSLv%Or8nHpyD@OYZn zpb(mr-n-L&e(8OV zMzxA?#x(BcaO>}+h~L=Ga|NVp8uY%AwX!br0y_B zS(AhM6sv3vzyK{hi{b|XYWJb?f7=aZfwZdEw$0oPNmp&o5hS6X9m&2FMqyGlQgt`N zbN2JenqbL{zzsM#YsMkPhEWb`9HP9W2DyO}{bOpWD8>7t|L|nN8VSAhCsB;cjd2Q+ z9c$07msx7(u6R-s!j7A2JA|VyZQ+SNm+qwxZ&b~xrUkov_^1Sn>cIKm_lC2Dqr|+w zmCid@2CSH?@rZKI?hvrXDR$fA(4O{f2BX-i?fi^Q1jY~$G>$noABy(!UcSW(%+2>_ zffiY9^_f{nP?w5DfEr+G^`l}swwE|sf(E7}>8_s%IaHq*Ao}oF0lxOr{ns1T6sedM z@L_vlmZudUO}re_Q5WPOd?h|lNIn9}#-@!I7z4mNd9em_g3A~a%AQM#%@AkQivC=& z;@m#WGXu3rSI4AF^%y5=CQa|)^nDWD0ZM%57UiGc^@xr*YBFBTgy|%Nn2tj^*P3h( zDJb>+PQ&?ZQVE(I3a+5CwICVku7B%NKv_@J)|lTaizjz^S#u&y zANJLgLb-pwqlG@Ev4lRXq0OfJY~Zo<(uNAkW;5C9?hUChU2D-w6^c^hMWvb|2y*zT zKGmW#QEYkQTaxWs_e$s@HyE2uJw)>2CMbf}By#S9{FmHVtT-C9mF4uj8j(Spv$T{- zaI^ui)-!EoQNn`EH6ntaweH%@7+NL;^!=wK*(!s=Q00Ul@Hn+A{9fcAhI-$ND>iD^3 z$|P2BGZ;Rr%g#L^@1fi<@f#1B0P8uzkJ2G(4>X_>{5D0b31xKWEoLG^3^oqM=v5}m zj)MkJW+u7w6g`=ecqWi4$5Ew5v#^uR9UtUqG-HwW%HRaf$GmFe@O z%+St?KVgh9>U+*Xi7C0{~ z4yvc2!)fY|q;`X*EvKUi#Ib+09L@E++&q$*_*ZW@2^7V!n;2`0owg*!ZvLpzes>!} zIww@n#x-ocJbFsPPGE9KE!ErSav}+NKmHj?Rqjk)D!u|?WHuLrlYPlmu#TE*181#& z8EKsZdPx12Oa<1t+Q+Yr(Aq*hI1R37dt5zHtOP$33TIr=1Qbc)lhLA7_V}7(mq+BG zfGhbwg!gZ-6G5NZ-y6%g!;$Z9xirsVf@Cp|VH@nSpLx$noi-9w7GlF=?2oYFi647e zdRuXg

`deAtP?Y`S5y`fJim%>1QQYj^}hg$x)bH~c;-A=ceFI9cM6aV-4em#a=uisU7>ZuwkBFt#HPDu?lq2H}d3mGQ4(*6KXm{7k6 z*nduRRZbVK2r6Ix3t%?g#5kWInmoC#+VbjL(pr7n=w)u^N|rz^%d*Qg+1zP7lmGg# zMn$wLJAa03TOT#*UWzI@m}^~8B)5<9FZ*1E`?|R#fNzy^bi1cKe~)}frH=d)gYgZm z)qo1--|t&8>J|A#sbhn(4N(w#Jt4`xJ4V~3`Tb&vb%E#-CuszDQAo-eF@bHIU9{ST z*&%Jbb6sprOvur!7%-Osss}#ov+mogxoZwV>blL$y3W zXDI&>m3@?Xk$P`nTG0|WcLFyg_?is-gnOALF2_Z_&X*gX1xfw14Kqc?5pO2TX6=5U zD~t*17n>;JTM0Q z<_a@iLV>c}cv}qmJ9*90P7!%C;?XD_f+pa2CHN*Xp0IVNm}wS!vOW%}B$fO!CHG^Y z@@UEdF}#h|vVZy;LK^1z!j4du?#h?Yr%2 zfL=7Dr>_{{gw`v^^H}&#%|qyFPLrf)$3bqno$Zxx59nn0EB?VU<;<@3c|nVIxU@xZ zM=lRYQ5O5eAKTUUNa>pWj5AO}^|Xi7An3HMGh>p5=XFpdQna{t(Jq z&l{SgYDgaB3X3ze13JxYYH9j;uqhuQ{8Ri`G$SfOm?_GMuiF}V?oSk7Q+Blvmf`DH z0^W?4U_fjpU^4jdafoWNtQ~B6 z2jq!i7GWx=54S1bHn?E@f|>DvopfiaZ5MEw4@XJTy9@;+8Xht3&+tyc-s!1}@HXvjm zHMENp+GV#;A?3tfA`v4YNdDUkyFLNQEPSsV9|0w}BbLSRr_FHz{~$Tcg@N)!B-~|HkR= z@3&QYcR(gmdDgVr>^OjPXM;$}o&}87M+v;^K=$rdi`BOEpwYK(gzW@7Ga^kQ(Cm$> zST;)%S^T?5`ATTq)M2cA5j~|SJhI?B4{7dxKP7^)SeKIx5kYj98Jdug9ONa43Py?z z4;f#Bhkr(4rV9x%Z+^|_!=A+9*vb{g6+>UyIi@cFK$*1~$lXndw{(ZbwF@u7 z2xGS2(513l@5?vBA;PeS>GWj$_|h9D&TINq1li*bW7($WeHaBvcIKJ{7 zx5fFubP`PW@M5)336VoT6aQIF*Xkk4;TE9zY*H2N7i^1Vy9W9gQ7-RX?cJCy^-w`(Ys+ng(}aLt*?sq zf-F6ILlc|G?4%5B7EK)VEbv0A^t5F>y`LL7Ckh^9P@M$|UIpAKEuzp{lRWax{3f{F z@OR%D|03isOgRe@yY@{T<or%w{Ejn>^h?gL2IIu&3TeK_DYS<$E z3=1ArFGw!xE=sw#SSk{;i+Zy7=AA)wrECt4!+3DEvJ?~0DW*}&U!RAjN6xBc(hf1v zk06*s*XAqu54Z4_VB4K`=OcbG3{7(Qczcisptm-`vH4-$50ASOzpNMIsxIJngiIjG z9ukPf{)OR+>__I|*xxVb5(N4E%5og7cMXLq^W_3{qGDh5JwF6N1tls#&8N1vxNI$) z{bA`GS_quD0M)QX8aK=@g}gC;40R@26(-A1v4t{PVOXIk;nX^R>HdQDj?#7b^WQNU z_uHefRVDSk-|os?W^wv>Iwj!&P$}g+7^{=LG3hy4=J;`|n=WYCI-Z$nAX7$^NENk?PkXKU$ZV?SrbiRH!d!k6175Y? zBV&Kg538W?i0;0z1%k!GdF*%C-d~9vQACG+(z_|se!t@P@awEd*w=kbPjq4PZ9q20 zQ(<>n?wUV;6jm_Ef@I9@Vlqf&Ss{zUvx)D(Av|xb$Tz11uAn~(cUCoJUYTU}Au;qN zTyxwMeu~2MOP`1F_+NJ^ej*05z};@2ajvYm(@AR#Vg&2&c|a5VTRvgwYDMKtvXl`x zs^z&|fKc`z9xgzDsL+x#V4kDgk)#!(%m-Hau=wJbX!`Je1Xf!13*oU&+>gf z9^B5#iCvh%-~$erm|Noxp-j7sWBCUq-_2KDMWC5*sszhue?NlQQIyt6rqVB*iGSlp@O2? z+~s#}1b3mIuU?60p|?@9b+B-#9rY#ak$bu^>^EtViO|SVDSSk{rd8Ti0^Sq+ka~GIET~S=CF$7;ro1>{^Nlr$9RI`^WE{ygOD9G z(?!M?{c60~tEC(jmbn(`0xRy<4Adx&Jcjg$+Fn%b=)PDOzru}Xxr3p`60Bd}8`l1; z2uq<`)r8+)J(eg&iQtJ8M!L61F^(^_Vdi+Fp)!v^yMS7xR72Bpoz8dnWc3c^I9WVV zO*mULj8MUg8OH5|zfvW8(Tlm2c)HJXjFq5|*V*jtj8>#?qv5QZRi~|UCSc#jIj3w0 z46&iC#ygSY3A?vSs$%Y>o=I^-;CJL82w|Zn!eq*}Xh)C?C6*~8o6JQT(s~wq zj@3^-*VzS+l(ofV?ocj>5 z=T2ZH1fh?beYA}fx(eh{e4hUp-LczRZ=-ar_>@SjVTA^G6Pq4}Hv0CXy8VIyP=B%} zr;y7B1+$8p>sh3RYhl^*tr3V0uESIn30#Vg$qSKIGCN-*NNHZ$x%Tw+QHD5zJK4B3 zaO13Bag3yJEXqZ}>Bbg`=vxjTnJ(*fW(zuZQK3hLC`}YitCrznz*H7qeYMLCUS*S( zAtro7bv`p&dQ{}eYN;(jba8a9M=Bnq7pHQ_na7V9%dlr%XW@M(=FJ;;)Ah6i{!b-& z51uZ0xV>-FXAloVZ`_4H%ka&i{&dOuJID$oBsx2-f$-wS#n2$({7+>f;4#YrEr>D6 zD1;kM=zlJY;mZ(nyI^~09G#PXWwXY+w(X&d{FL|C>1qchr}A(+f!YQSG}x`)(`y*| zIp*Rr2v%cmN}O_ZFjw;XmzZdRsJ#5V9AfoFBuJ>@7x$%2dtLL7+B>g0G}S`Ol=R=n zzAkD7Y7deF4(Bh`5Ob=f)%p8^Wury#gvem%+_Znb0{sWye)cDvBK^Pl zWY^W(iO6RN6tb-%^av_f`)LNF(?#?|aVjcO4*S`yS=AI|bMNTqfF9I~ z!;y0d%4}kk_n{_9IJBMCk`~SSIEGgw$ziu6xjsy~Nv0X%yCHXDC#j#9t;OHdm$wqotIuQlY+WuZ`WaKVDzM|?N0?S zjQ96CBC2J?y+0)>Xi$Fj>Q<1d#Kr`w!3GmH+gu!F2gAz=Dc5=TAzhjw9f`?JyZGJmy zj%wC}1R@x}5DQo_S^N~AcNKceiN{KN*Ky-ShO)7~ARqe6ftZ`nB%Bs7b$VNWxL?IN z*=w~+Ma|e!h|%3zkAdd!sj=o>`2A8})E#P>Q+@Cgf(j9rO898AMK>zBXu|Ij$@r*H#`;}6rUCgs^lf}8WQf%lu- zkK}UoiWQHeMO{wKalz+DISG?E{P zW&0)!P`1dZjqYyDw&`@eH!QJG%V~*N?{2FKB}-TnQU-Mo-b^XiJDFpiHp{1_0%(G3y z!m|LWc~fdkPld+0T$%2Z0{``J*D$b#EOq8UWs`K>P3eT$S4NjDMWgQRekkaukY#}HeN<*hxDzH9lN4)c@^|UtZwF}5 zkTp990VhO{!|2+yjC#R(Vl{7UIWYH8*row!ODty!LL6{mM}SLdDmNan>@Eq95MNw#}UTKaFWtVLs2xJ8J?E zK>6664{(z3{gMPr>mH_c`0qbW)kHqGmIs#%Bb0GmQPg+nMPXMJ1w?Qz$eOE?bD#kp z?4D_i-&*y>%wNd1dd&?Z!NP;A9n+sho=bnTYbrnsu2=GzR7C~IY{?-a_Os9sgr9UA69PC={VpFqxl z&uLcd{g}U$V;)T*1=lC&eDq`C(H<2{);Y|DOu#kyYS-d?+SDUCJy)gr2?k{l!22W( z`jAmd6is?KY__?5s6F4mDP-{2T3z%dEc6(OAs&t$_OkDM%$u>cS5~keFrWME3v7KQ zYaQkO?v9w7w3~?E4c}xdI1Qf0J6CXBX)t_rQqm=jRsP^kM|i~go>k6u_I zyyo-rZqWtT^?Xlk)ai%w++Jj$I<&xAjW>ygkA1<5kHsNsqR~QYG37MnzA+-Ek`{W? zdx)Qkf;88sFV7Jw+p|(U`{{ec8JR)6Ecwfo;Dy9j`Le*5mEQ&(lo#9E7%`4EOJJ%_ zV434Z2Y{IX__x(b*?6mc1jvyjm*kRA z1F7B62J6L1$;}~cI1n!TTNjvx!fP_>s6`?SZLg|%gZ=XFh-61u>()GVg6F=E3?7FU zk+JK``48|=eiXMmxGvMKZA1Xm_U2FVbe*4mYN<-*M4#%3?&H)s%LXO)4X}XU!dQLQp73=y?y@4eiP09JTdy_`{C2+ z!sISI!^cs+Mq8jgN>W?$!Z+t8s*er2UF)AZPy@WI6{4wRoa& zJkF@uC-SX3Fllw8wAq&7&G!3WzFTA z@v_U}WmUkCy^;OHiZyG+fMQW_z<-o`Ta)qcxok!;PfrxPbVAH87hcIe!bn(`*{mni zk_$x<-ztae39gG`mkniUlhkDpoUy6b>FHym3lPH+e2YmGC8NLOlQGb6_IOya{_72S zqWRQ0S3J!=%E0hRfLMAvlCe4-SX-9J8>^1zjf*Qc6ovo7L|@6*5&SWBZ%#fUl=$w~ z-J029uT%)Nqyui?3E9#{$Yu{KXCHMb!z27&cS(`T_P6=Ap|?vdii557s#fk6#3|E4$55BK_MI`AxKJN^X`-D)b!qgx+?__8`XandwG-0k~ zGq?f)VNEE$1ridH0cn0STP#z|bI|7d~I6!4rPdDF6bTF+K z?QE^rz@i04$dd0geqqd)@39Z3I-g8uQjIV$TsEmhC%bC4!{2@ z4{2@bpSA49^2n*dReX+$8SL>I+)$1dzOx&w@F_Z*2 zTqnuI?+;yCpWCp$zT{DbTVFW`Bx$S>uxpWLNyk~(+j0jbqinTp&0PyGZ6`UBbSa~~ z>?}Lxl5-gQx+jH6kEq5bUp)p=2>!VO&{v{Z6Qtkg9_J+;P{SEy8E^cesZk*t?TA3Q zKR({Oz(MYTia2oUTiB9GO!s8NlcBbIRtk?7O#>JyM#JO~3~UFUIao9bibz=8*7rFv z7VPuM#AH*dAE6zg0YPNIXPzQB%ujToD4lacw%V=PS+>|B+R|jwYa7S>$)@|`&)u)5 zj+9D=Sje?sY?LMEF)X?$pw9N(GyDo$;?sGLH3H|VryIAL zkCE#J+n4ezzjyNkf;FS+zsx)*VP9o^NFB+w@QIL!;EAF-I$m#sKXHiiflML`&~ZV% zg-6r*UlQ!1iv|TzWjWzZ!h8{ojcGup*m9o-M#yYf^NNaJq%keNDJNr6)BA8}RKKz+ zvHM9h`MaEsagvaQInn;B^P_X1!7{L@c59rI*~5Yt~dT*M1}vQVndt;)TD#9`A+&?ME->^`A2 zbc6-{XJCDO)NuchiT+!$Nkf~v_Sd`96`(~3r`B%?VDbr>z}UNkH8>o0n5<3M&7go3 zzn@c^#}uK&=~y+J($Xad0z_@@i7C~!C{U3`LcpG27bJ%kix#|djSb@rEtH4|It#z= zzVQ>Fo)oA^2du~F#*c!-WhI@qob<_vN8V%uQ+M0=TLOu~hJO1S@!L<=V?WMq?n_-6 z1s)6b;zlU$&Yr4T!wQ+S5*5^B=RV3u;-UGtsUvnAAa~`2S2^hr5+X~!4!*~asD3|> zKZ%?BK_)m<8)j}H*(lysf$ZKmEZm_+DS$Ie4#lCMyk&Wk_-v)L?&;u8URxi}S0)w&qiR3;LI zkf%XP9s=qKXztV!iuj@RxsN7)1`H49b0*}E2u26+9kP~PK& z3vs0DIRd7M<$4mXebiFhc|cxIw?R1bW*_&@^U1dbEu_=6t+jdjD8j1pE#2L(Pi9m4 zh8LU2V=M1lL9D;Nlw34ri6I0|5J?AX{3$Z|V*}i!@reY;b@218!aM=iJNn<`Liyw> zQ))ECa9T!-Ho&}bMhlhv06);l);vBk#N*r#EpDff2o|<*y_K_<*JDA9to>;>u51Ea zh_J03gJQxd2LbkphT`G-)zQ2n6=(z-a>N!(Mr39m_+`_Ae7wl*x1p2D+ZCHJMwUyr zuX^4G79tNpwraN`#;CSU}1qG z8p_Wl?APf9$zXB;8v0r%<%ZybZmk}K{MSJC9&-N%X;FSEf!OyZq+4FG2e9J zF{V@$(j~MFB=Q?w59td1_Nt1jqdaS6vqA?NpVS2r1k4nv^?3~M^Duz?Y@iZPM0rFo zDW_xI0hz0X-v0YXhg<UIu7L&a6=%NW`H z)3;tUnAx~~j3`;~|ug7*#%N$OSKQVEdEJ(mp>w~yM zOrQMXr= zZBp~xZ5bo`fuhkOD?p*fq|r7c}^yT?*{ovfj$ zxyQj``h9nDHP!CZVR~hW3zk1+~Sf|s5J#Vh|NbhiA|6m`g5R$hTl)>2JHU$tpvhP zZ_$`$+saZEKAS&AS!D?4be)x@U2f9D=D%#e6gk~fg>{UZ*vKd5gr-v#z6))=%p5g5 zgEC4d%fTUnc&Eu!NC5uca7VCz>4;O&`Hb&ONYUJOL2-W>L=QqY*3cs@j`s|N;H%!+ zCLJXo*fb2^GLI0!Puh}l{E>0VW*9o&^U%rS7Qfy1u8 zzzgzFQndcmIXK{EtA5ZFEj(HkYEP&|nNLcVg7$wLU4vVleIGw3+c^2;T3ohmEiBv1 z%WipTaoMhAEL+Q4URbUtoA2HG2b^==*H2%4u($#jbVj-rE(*RSZlAVKur0_H$WX8T zX4f;R3`J%g5{ds&!@jgJVI$cc**g> z2*|V>cjsT+{hRt&&ve`6d0HFe9OE@qD1hws=}!149Su@z{_Gpk(_hFwW_jqmbqhfn zy03?DD);^KWPd@#Q2M>Y!6Lwoz(@JLZ-{+BItqe^MnDonoWwvaJ}`Quwn$ys`*_yC z5L&?O7fU+h21hX(GBw;4=CZq9VLN%g=BA2HqL9rrJhv(TKpZe-t%mqcfiM^iHaz?5 z*Qws79SCXW?Vx&)rLud?@IQY(y4tyaC9Nd()eiNal{UQ2`R}$a=UfLnt^ZlT(&Fa( zuRP?C>eq@6Pb6BCR!s;e+X-5sIG9&NvO;32%X&pw23E;;Fx%Ze6oeSKik|Q zPq`;JO+4;%#%?aO5x=8XVVAsTiwOBbPOi^ACQZbW=V$_9K?BJoHnP=vQu9_@n|rb2 zlr0@t#pQ1)?u}o1Dm?V}s1aKAV;+6gfh{cj#7_Z0#)rOqnRx8|)pr0LG4E|SaAt)_ z@*uM?RbbNzk36=^WKNA7-D&v479f$L|AqZG&Yg!Jr5u)P8fh31oGQjAl1WH{Ga%RH zX5b-GaHAeeyAV88tx2$E8}k%5HTm8}g^!nqTZDQq1RIlFKfYWgNl@{*ZEu+{QDdjw zt!7nH0h2EP&dTL;-0l41p#bqxL2FKkF{2S zaW-5X*1IKY8J8pTy9qBUGQ*5_#NXjc>W5U$%(4OP4Y!p)>{)zZ2sr`vP{K3V9NRz* z0;}A6DTu}zSSypdPK*4`{pKgmOr!ENH>3k`FQ#smL!x9~6CakJZm$4=`H-~nx~vgB zMfL#v0?G=e{6r#$EOtZ{9h^+U;~*()yYD#t^}THWtrUq1X1qakaDrl^`oP0(E8=Wk4q?L6z2pv-cH^-d7ktp3$lg?={o{) z@)h_o#{=B6vqhXBY7ioo4j{kAxSfKXu>4(I2fp0UhI_!DnjR(zz zVWmvsgUggp>c>qlTDlGCri2&t0O*v-I81`D`fdk+W37H**lTHq8b&7bV#s7k*7Qnh zMI~HP2TOvA#G`96%@t%9FbT^ zi8bMHUX%QLy4>o!?pV*97>|I^;wUn`fe9I>f)P-v*i3ASb>bp|`k;1&N~fgn&5{}& zGnq+B^*}7r8=}*xWyQiJ5Y4~T4WqA&j2efi3cA41&HfA{X!tYc!*5$veS+T@-)Nng z)e?%hu#P?0V)BMm^x(WtS1EjP58{yXvFJ0oY|Xy%#o-J{LYzGTW4Hs6h?bt#Cy+0_L_)UeV`_*JxrVE(EI$p(Z?!8*rB_zCTf6FaWAxpIT{Xz}cQ0MO8 z0^ZtR^WTMtMOfNa)(8;QJkY^O=&)!8t46wkdie;+7|F>kk^h3v&YVFc*<3HnMTZ}d z^RRisZx+gSuYVzVVOr2f4()~Ob&`fg>Mz{`sx#G{7_9pfd^d}n+f@y$2?Q)gjp}@H zxP@(O-A@-(LSF?uhQ?l>*X>>R^CR?G!AqG;HLZO~DA;x=FzYef2p@>>&rq7_FLO-$ ztU(drKIj^34%~4aORpe^oE*%)7=F`*YK$WtBum$m&ISf`o)5 zf;OQFbku6ZL2Hu;AT|*9bPbgwHc1% zY~n1_JinTA8m;*mmj-|^6am)UM@5E?-S76yYmcwCj{8-bDmNYP5YBwaLojaGWnS{$ zdtGb;^C?H-&9LgWxmxnS;XU{_!bv_QTlaX_dy)CE%*l(H#KxgQA$c`H3W-jV8d&5j;11Z0%|tK+eRkI!HJOgx>#Ci) zGX@D0PMFGb3?3q|e8sNKfN3`EqL6+drWKDXE+dXE(w~!>hW#MWfP06Ab#BucV;4JZ z^!#Iv?qT<$`>c-*5Kp)r0c_)PcM&$2r`t!aCHg`B;9d0j>J^>uJkKg3_ZA5*FgYoJ z!_a&)85Qy`zeu2&J_gKjGFh(^NPAVIi7V0Bubhedc6Il~SVY}`Wek2>MjV)3fp&0a z`1%#xcCFT|dHYE%;& zKu$#4LSQWwPKGBfG|m;iz%};hJC?Vw#((p3GbXcoxMS19)3{%c9I!nrXlwIDDayA1 zUd==W+$=ZOh7iur)oM)>Ie|`CQ zS~y=67aNFi#Tc2;s`iNjpeolFQ-K(XK=bK`-|bozSN;#n@l6eY#+iE(4awg+a`!5Y0=H`-nHbvp8xCma6mZv ze9Oxn{<)J@f!zOUMw@^7zv!L?X*ipKNTQ3ApQj)53tGLAiq1zt(=(BzF3D3H7|<=j z+BqD=z0vn02>Xrm?7>u+@v~vTL6#7gYU9ReT++y^%}u`L+ck3nPh+0}VqH#&x1W3W zN)bf~)kclTC{AR?kv9blX3)3ROA4cwr zirs}dWKfu#&u|3=QHi+XVPow_x}bHhko9bG`S#Q?`0m^D>BRl#0WZXZ2wZt=6e^m< zS@O}~e6jeP+Ps-XQZK;3-l6DcanOirCXXXCJ!M+YnK_pHqd93*2XHV<4$mRJ|E=kq zlY@xjyFb*E8{)~KQUpGozYTF#a7OGYkG~XewxwG1|Mk06Ua`z~jIcnJTj$(oIN|9B ziyhCfn3rvO-h)T5#KPP~+2(OXtV5#1B4q1;5E?HBRca`Pw`i7`E>-0*sZcatN6RCN zjh1WAOQjkTxX1ZEcM}dCy2{0ECzaL>|D>Ofmvb5r8be-6z&-VI&dkdaf&1u z;k_&{??7iTT?z^;Q{Xa}w8k4F1E|QA;XGHi1{^|)oan>?6;a=*`#>$K!V|=hyIvmb zeR{t)+U&;+i|}Nl&rakxu?o`)&%5r|;5ce=B4$@%I@a^AYJkY)ovf)_2A6)&w}KsT z9R94{!Db@&+xnxTp=4&so`Z06@Gh8nhxHZ2koJG;7_(g8BEtmJWM#7;qm|X48%iqr zlE0f2fQw85!}Yy4=dpxOLZRD|J0s`Afb5?CqaOw23h3Y9av$izLf=IFB;V`yC<*q^i9>ycvi_ETd3 zS0qw8XUWh8brpyEKS%iaf%h1h)Dq#XJZ$nRg0fKVxOOA8mO#ZIO|dBo*!X-pWz06r z+(_gB_8ZDI_{Ym{C=3OfVsMi#O+2qxbE<$u)T!eh=7t{t@74Xz^Pi0%GN4;7<9gDz zFrp&(JcGeBHf~bRtry1G<7>L=O+m}!`d3^950?2ePnrUy@TJlcQ{@^JL@AaL`=uwi{8I>fb|9KV){7`~EBq?L=>xmq z2+V)*v%t0L?&kWmx(GR$x%i_m?67;Bq9c-ug0J$UBGfw9zxi!+u06z$!pF)7VQ$LP zEO_;#$A*a&nFQ-$$R#FV+zVOuP+TseYyc-!%4;V@XKcbbqnhaK9ps90_CLXKN(Sx? z3ky^!7q!vHU%LEpz1~}8%=*m}#19*~Mq+759ii1=xb>owQ7kx7g)AFP47hM2*cf%% z&>(6t8Cf!lnh$^q$W9L|W^%LRddu+RPMWPolN}3Se&Ani9>k7g3A2ncT1F{VWC)K^ zkJq82Tdy1yyR2PS9p}gT@~h4CN%Rhy!rvp`yRd=hZ`DJM0<4=H+|U+><0LgNv=D}* zlM9b!=8K9S7!}xf!9Xm26ZXjWH=(+k!>YxT-?xH60fU`XWGIUa&|!6oJF<&g+tQX6R!B9>izTP(G-G z`~`Lg)(hqgR^*;$kYdB*&e{TDns~&;vt*bD;A?r&AY`uYLK_^}rXTKgwH#fMNSn@e zLtuKu#qtnCzsh0GFxmMx9^)^7gRh47x2mzFeu+LMM7O?=g{X~qus@X-e+#jW#60lQ zF+tjZ!!rEW^WHN^Tq1=@rx_P1Uf|H?{%S69Xf&M_km8Rc4Sf3YC!^21Z>fMKIW8Em zTp7)MeX=h9P?@S#Yh?H~!#$VnWTrFn`zi0_HnGn1?pH;q zH}cBjC}?z zPpAh^;^UoGJ?|a%`;JBem@%pmHp!0T)wTw}=G7}R+-W-R>v5f|x9w7Wp~po!Ir0br zp#D$!8HI9*BJ2Ih<8=Pz_P==PPn{l(J184i>~&cARPe<}ap)u38)dBqVNkx|jelXd zg({(hkPFb;)k2X9_~d?&NCMq}&5x^V9t2l-G?FG2=hIV6%GiRpNa6;Yk*FMF1GCs< zkf9T|y{17ncX$8d$KwP~s639Ee~d*=;6qW}esM3z-CyiDwn>W*%RE0cY;S~~ep~ze zQ|WNN++2}Q$JoE=7xTuzru)WETY?bQFs0}@B#tqq?;n2e05skv znOy6jx}n#nJ<+-7kPgfYlrh{4G@foxM7r`Uj=kagZ^}^Ip#FZBd$J5nQn>Z=pU*u& zg`*exi{hLxeij_2s4>P#9X`}QM)9dnY{XXyjAqY7(`4{5G>Y#AZvSkBk3EhLCK*|- z*L>ri$QTRxeZk2wIM30C&kdcp!{X@09W~*`8i(@s*I)z%25@csHC04AzW`?5w0}%n zZ1~ z`9c+k)~zWmm4zM|XMya;wAqaLwxsBGx%xh56U`V}&t+9_m=-4+f1zg~Hu`d3!Li4&W?3oTLZPB#2}-xrNUi6^x; zNF6Uk1sL-rIWjq{cVz+<>VFztPjvu?{MSy~9)etdcsv?Z8XT2WV06I^B#7&F$pjq5H-+qD3vbGZg z5YyTeYugNW83Nm|b^4$u(dT$cys;@2nTu#V$5~o{*(3f={Wz=Q5iZX5e@k|a(4;t#UxB2N=`ozJh zZGuGdH8?y47=?sx1ODXA$AMCzBWD|+D#M?XQunW^fDE%9P>FoW8vTIPS) z5OpNa$P>VJTT8!WX5L%PBr;j3+#4>+z45&soK$*_--SFAyA&db7~KuPY6GS{(m>|$ zo17#+x5iL31%MOC#|a!VC}K&eCpEqTpUUt?yZuG*yW+zNA9m>Mf&X9GCRn_zZ?4!e z$sGF^w!cyCRafj=&5Uv!RughHpcYx+kI<^e5@l(ht1}ij{r5oDqfXT$5^;U3ek1lv z@2Iy82HSd;W8elM*`LDKArAjao!_W2>5g~jwd}zE6YbZlZY#&K-gU2kxT!*m&Hp*w z=t5j9GAW}aV7AFAytflSwJsUTDBKu-JnOgrV>wt~M)N;&iH4@H1Ziz`r7{ zJN@4=D3eq_#w3)#7cJuoH46Y)U!jxst0nA6Y1m@6nuA zZUVh2Rnk>~xATS*N?fFhnx;Hmoyau(5U~r;8lhMHeqrobN^|D%iI~*z7rp~u*ejOn zOD99@9=ab6Yh70y9hon4>{}hW$F} zx7-{DwwGuvS!j$P5^Ya_yRTh$?^6_kOCVz|NmluSWp#;kQBEz4a zj`{~q<~k#NvN>a3lRjNHPde3UA2idJzy0A56KDXR=jV~;>oy1&6R7Gjj+a&?HQ5go zqP;c5_$8CxHA{XH`b2hI|Iw!%t2bFqv5|$6lTMdKPbNJ434^F{ za9V)me$C})5c>)#JX}8tTwN=m6ct^bUptKw*1AX0UTnph?EmMzSw4|X&y-q<7P#*U zNG>2D_cW8T8n9Fb8d5PS%H}T@Lf#UP$(|C;kB4sLgRt$F>P-hpL1k(6sxHIGPE2BD zjA?Gz?2U~(L=;TuvCJ~*V~ScF5sjjWfp!TqGT!Hdgmag$25KMGG@!NF-qyJKv%%Cs z&Ui~yp6_S@<(NS4m<3%JKvGixAyG>7Cg`-A?Wtj@!E())V(fuHb@ZYA+EV)Yb2(36 zv!mv>C_n2_g17dKE6UI`>r>#(B&|N4M#-B6P5H^{d|9N$nWbKdvF`Z(q> zbVJ)sgm~gwVps_kv%a|CgKTM_uIsONFkSk)BJ_GG>^XUy{nOLR(`EelYFAnC6#g&X9a zcIvM^nrvmXEIKG-P{@~nx?LFzJnSf+-vk@@+gFTOMIYkDV2koRo#^LP6?z0Q87L{0 zv*TSJtR+b&rXZi@48@L2`>K9T2K$M9G#yL!jz&}ZO(t?5MWm6MT}h3%c&xy0{PS~M z4k&!Ri3iH^pd-zC(I9My9fmLP*_{f8zyn4R#B4{ZMZC}mlRY>SjOFhb?^N=TE##$1 zjFFO_UM9TL4i^TR%5;V8{s=w;s!+yIL!&&~cJx&k5lG-#lj3PUuhwaZ=DBgzUI5w& zyBYz@$ErwiF$%WB@?Fd|@6y*iIAoc}x~KB;{3SRmt-3$h5TTFjciFdCjyWNNo}siL z0&Wb6i2ex7+S3+ON);(9AUAA!@l3~NcUWYv)>yy`f?$$iUxU%8)?oO?kkE-X%H3+3 zES#q2!kJs$UfgkxR|x&U2+Jez4?SV8sGJwY`_3iDi-~0;&uG_6g!{w^kc~R($;pkj^j}Ur6ILhvi=1Z^1OxNJO@S?{2L1B7 zh$6T~O+NS|rRG^_>)e$Y@{We1qYfGwwx_f}<^*5c<+jJzjtdGMdrbh1z%&yJ#&=?( zuJx1CDPIUTt8>o34woLlnzr6&j*brc7$!dGHQ9ae+QWno!lf6;e?nq6lf6f_)%d9P z&N~8jyS#CeO~2FVf-`$6VZ}5E?|9sO7GqR8>3u*Osdr`Cw!iSyMG$cauw+dzDE&70 z4Q0l8n5rTzH9y4lie?IB7nas5F@MxtK3#bnVdK&djpvE0R0ow4roV^vHj5y=BUm!m zW9+&FBc#TcMG%uIVBo)cN1$MPK#BWww?YN{e`Sa)MXYU@#4Jb`46MHaD$^hf6z>c z&kQ2O`w-HY7?IBalSFiN=YNC}iH0@lEN)Lns+6|HlTBkS+YI+z=1;ly+ZCz~A-^hF zrqf>UVNXb_#l)go|2;xCP+VGi^ux0%I0NKveg_Wy>hcAx^(ROIs+Cs?{@{lmt|y5N zij*oQ^Z9RCzWhN_o~^5FA=~o>G;GNf1P90CFhA7I%&9~8D!W~Vh0L2V@KEVtNSaFn zuYV)AL~<&3^9K!?RKYb8g(XtC408Rm4+j+U5#G+cndL`-nAc|&;U=va{DkbaR+@F) z;W`;RhM`P$Q;GS#bmuN2IlMo0qoHH=n8~TONqKIU;$5GRBq#s+F}^F4s*Ipdx}|?r zT`%CPpFu<6yhNOEo&E?y!_YvZ7A(e52LGjC`9hkD1ve99qF8z2u-Ia(@be})YaV7# z><6+P$-YE=MN!KCzXq{@3J+Cpo@Hxm>#!QDL8sQ}`ohy5)m>9*pmh*pbFSn$g%bzk zz&MmUGr%{#y;YadruawEv^HSbh**`B{`=m!m!3~5PrLd(DPo~dx7!{HCN|ih!|SWx z`5=Q{y+xbFHIejS+4k_Bf{Wz0_)$!%&LVMLQ7E^YAjL zh*mMxuauZXlREG5RCeN9R-(&J=NaD8v9Bmi^K6Uw03 zd{i|a9_h(l?;6rQ$wpAo-*APGLDpRu$X*O+3KwRGp# z3vsTw<6{vO7c&=Z(I%3-P6XbU%I-bXwQik{CDi_y-*nODiX2+OciRB%X-rSKVEm}84Xu7bx=fG3OZTMSD(q6tm!(vN~ua+ zUQ=}mG-2)Ux3Ey;-7Y8+pUnx7^WiKu@y=iOXsQW@Mbv06i1mFetz`Fgswg^{N+A;@ z#rZWHTi2tK{r{zeSFqG?!YG&a2(p%}`}~3Wp~yh#F84Dl*S5W>)mOH=pN+-b2(}vl zZrmoNUl-=IRQpThkDM{Zg>W6ByQC$Tq;8*>=2YqRnxv;q&FnrQv@Tyr@cVgkll`cH zqkS+p%-G0Yk(bX4yir0C8Js(OvnFQ|DxPb0R50k@sonsko|$f4{S6i#m5Bk9DUqxM zK$HC`Lv-Q8hZ$JI7>%k2yi%oN?^6Ht3~5suWty1x1?gxr4|*zASavR>(W~Z=-auFk zJ%|Qnu*z5gpeL?Y-S9^i*zXv4x_|ySm!k9_)#}sJ0^-*%cLz9kR=@;T;m<)3ipg;! zrljRsxQ2SQex>p3^=YDrM4j1B606DiZ_>Ps|8is6p{M=UQ%KmL=;sopnR2#66F=;E zPNXp+0h%E5ircJqZs>YIVO$#4ai9YgED|&LN0}QbpR-tlUra?XmWYub*Yv0R{lfUM z_<$>F{x`xYfWDi#gh0kD!h#lG?OePJ;Wvb9zjF0-frCs%y^8DL`nH?+gV~9Hr139- z83huGgzr#zk|<$o1SkrHgF1h2rwlwIQpkzt;9^H2&4uJ8oG;sRd`Mi%h(^0B7IHrJ zkTE&ZURq_7EMVoGgOwKuEyF;`AQkifp>a(=htHV7FX@85OHaz29O6%;ku=lFt@QTM zgle4~e2Z)j*hGBPSV1->c&7l$njYv}&e3e70^Xas&&L3j)_d=O#3Cr!z2xzltRzF!I$WAddivXC(0T(fEFcEs*hz3(RcXWgsY|PS^8? z52Q29sl6tJK9m&Yh7|KrEm-*4H%$??0XJu3v~4ZiBy6 zwZgfCbp+Q}74}$WQQ0f$O$ZRu|;_c&Q2!U!hoy%Blvm0HXV-heb)E9Za(p(o8x~iofu#madK%K@z&8a~72r*~zsPnl#O#f8P z%)+2)2Nc_N zSNmNu+9LdN#;9HwSnH`Q`U=BwWR`zByj42k5OPrIXr*!O)>wj4_7*9BnIjsIkb79{ z{i-nErnjg_G8GDNxUnvocTsTN;VFm)PAZ=ck1I8Eu0Ul-mqyjC2OOgef^ z2r_f&x>WJ*C3k)$4NGOf@Oeb_jXpjR6hlD&P6gnkYk)rQxd8gj|?Mm4Uqc zX!MW3@}CA34}TxVNA0=$SG1p4#Tr2^A|j#%ALk-g_)=b)ljJseU!LW)rMk1~#+E4h zdOy{UKjkEcrlRWDn81sWAdxS}ohrr)FV)r?yo@Q=vsj1WmXZfmdlGLfAY%#a*VIeS z4P>2dsl|=_mjn;a2KEOul)<+_2VI|PTDT3;{yc!GIy^qpp{>O3dj2A@zw znRx8)SZx$wXUt%LoACQ@nl!(&S_jAJ{!A{=-n)1>sjF7dE%=@G*cS6vnsmU71%eQt=vX+AM4 zajkvyiQnxCtJL%5_s0O|iPy2(DN*AWRqP;mb%8`V`ZC2aV_(TmRb{@}=)PsQR;Pam zi7AKejttb%EZ`rJ$ZCy$rlSY#a2;%}IDKQj-~6{izxLB};obzH*JK-jzqqy?)%@f_ zW)C&h(1^AiinY7|I>ol!g1l$C#08RiD^Rl}`=V_#IIT`=?)kfVLS4BXkF;tdChyBs z`Hkup+Q18{S1O&B1q$X#&avS9vQAfGjbVpr%DZa)arh`_I@b1msm>9RiXGTr}mrK=?dYM3oqv*Hm;B z>ECzt&PhHBe$jAA8izIi?J~S2$rC{dWCuYd7(4`7(7vmg=q^fF|KF@001a*w7zvv5 zpS1pKyuAbzHV)-_U!L2#l6s5qg&qyRAKkah(1NIH=X6edFOGRY#GITpWHmrdD5e_3dI7i-aX;)qvrY9GzXdNU4D{0z{5}Rm)6e$b7a}S~(o+exED!zP@ zR)JtwkDv>EQKD0ag^~B~LK{hfFRnLHh+@vxefP*S`1c9?b|6vEdDitLO+q;_1bv}y z+El?j+(H2;0}M~pApmymSvde8YaL0HmF5O4>o|Y$vyA0$zeZUeqf<&(TX&*Hv$eJT zh+JD2dTqpR-eu7_CmlDj`VRhn$B^~QBaS2|Tr?()KzN$_h%ANtK@IFfK$a64H-DfU z=u4p2?c0p-=^ofNm&b17M3P( zgjYkrw0>kHaWWb3C!#r1_htN8Cv8lfU{QlBWVbVMFBA3plur4Bj#X2^6*XH6{zFiWGr~3vGfL^p=cfW8JsuHt6wSUB%p=%TknRBK z$Z#E)hdcd88Hv}?F)ttfs&fbeI&oFZW7*T2KKwSIMv!bCFRablb+@H<{rU-SD#W=I z8igbk^Nx!Ss{m5N>Q}pE=Q+6ha6!0KZN{1Q)N<@2>LV(SOspM!GJrFps z4~YfD18#rb&z7oE2)89`p>gy0|NQKs+u9!VxyHF3)ChZMBbNL=`-Fi!7%uydS{A2a z=U&@fOp%FG!7i2zxQ#W!1_*)J4PqykCTi^sjG$9;#8C9mb}I8SmtAE&IG;1TX8|K@j)*4mL~PR5bp_-lN`g=xFiCAma}de+@_nW13mZyo;7 z`;-79?T3qR{gGFeRPvAxF>DqOd1Nhnd$m@`D_W$*XY0ZpW!3jo$o9`M%G!(U)p@q( z-x)?w?CTDpF|)A9i*6}mz~|lJi_FTC1jO&WX@WJcp+bQEPyM*$a=L-dth2#F*P2l4 zV>BPuZzd%LOhrm98}BMwAVtKUir_tT6MJ#lZ_Yfqdl*QD1I zHSdb05Y?$^+km-03&nI_4(E|T;=@MJKw5Nf4cA{_2n)IlC{vuzV4xV3q<%Kk2U2ya zU2H56O4S~ilD;IZ@U?$fV7rhz>JpczHoWR%4Q9o38Q-MO< zLx`7_n)%5`YzlMZyV<(9I2IMV@4gy8;Lp4heVLnjRO6>j-gzow`+P`$F~(Foif^4$ zUI=eK6q{)Lv_(x$4 zmx=GsNQNa&A4hN}P`?!;;z8uM)LmTdrlHQ=j5xDK3SrB&OAXdXF9GjfGPwmM1I|Ka zn_66N*S;0I?J3tg=#{gYX{Z!U^1CW=%8eMpEhhJdlgDt7t!^~qK~mZ2}0s-;_-R~dn7iOU~Ck9D_Sr09@Af^?p$$C z$aLyZWqq89-_{{CDl1gzh%@W0tZ7%s33#9YsIIr;fCyF|wKKM?5AM18@ZNV87H4^= zYWBP6FZ=u`!E3%Wd(O^9RGXjzh}$lo8^OcvtBOJ>@Vs*&L6_uR^vz*6#lHa#)LvS? zl5bd=#_7=_`EOUZLA$TxY~9S=55KRY(F_>V3@!+5*8^%)u)R=M3MY-6sPdLYwe^pt z?axS-EnKdk&>jyE_U`NUuQ8PpzJ-rN%`{654O&Nt&YV1HFnYLO=xMQLsnn0#zP11P zq!r%faU|i9jX>ioTVPRSJ#rNcd;J7%=6SeUGTI_t_$eQq9Y*r`;lDR*?FBsUqC3KBcQN#Jh-mc zm&cDw@p5wYl?6x#ni{Y|U2M8rry?a8iGP#Da|5fAWKjm`RC5g0oeI@MVfDFD-P8-?$DvwXK&$rNJH$BF2`eER)3viJdXC+|)(7|0whn#AEm?ZQ{SXo_P6V3tH z>cNxf!f7gt?RQ8l1x2ZxZ!#}oENDNq?~8rgG&SAx*TSjuwi3!48N71mMI8-3XDK=U zx(biCD9gpLai5fCj}~-}8vrK*!^gs>ALdL;$bUOH=$Ha{&$0-gq`kAKNH6q|fvT8M zAsbotmLLfI&?PUEOjFeS&{;)jVIgPRXF94fk~S-euXyPp-G#i+pL6hTBSeclz}5A8 z$4mPNfSS?q-rUVB2C(i2r2qt3;mE1uYF6<(8idi@tz)Udb1Z>U(*2=)KtC3jSz%Sg zKOV2?^jIIs#w6B1T^l5t9wK|qYcUN4+OYGpqx_E=#SBaWk1cOkB^W(hnR88$z6A{P zjCFBiFzr*XJH6t03(}lXl|BbDc53S!?>fIzU&0Y0YlA#XCEuIla-pv*_!bU~?}{eT zVX)udE=u*N(UkNKQYEBce|FHF2hU&Q$SHqYWr?5-Tnj)hvgRky*E?YzMAn|bAI{s# zu6%TNdU+uo{Xl*@MmxCNnmVn+6c+;RZ-Iialo7DS|6aFlkVb$YVyiPe`8o7>gZ&$o z^uw=()yV{@QZyja_!ivu98)6FP`r?pwbv&%?1sO;_n4I?6FSzSupQ_w{YnP%YL_WO@6M zE^OjS)&iPRw{%KJ^6zGUe3pn-bn1To?4SK6ZZgk^SYwNV{fxvnIMHEMb+Ooz=mrtg z*2dJ`GdL3$hw}0fJc^gerK6JmJ6(Y9`6l!Tt{oLj1_Zo-T%DMUp(}&mtpuarJEhZ% z$RL?7FcHP8ZC@VI@5NiRS}y!7e&ySaM-1Ur;cwEx{;G zv}h>@-4+$?mrXq)0WRZuD82~(&mp4e@-Ib=KUfirV~PIBMYW41K-u_L&IK4E^JI13 zlfHc|I(x#p-{n_~$DxWnL?y3;KBt=$x&5_ARG=AGx%0R#s{g6;num;I(z86P?zJ_P zf|Sq+{^zXR7)=G*q_FoN4<7+V2Uu5*2p-m$#V_@(l{)Z!h}N6SOsAQjdsioLj8UgF zlSMV!&UwwGNq9z5KQ&mqzEE?h^1sKWS3)A$mbRPTeni!5DBgC)*T^MZXiTXWXFq)C z$1E=UXRRUNl|FK|8JlSlS_d3{myl+*0vOZ#W(atpYxJP4<{BlBJGPg7!EiwWkX=v! zDm{vE?gg$i&%d^`E-vd8zvyJ0_)gSlhX?OuGn8k`f*?3B%;Wy!E#|mavBS%Erl^5x zijq3$fY|@<@=hAj{_`_Q*yw?_m&o=yw0r)FGlCjF_J!OT|40WtuK|~t-_kMjytc}- znd7O|%sN2{K0&2Ni5%ki3J$+L>sI(}|~$G*K=`%UTy zm7<8|>H3H7#Xfi@yB%aVb7w)V(*@F0sDzPK8MGY!^&0Bk&Pc~qBUA7CIMN;2Tu{&Pm9nc+Yeb-qjYYkom=!`Snqf+aIk=5 z@Ewhv*r5l{82eofCfh{i7$P{5=RW23h)gd=d~K!4jS3`U29hBxVqlSVO>%u(yzhA+R4d_6t9)xY znmT6$&vl*<77!Z}B9-(LQc2>pv%CCRI8<0twWW zfW#BiZ?>t0$}~<;xjyf@5L_HgMofs`9^%^y35_(xQ-~VjhAflf^}`?UH74<1kr0IJ z>Er+BLrppHA`z@%-j`+^P=5J{)l`J^ZIu`%(t?WzUlDbNfSXC&VsuexiHlCXzS)W& zo-cZT=Xfkd%&eXcWl%b9(;9gn#*kIF0GuuO=yUi<@2nuKKPeZNygE#d10(Ab4qb^| z&x>sQ=YW@e>UDFR4|#B6lK*)E@k|#kI8B~u>TI+B+RAXQKLWZP;NNROg@o9C3;v5b zX!Zf#s2hWkCuCLBG=PVj#?|*~pYl<@hsPwrE@VWP>yw2gg2iQvZM}m*0dr0gBkCT( z=y8T6hbSv_JS|ne?=KY)4EVy=rRpFaalju$1uM!257KE$hQR{drr!VPsPUEF)C7OAq6@n3Fzeg+9P?i1;oe3%B^ zProX(o88cA_7GBWFu?_bbm6_FZlNQHrh%@{UrpsOHkT99tY)~x3Ji(vlLq>ee$wl~ zFh*pv=PtnZjGkq{Mdc5r9zEnpChi1F%E={Aul+nqUJA((riIrtAuJQ-m=yoJT1*9t zc*yVt$XN(df}Bal3?t9|2-qox3%-+8f}rHJE+^Z)RQFf z;%WnOVCpudclQfbdjB$~q~`MT_a?9_w%l~N3{AW!@39fDHmZFi!4@Jkt2!z3g#ORH za(d^o6r&1FjDQMB6FlMjNpYy4Y_y2Y>64i~h?%(pQAl&Bg(I~^fcVnuRh%qQ;k}zx z4HZZV{O&ViU53f=0S$HTh!FTB<^#6{3K7v?xL}Dg#NRSu&aPekXqVw|a^@e+E5;i6 z?9!0~o+wf)G#6y^UP1ps)61Zb()&2bacFTUcF10F9C!HwApdL(LVWEy+%p z4-bG;$n@NQ8h+t%T-@yVpP=5_s4*2VY=7tWYHJ;1JuKsvd{6m1Uvq4}{XA=R_(M8s z+^*tTj| zM;)|q|Ep$Ui%f)H>ZG#rOca~Sig`Ld-c!_k2u)e{>93O}qJ;F%!UG|F-Mf5i)kOJk z65Zifa2@7>Yk8~(ae{vEQHvxJ4R8XVzloWiL879*cj&{IcKpL5!G z)bse#>Dz$s{h)1+)>_#^P2`4#utR8m>V|2~%P6S?D*&WVGAdrvJeMPgkUQmRd@E8Gq}JcV|x}x!{ex zG(tMX$vmnUN2Z1A^#y?yZW6J zHV|K?|N2#Zop9XGJDXbBJB3eqi6L-IdwCun?+Ffd{|hnydi(W!h-8>)Sa4WzSRatv ztL4}|j|Lqt{1r0t&(H111E3kFS!VAh(Gy9c#i|#a)K{ke3yxaS_`xQxfGS+xTHBr4 z^Svp*z%*NceNNUC=Cs?`RI?r~FDewv>4T7J%wfKO1lPZXD*mBS5lA8}Aw8rw8^`s&8vL}f_`!HsSLP4h zkR*d+#4q5O?}B4~tdFGqVY|VP)ho@7cFivFoS@kOzr!CU0A_#bkBI18#R;NA>l?m$ z(w`TqPyp@Mr2o_Gz(r04&6C>%OBW}YDc7KKy?2Nyln(7LQC2xOQ46A}(vK7X>!i=T z{S;-m`Ch4@0`tcKPf6E-O`q^5ER~wcJjyux?CI~YEWRUnD?hrV3tVZGNg7LMM3t2#B0Zd#bk8^5`Qkkd9fIDrYXJN z<)0!Rm59ud0^;aFn1t&J$0O>|k3gE&8p%29_RuE#K~Oe!3oC$FM!oXIrn{C z*IVam@6OE^8T)E!u<5k=IxqYF*?c>-2Hn1%48||(7`P0;VFWD$%EY~ht(^V7py;0p zW&%>{Z;M4V+$q}KV|uVd2a$r*&=8nepfcM;qJ|Xa;4AdnG+>XNNg^Y#u>rEi$8S>y zb8oY-*E4}J`$PNci9*8`i_W~<^H0)s#wo>#gS!(>cGKYdX;UPugiK9i^}Pg>=Y7rW z6$^W9m4fIB|m93L+q|J`DYTJ~Xc_=UMFXo58sM#zHJ z%CGcuUOA>EjSFckwCeNfNGLY;d>C+5IrPiROEP8$)`WngrX3;9a;1wvV_dHD!Lw79 zjv+VOLG0NWWekiPPUIr@kG1+=^@oTjs9RcTlFvQ+HkJNH64BW1 zM&9FE|JkpK`+^ZZ?=V@ef`AZpPzRZx5HyY(!WwlB{WO7ur`gRcn*&S0`mZPc*7L96>v9fn&4BzOaQeO z-`>yqXsMM&q*PXmmO^6ClGzZkTM8-N>aEjDrO~k#o)nJ7+-__(7AUmvttS-{`@y^7*xR} za6JdpDwuH8~;e2d`K()`T~d;q^m zNo2|w3eEgyW!^}%3rmtAzE zLLN6j`a)8;q{+JvNV=9??TwV6fEAMrfHCJQ$OH>rT`k0s zD@|Kye!Sk-Xc5SCpPD|{`r8QDV#^aaR9?^>|8&DS|H+PT-AlMR5{X53^lYoC`I{Gb zsA5BPPh*lNpxOTc!8l@Bf9%}b?2f&=_8 z=*7YqfdK94?o?;fzPFEj@8fwwszYn=46{3@O%S>63pUT$)&M9chE^u()ivxX0#;Q* zt*(+3zs}`Qx%&4arVykdz+(X*6?*z__#ZumXu#zg)7=sV`SWPaQC{nK&hZ+%jYIS^ zj)6DYtvjwa2G?Kn*v%z#&>YSW-ntMmm;b%JxMtdK*k8XRdVi|WcY5ZvpCU~_qoDn| z*MBgJZxQ2n8YmUy($^yNJM=t9$8{&0fgYa%IEd@Q{U6+elPk@r6)%rx%?lMJ-%p#P z+iv}ccStEIoyPLS#sjrM!^>B@6CPw%zihK$6OoP=BItFg>rLR=U~XUK8R(CQKW_Ws z?_Z%I3)M!>xXO3i)uv-=u){zu=k(zHc$hQ55gymq>22Y<*5^VG1&^HB$wVrI8<|6L z;A9Oir$tMXRs7>2w!jAQYwIdi-VX-MQDX^kA6huW1Pu+>pt}o@Uo9mCTO6jz_n#$D zifA$5xD;Ga&X!eR?5Fz3p#c~=Bm^cn(a~lhVes`j zGbkEPe4?c?f1^JU6jcfW>M?p;)(f_ z`jW`~_wCm--@Mdj;eic%f`ko6)Fhgakp&a*At8JOd~50a9{Ndk_Ok33`cn%74p7*@ zu(4zCJ{GdY|2`b7cS!_PM`nk@FTr;K5D-Q)%=M|wvoGl$M-*yO?l-&?j;|zU^4ti} zbwHY4{x(X>3VeelY5J961OavcQvn!6R!?FG%YDdZynzUe6k_|uFJ-AceT6>(J{>|7 zDe6JN!=&{P(~5Y19+`lKu471p<*fMIuF;xhib1;Fzax@4r z4ZL_S+IV%!?=;{%^&qBKXkZTj9V|c8rniqrv*zjSM?7w4NpG*uV}KtLE#UfHLj{cq zeYVj4=ST?Um{Q?-s7X$qAB?Al960uHf-2WzW^Ms@jEIWzK8d(xBN+=eK!oGSv_cI`P zCwaRF`yj!{d`lL6Pi1IA?kT+d;6_p*@k%r^qHHte3%2^82)xvNxVDVK+9b@)smj4h zOH1!oH*=48o!`b=*LJF9vRPB5={iOHhn+*25h57d3%|L?QL)-8dHMGwSY48nk*NU> z&cFqD4>mTx|He>|tkv%{`5lhd#_oU9QzL0a;;1a#5W2F06g3EZFZhh896~$4w-2_y zJZ-<*ccB%8qTv0V$dTF2ar;hZobZWb^TnU}VR00QGW%-abDMI-DdUQ^yw;kv+qlTD z7AHXO=%{lm3u@Sp31-~*JYb34h+q%jV>leI<>j2rcb&fl+ET#(vN?#1?&g?Z1@CIGCLT`xEWV~#er$<+39 zeR@Eer}tqkdZ_QjE2%v`I+p}c#LR*jd;DAcn#=jzpYNE~=rrt7Kj0=e+vL_OM)y}D zp6{wVr))$}nx{DYsNC?QeobQ8*fB_{AXdfS0|O&Jw0^n#Od6PmIQL$zzYzY47}0Kc zKAb?IePX`wW10E8O+|x!-dDfJu}#;5cR;(S~h&p!oi?QmI#N%T+&948iWLv zo>P%G2sVE9chpE@l367pZ3oTO6*h1u)wrS4yX-aj*P5mfF~G{gT^p8{OWD!4eIjIz1a?R?|OSB43pL#!)T^3Y(g z|L(|D6JeC}ENJT;c!R$Je1ddk?cyM(zc{i* zGQZ{F8!RxXsT&~kX#)B_lS8PK$*bl5L0(_oz%+7t!Sx*WPw;2cr!ew5^V&^`y-MqL zNl0Zt*y(=H`EnHno?R-Oy)A?&n!GHC?Fw=~>+slZ_2QU%Ci@ZB zTSjyN{O^)y<$2^j2pE&LG zRS;Q-pbZYR_d95)7j%2w3uW+nlyqG5QFHBQPZql#dmk<_2?63&h{GwP zqA!@oi<30C|CV~xqZS2F&m*D`1aMG!8eTbUmhx|j%1;fY{{lHQMY9m+U8_Q0y1iQv z30s)99mhQ#aU!Yc*cHEdK{DW@!rv1n6n4@8rx$0wzTw`VjJ9u{RZgazPE9dTJ9}}2 zH226cL+8*H=uU%(aRso9I;T%#skR?zx8mEq-@ArKG|rfDTqlj!9dJKwtCQj6xJaO= zCx|=TM2TSNxSTwilVH>iU@N|=GI*5+{(jMdGxTaT>P5P`y594k;CdW2@m7o zN;uQ)*(v7Ly6Ylt>uHk^w{VYV%j+EzU|D*8d>D(P>k!~_Fr_7dN{t!9h`uglmcAcH z(ydJ31N%$Et;c_enm(J+C=i=q^d}iv6t3{}o9M`LO3{8UaDph&Sg^4Y;Ex80A$rIxacEj^w5yFL z;9dM%F6*mxDvmXIfH;VVxHyrvs;a~+IJqD27Ac}YZekfK5?MB&5x3f9kQcaGH z>K8wLtei-qRFgevA?47fmw;lE<)#`zJeRVIU)++)wV&k}fr?~3BgqP0f}Y+A@5j6= zOA;joAL@}(6J*ae51CM_+Ku`@F(Jgl!^*|avKAYN&zJqlG&0Fb?z&NL01XAJO^1B- zbLlxdJxGC(zpHPS{u`(mEc`<8Wgpt;k+0Xc3k*UM!JBG7&$IGnyJ2=`ZB}7X4-BYw z=S2zxmze!uO-&>LGQi48-!M>Yk|z;VME?C4_wB&|PBA@=49BR70$<57-^CO_H&)L< zXa3X|RllvQ#l)n9JyC&IBxjDSR8}AoW~FRvq=X@OEo`2P>4;QJ2&__}o!}*DAD8T; zv8tU9mWa`d+`K@_UteMZr_m291-lqRE+^F~i<9{KjpNGjUR&TmEu%J`mg&YeRxnw@ z^||lCjaq_0=GpQ*_xS93y}AiV;+rkVq}tuafcmmHc0=T(Y=wTh~pr0@ND)MmiD+PGsKOP%PXYyG@T~4a|{_lS^0h3q`cry zEPZy{P@9@(MUx7O7OZu~wbf>x;2!EXrSyRY&dB!--{;f1$BXd(-t;v31U|rl-kEo@A!5H}hs{NTc`*Kt2Nxr*aUf=t5?lzH!9LLEIt?+Inon7j$i8kd@ z*$lkPbV3Dlv^lWFNy!^E1vaSRyV(teJ*dDvEYlWI<)|U!NQ=a>JvXTcrd7FSjsIXM66oSgQnH1-gKSZ z{8YrB#$W$Uu26l%ME0@3l^x0ujbf?Qx$H|@Kf%hlyq-vp&d{;jqPh~OeOltg_FyVv z^dc?=AR;R7zbWuuinapT;JZLUS9_rwEpP>kHaM*Fn{>?;)MiIPqjD9yBlCQhCv&}r zQvTd@a3ENJmsk%Jdn>~%lO1%M&l_4!cgq3`kpVZi5V=F*(xSbne@ro6p!_+BZ5xVl z?`FSzGg8X(-=(4&Nwz1f&vjigs=Z3gW&1dCwxp?|`1Uv*!1tNHD-vhogZG_=K(xZl27XHJ;|6~!woT=V6 zI8m}BA;!-Usqqd_4w9n2c1g)|AM&;WekW{jzaKtBe&%(Y*eXAx$F{Z^Z(im`H|+UI z+PH9_Glw^~{bxy^dUJvyMT8{v9Vfcf*2ZIJi4q;O&3SSA% z>q2qXKk2(7HvBuYGh+hE4Ig|Pwc;Ua2veh+l*|V0x#vOz8s`gau$--M9ZYYT*|>b# zajl;sK&-$xJ)MPO^t#ivoZeOd{KDaV&a|j7vGC6~UEofZwP>Fg2`jo$%%^t}IjIa5;a2_oqdmlIf_)E^#tzw$+K0 z&1%xF|CRGhisIsv-lPmMOxjrVtWm7mjyu&?NEKh*)Q3fpB%(H%z%8Xw1?BzD&^#RR zIrr<(5^AZK2=ta(WF>F`_v1`J!XYdBn8HY-pVr@?b;A3{^TO3eHT znJppM{diugx4?(q$Ie2OGnHzNd)hqy#l$*2pWa&qT1tM}&p1G}q@Lih_GjCIv3f%O zF}x7Rq1b2>1sz=ylvKi6mfayf(*+V~cS*4Jdz9e{6~bYJiu}e*`}JF6UAWc`4PCeA zEi@jxP38hg{zGI;IuZ#PVuz1Y&)F1Tb;NKlxbU0J_VC~rt!?*6REPmOVKhj|fa)jv zhjgx1No<8N#1enb+7rK0=DZ_c5nK$6B$FP0(Q0x$hEoWsKbuMAv(*wdR6T{rKmGJV z33V~Mn0{72a`7E6R3`zMWy4)qbfYRD`V+YTKH-S@E&Gb@VlQm~3Lg#c~8~-DSF@Q@7`MJAJj<*BsoJ&ev{bizMO} z0?l){T@wQ!U{Wza>}=Td^Z+f*sTPLW@N>2nD<G0JXw6Y+shv{s8HCDX-Zl+ zFQu*9eUEqf=C(=EmlRds7^upuhnJG5WXv(8l+?A-orRy^zm56)_^0)YeH4jft)$a$ zMte?)D5x1y$~|seGd+H*$kD@&J60o8(fV zqv1%gDja6o!L`*Ld)~4F+0XJ5E)JmYE^c{m-GUjg?A9#gu!;H)nz@kRZ%JSIW3ki73KH{}U zd2AM(@Y_K8Z%8{2OxtSJJ^Bq65jIQl<%=9>U#OnYZkc1#1L;M8C!qOA5K32h_RH<`Dv0UEH%MOA^~dwL!{yRixtNbX>->x% zj)5Axu4h*_;HEQmF3#frgj1+b2q9v)(8BO!-LdpyTY0joU!L>%PQf59r$cam;z=k{ zCLx^tuWNBOxYP(1Z_x`D0p)WLKf8;G(m&XF@(QRHN5@o5(^N4;1EmMqx=49!E09GA zJ7nPuyC8HK>gW~tf_1|@7iuGfyL7yv-OcKXWPq5Se_eNlN(6QvTfS{PKIm%;_KUW8 zrITz_XcU_YhUxP{eVvdHnW8EncquTD!GyzQ|06{tgvs<4GJ@CD8KC{iqJ4#$=lhQV zH~Znp_J)@t}d7PC-GffP?_CyERt(k8#S`C z!Q?kCp8!$aek)V~|7o43)BPtIU+Jdr4r^XnG}-LaigNrB^~qh8_09`C;WIBSDOUzay3Jd zi7FQi#1>9cry922OTjSMXvapA5+6jRmeQ#rzWP$jVDsei7ds3!F^|ZNq`dfy)PG-) z)@rBagh#hlIEXNzJEozLg8!#d{E0!Po`tZi!mze&)4V)6M9eilDRKV~OBq$j0cJDD zMttY{i>S$q`%rmX#}mVm8@ak~Pc8|I`-LIUw*bST>B$ut7xp)zrm(LH@T#{!2O-dp=-xRr}JH95iShr-E?)Au0W7Jj8vZ zVP%U#$fJ1DKO>x24JAV-lMYggc;eMuc>AF-dm3L+57o}u8lZdqd zS5!V}d8T-}O9A~{ym5iU#0qPJThdgq1pC8~lKm?7bSuUsrhKEn`t0A>2SUw=>0aKa0iFvGN9Ssebqckn1PKq>N7RVq|2%wN%81?llhX?JutaubNNH;6E3*aZWoDW zZAge|Bxc;AbrvB&<*e$<{pD&MdUH6dAknkt&*(*=haE$5^0Q!M7W2u2ia#Vcf5{t* zY^h>lwot4}1Ose&(|t|LYvM^1jWG;X%_%h5cR>ULn8sLvO}6@Y!4b;eY8;wQ6Y>AJ z7v?L57hb8!`Q!C3MjFKj@w&$EzI}59XmoB*TzP>}wWrkEzTcaK1cc_C`vIU6 zi9rHMA$`2XS59P{OdgZ5NnJr*PD#gq^BFbZ@Nkctk*HcNM#JSp-@}X^E?Td~4D=b^ z&);7KP7g7z6xboQDC=tkoIy4R-Ijr`;u3NEudFA&?CL%Og9aY#?k_)bjQXi)j17pX zwk3XPv@WK~GkL#OV>0NQT1(ScA`AcXc|Ic1@frF1+9mvFk2K*pfsOU|I(5Pv3Wjt}$F4tF9JPIdh_&v}2m;EQ3*N9_&}UaB5T87duD8{jb=lZqqV*kw+t`ORs_y26pCd7245Ksw(U~Ho(Bdi z$LI6b@f&q+XR>N0p80^vq%#vy@g7_@fNs+06&+w3wiybHz2qxcOd=+T3RksyU}YD4 z*ozj3l#m74&w#M$Cs$iMO-DZJrPny_0mxGgx}NZ)@v4`uAV;ouhY7VMm~rpU96s=NmQ|OvX*VIT0qIzK-{{TQL4t*)~Y;7!rg#&Xn3SxY;Gd~d`9>CJz<7BKuAMS5+ z9+S_jnx7*kfm6N2ZM`2V_|XF6<(a{pgVb8#RL<)~9Qvh-{t6%nUl{DOGt;qp$_JaD88uT;LBBJP^Eif8Jfr>~^wx7^Lk+%qXyMx-S&E~Dm3 z0~hIdmS08Z+ph_3nYX9K?$D9e`h+ak12_68s_;hZ5|iG@_)(fbf#r&lwsM8$TMuOk zKs=_g!=!MvSA#=VFRI<>W?x2|wI%omwdL8aEMI2pqUIaTShJCAROHkx)wv&aK=dC) zoeK8vR zJlZi{&A~pL4se?4@X!gvlie607Y)zW(D67OLaqkWj>Z>bDpblw)0vDL?$1-vY7ost z_SoXl?hUai#CO!Fi-(pbt>v4N#VxtNBc<*SAk?gh1@oi%49ODZ-mX` z*laA!&SKZ^C^86GjRspf4;gkzJ8z+ZeJxhYza=%dKtMVlpoM|qvC%s#3fQk$_n?h2S_|l(7PFX7%!Ozb!{>S)(Qxf1 zn_X>3+3o^Kn7|PFc`TESU&sUrEKf!LZe*$a3g??vVV+eu8A3S&9BzQ9WCM84+RwPvNF!yzW zKRfwH4%zI+(0H&aM5h2w2D4b00gCu22^tdYEpFwjHAgCn?waqbJR-tM!2)S{f<3YJ}>Uy27a{GuO&=vmz0LV7Fe2?qB|K`eP-F}HJ++b`Ld@&31 z7So~y?2A2NBvL~98E60Q*A1v!&HAxFQy%3q8QWVE13oc$ ziD_ax-87RTX17=Z9n6w}ohV0R16P*z)RncEOl{KG9+!h*$YK~Q2s#-S40=(|$e#9g z9eNh_$sLWx?+M7p7?%2+^XYWucG1s#tMApy_)QqFAF$y9)V=ei-rc&c^J-4F^BQB_ zZ?|8o4NU|Ij2m)o`kYaZEh23@{LZ~trop3@VD>p%hMp3YAjsvLH&(>jTxe2-f(A$A zIM$1G8ZI7`T3?0)-U=3^?aF1KsN~3Ka=T;TwI0ao9Fs7=KMO~q|DwbG6i2K_#@ecB zyWyf7;;<7A$^X<6*n;P!Fde{3lfemrQt@3NSnLmlpF&|WHWUYY2bxBV@+X_k+`{av z#$C5h+>m{otwud7NrC7?sae#$Ozw-g5|Mi^9p4Zsvuc?7x;#QjU&uiXNE?nrL{5h# zX_YZ#L5N0nQ)-82ug@k#ND zB}%w{#w@SbXl&K_HyPHbP<`gC9BxL&7JP!me!IlQ6R8A4AwZ)!@L@`xF=v9xF?VbB zAKdC!ewycWdouWk%K}NEosdnlR0R5(8Ht4k`Bf&D*)-CQ&in0g-!(VIHMXnQIPO(U z0#uI1m>(Y?*$Wu$SF3>Y&UHL~YCWo~YGm&PPE4G^noC3j@1G@rORH(5$9cl&UpY!a zv+{_1#)R{p8+9jvO@zTd*_-+!y<3>CKX1f?Bj+REj;d;lf${X!Rv5`qpbGo8d`=rJ z`h`SeX>M9o?OB-$q$oOR`)UrEeE~imLX@Q8*WtW&W$6_cY7{Zo^RqF+WeJ=?+aw<% zPsw}@dvlz@Yr&1=@ZNMLWoG9Z%=3t}lXX1LbDDJ=g?CpXKiViwu>Lam>v{18>ok>J z*(JbCI%leQ(nWxA3TpyJeCU$^zgpy#bT(U-0de(Y)-O?b4RzK1SsD-p(=gbd7Z+nl zt$!Ub(cK0Eodmex)XwtQ_>hlc!H#T}7GoYY z0GY`i+><}QpK3p@ZoZXDK|mmrq8}cH9JOiJbaav(;a7DOVq1R05N4kl^xiNeGZnqS z&CAO=r7G8P=t@W13e_-0Vu)gy;1tn#&GWt*@od2g@#pirQ8e*P;3l6gC~tlqKas+qyXTaiO})AVPu)8g>5Y5xyCd|Zf|l7%V!G2`7SkV>M6O@W5e2jxKqIon+yDsG%H%I;^S1=QEtkld8!CVKF6< z$n{>9{MbRh(8O;6&%7YPpwD#ROt6?~l2TV_odVyqe>$$1sZ?zjRPx?BOZo0MHkK|25~(CzMa zQv)8_^E1DPNc=i-HwK>%=Bv^GAmkj=gSaHlKsSPylp#QrL{={dkK)+YJQ|XPJ{VkT zWdK(ELV$jw6y6Hv2i~5Bm(ehF0VBEi4>(K)vPe--I47D%Amaw$(1V3ZA3WseiFu5mmP<61~YZ|1(+6OOgTT0BZluAZjynhHukHLWcl?2#Q zuQ4wl)om9LZi@SWsL^|;70WWIbHMy|I6lG&z@v|r&t%fRXb^Z;*gXe+V*Hmg9N4@b zbst+#=H?Y4Yuaz%&Tz}$aGCjYP<$Fy!rz;De>j%4MoX}zqmiH|yFV~c7~s{sQPfr; zrLp=GxTO3^XA(Ibnx*X^7PfVM$DS7&9i;y}FtT?-tBOoOJMcGE>(`C%V)OY9V;LQ* zyxz~inVlQOaP@st9>1DnD4XD)aQ;w1ykA>=O{-{kj=>Om*@|FJ)|%vyi5O# z#%UU;o^KpG3`kU?Kp+;}pxblv;YXXDmGQn%)T}0+f0oa9I?1c4)O6Bqpit)#%Gu=m z_n%Kcj$K-Uv;(8Qv`L3R3fzSr7ol zQvmdrvf~K9Z0GPDeSyKh;9~(6SwM^L@3gs9{iwWPFQ_@p*pL_hOVumAf0MW94$Z z7o0~Siy!S0_S1lzN-w0JNG{7>zj`43l3eaec;T~8raeEe{ge9qF2l*Wje})@rAdeORk6L zx2J?`U>AWX!HbhIC_>@c@hyu78Agx^(Vy8+-KS9D{|%#!mUX8p)e_84befEO0ldLC zhtGZQTb_8rutt`v%5cc1&uB{+5mI%;>B+dSaR840&+WPePr0gK`%Wp1L2-AHIM#wBEtiszxxkJE=)!7 z$MA8Qr2q;YO0iD!*^|BZofgN()Sw2MgS14D4-{fpBp#D$yE;?iY@UF}lBz)s3$XzU z0%y{n+(g=F=hE7{-H3GB$f{s>>%XxRj|yZpO?RjuGl>^=bAuzI_}Y1sSc6wUwUc4~ z#z4SMh|HJw&J{{lq>qj-r!~`8s9Y{bYrgi4hTlQ79(h-CI?`2{(KI$TG_(A(wL+5m z?WF4N!0y>bHLqWz1R?Er;q0@YKRV)90!ZoPPC0aT{E4Ke)APgTk?<)8er*%J!t%P` zkUVtj@#wu5s7OlQNqYqQOk`uls>mEv5CNl*kt{T!)WsleN)WVU zwJ@Zl#0AT7HWHCSbT(I9g7N*`3Wm@HOCsjRr7lBRhDK>u_xCFxaDp1Wlj?@h3eNX3 z%HZI8-NS>)tL|Iu?2MbJMs!xfV9)p{(?rU=mII;6spk=5T`{udWSikjiy zsQ0^RFd&K=&q5Y0RB(jlPXHgDYU~Vd05t<_7=|!uUdU}`@^Ip(M7ahun?;mLKzFhE3?q=5*y^k${py8FHH6q)a}i z;eJ!A^_4fSLAZ{G91-dWa*CVXVhWLO2KRi1F6;ybqlt;EN0M=7!fuMqU#N%_jof69 z%el}a4wdq112v}AJ zW&`%I5_+a(JovfFs4lx}=6T;lv?;E)K*MV3@4^|Fzs<*@EoU45p#?_V*Hd4zThKvT zQBbetebTabt;(A;RVtdweNPLbWOPYai8+VSSj~<7R>_u7g!pu%ATWO~L3j}qkk8(; z%alr4A}OyvA2oiI40;H#22qSQZtXvA&t1P7epNoiayp@EK7-IgCa;^HOq=Z*ORaaFAf}46vp7S3!12r=nBQz|lW@ML>Pmdm8a4x4c z_f#z8oW%u+sb@FvQB{#ya{fpl`@Er+hfSZwvMc@azMCKwHGST#Fxt6Pieat2R6929b!DiKbwjh0cttlKhJ=53jBsFnl1pJ<;$W9*JD$^}hP)r3CMBH%rt(#Znd)Yd{rT z*@#ot#lt3wk!h@*T~p>qKxH@|aAx3H*;C`P2}ye*mAByhyljaIpM&bMIMI~4YAP?h zY3ik%uKW`C0ufen(21-pEV-nP$f21WlSLZxD^TSC_E|hL6=vx?C83WWk(l=vFKuj6 z5iiNCuK^g~j9LCPxtyWN@K51})eP33ptPt*b^eN_K&N(JH)Ck{I^)Gm+w}z!=g%gi ze|gCRtP^DwhRbnv^C&REt~y5Yydwm-g>RABK6h$Y0K?k4IzZSppPA%qPRDhNl4$TdZ0w8-^^%*kycs^d>8Bik_{6P()2J)4 zhhok{&(m4K#PhTX$f4Aa77N)6=-x;?w57Xnb-P*}erCq|!x=*h@m%}%X$ITrJ)J>| z(=@ioNzi_AhhQjA1ZKwSG@j-b3a=?o|9kxIoezE!AA=F}21mZq}Hbsbv zSs3)3(NF{OIMCdmaEOkgqgSyNLrb{oPEpt@LV_-~BGS=j27XcKDttGMGo7m%@pzy)@H8-RX1RH#k(AkhM;Ddk_!02t*cK>UO3?lKNK zkJAx#b1+MgYOQe`Li+Fvm$}QvczezL!QbkS|)=+84LmD=M2~{6FLq-R0A*7QfL?6#O6Yf zdcH1HOOx(!TW|G*u7sx6-p_yN!20hH@d?2kAQF*!2Mx2Vo+KH$Fj?ODl=+wRAPgDZ z&U7Dv%AOb|zY zZHIf~UX6M~Mg@Wy2Ut)w2XXY)X>=^1ZJ~yj{tSkexP~gZ9FusDC)Ef6gl)k`H_r3E zdc}9Y9DTXIb#TLPFU?wBt~M*|KiRKYNRwzn(cvw~gp!4CJ7%C&6rolZvI zRMT&s*sf-AIu_oY?N?w}QxVi?yK2^SJvBS^BXI=FnLhz}T#YY`w&Cq=IMhLTG$h{f z1>rry>nRRHL~a&7;BAQc&Lv0V%DUlo<5#;dw6D?eq9SGu0W7zmU_o-tX^>C(2pG*; z9&r-W~MM`D>RQ)O>n;sY$NeikQS#6 zZ;B=_zw=uG_-B&dFQO0O-Vp}##zcjcXdq1|AuK+!iB{XkU*&X|H7m3kKA*YC46uLU z`5b|FE7uA?p-NpN)g1Y;Fr|=DL-F*-46)xf#3U6;z%U%)Wefh(x<92GXcc>VMiI-e zspCT%M^yQYUY#5Ds*Oh_2cpe_`U|0qh=8rC3S*NDna^zK5Z)(IPswWre>OZu$n7F& z5HDUgRE*wnuvnXyS~k#9#0q@DP00Wmhy`G!1RxTy%jWvrX%T@LS{dB~=Bo~qxoyAx zCdzzKvi?@`#53So%=CIniH5$^@ij;2B9z&XaymK4y+6_)v*C<3ciN`IXF!T8YL+*WDDXWqIR)BVZD`4FJ?&(w zKtv?u&y!Ogx7sL`e8mAQwZh(1CB0U2-(JHv^o<%6G0-S{Bsot;4!5P0uCfr%dLPxv zVS+y9Cy=MD_gzL&sfO580Xw^JL%tL`$p4p3DdQs^Jp38(1_TmL6U=6Uz4R@}<`xMe znJKjFW;=V1o;E8ix#Hgqza$dXN`^Y@=!Mr71LtQzX!-$Cwvnu?cLJ|O$8y%;Bo4WCl#`(P7dO!Xn zF;Hw47)e~zPwI&$OZ2Hz1+S3E9v?~eb_OfQIoD0AN>O3>hA8T_ruOHg*{U|C1*Re- z@$GK4UY{!jkei^yMJ3IQuNLnlP4J%@PBR5Y1foru!1)I(T+2J!>Ds^8K|fD5MU1mT zkDewIU`8F%bzNCWWth4@{O&Nog;`LCfgzGEhCeVr>E>n? z%>hkv9sl^1+|RFY4#Cq@bzjdTJhNsFct3JgYodKmBV+UY*^1bSCNsK`O~qyJHa`%= zWhF8yG{MS18c^w$sS1BhlqEn?JsaV0I2fZddQ40!lwOw@B1TlmN@w6SNFQH+FAyL2 zI^wD4oROfu)-9mXWW!Z>zS_R7Wy9lvuTLwu2Y~5BdX6; zrz%$!QY5vo$iayv^IzVCd$%C1e^JpjR{O);wbNA@8@?^F=$tT5&jH1JPdyG zAhE*=CGvi`Tu_UV_DFr;1k>V1Hdlga7zG}KKEaRWDagP_@L(g0?))6o7=OIcS$+e( zmk$d-W_3ztqdxFzX30!afff*I@Lxx&4cCJyjdKLLy~@`D(m=|)y{il;BBQGqediFd zr~Zsdy=(#pZnzd9D(&X)>9OBfN|vD#-RczGupsNZv%GTh3=()V+Gqq?R@GnhkMEal z9ySa0^Re}{W{gJGv+2crB#w^WOLAl=eQvQmwrjkK|LNFE89skszF#(<*$13Lk9p4G zxn{A4f!rkuzo%J)d0!>QZ8l-f)h*k-7zZ7 zexEJ7$G$2nFlfS_G#2YiA;hOr{+1~quF!<|)W~A7JW;v~R*e!F%3{ytS zs?A$RiFc}yxaW@3uR~;*7W3uysN_})c4Ml=q^V9SiON!J7~XJmSIRjhENlT0K4B;3 zf*A{qI-R>&TVC$lw?cA?YdW7?N{||)l0T(PHo<j? zcY8QnA~BXavlYW(@)nK9Ec2=#sMb4b$e8>YhclVFHSF4C+Ql`-WFV_>Zr0c0SZ{Y{QzjQnqioj; zM}oWiQ@TX_XJzltWPEQ!XvLdD3a%>jPjznH*JRPRR5oHMmEU?cga_cY7x)>&$X0|W zyLFV8k?fJg8;pR4!WH?;OYtY$m-V#XrWCr8^;LTrAd)sEtVu62>dnu~9Gpljgn<9Tzyl%M0x&D9#iSqPd@Kc&e_pXrB~kTqrh&x*6Hd5 z5%;iZnjRHkO<3!K=5T+uzDsHk$bqRQf4prx$%PWG=g7)>FlI1ZI)-RmKwIX?g#qHLQ}&d%KwF(3LPSP~eGT zWW)Sz-x|J9i5|Au1S;C)s&=r6j+&1HE~X>TZr?V$&;e3|$6VlNW9qrz$A9~W;=rBi z2t?FWl(#zTU9FpLJS)m{ix6Lu*WL1Xum7;DTucf+W^FV0u0W3E7CVis zq}NzOcoS+(5n*qg2!UuljtQ9|W{GXr;`@;7W<;A}h#8#sCuYB4Cq#pxVP#|}>tiQw zGv!Gy|7dm0A^Fib9r>1hf#iRVS6Zfr0!LF#=u#B@GW>8JzBp+WPSzB}Qh*b4DB%*M z)JGUs*I_7WcF2qB3jJ*R`pW%BA1zT(+Q@xH6p8XWz4~jHrmKx5?n>1p!a-2s53Wwy zO}{}6W%fPeSHfdv^%;^%lWQ7YM9szMe@?u{9T&U__3jqIJ`bFQ06pdYL{^Wb2yFwG z$Bh`Mlq|`BFeL;xQAWXZzT?H-87VcjHkO1n5O>k)n4xe_lkAOC;)DKubCD&8F ztV;6Fn&E@-q~^G0UmAkQxarh?!~=pYHBvfD)3@-Q795yQI4jq`M@fyTQ>Q@cexC6871?cb#(`wSyn#afg?1TxU@~ z2<)YNh^?E`{H$*O{Px7}68*D_8=f)o+?JdDfh6?wwDIMUe>E{9o9(#D>3e@41k`Qc zYC8%!g*Y9pbbF`#*_N`VP$A+Fw@}Vz%t@JBZa2>>t`*6*iD9G<#v~+xL+mH-yJB4> zHa19)*B4)JlbV+rUyU*;cQSklq(&9YPB0()ZR5J(=`_StcGU81WRw2pj43?VPakcu z+xKR}dw+<;)UUZvVhEY}kyYWtke{uq@!<9#yn0DJ=tW?g1V#p%1s5VP|p9fva9_F5$=SgH_T*z<3cz;Os`MaIlvFb77(s^=!i>KD?pxJ0xC1d{Qc+6N`_}uh z8#r=1rG)0yI~Ms=fc%GB>`K7h$@}SFE+FBTE3~M1oGyZGb#+b=q(gmmrrPd_x%M3v zwZ`aI5)ww6T{)o*_9^B8IQ#oaHXUI}XsF~P(UQW6GW_fSn>$yLf=l}8^?7ry$Jg6x zCO?qgx6_aa0e(tA=9kD-ZgDU7iuK!<@^=G8lxJdF!-AxA7-svlNJ@)vJ+LP5?RZX% zBHXGzdr33h4${SyO5G}UM|-s69m5GlzVjDKGEp06)qc9DQ&hO1x5?Wn61^}9zld z#i+yJTY085!ZT7pd>&j=?BwfmhZfpgwV^HwIKgpu?~-q$d^r62>t;`@i~n7|i~V)i zn%^W(CYM=J@NMjiG<{s&!uxYwLsoO-o$L3$KVj)Y6z(m?GZ%9kb67G+-E`@dGO6rV zg;)uU*A(?z?LQ{V#HZ$rMHUvJ7s2rA5`IZE!t&!q=aCFq4;auict8rk+}mo%{;n*- zo`8OI#p1f@><*OuICon^f>Q!~gAD}q!AbR|MHmmVBHiw4ma4wFHLOq-94eNOkge-P z_6aWD@6xOk=5r(k4bbud7S*GIdl@Nf#2B3-WY9`a1Jx*d1!4>wn?#=q?85_705r=? zI;JWXoIq5^-KI{A-t!>dA##`QQ{-oFoXwPVP;U-WW;|K;Sokp^y{>d4`zAu4N`E7_KGe3e_yz<)gfXkCT z9~1WVYDHIY>u_6tsSY zn*E1I|2t#u76vS`;cui`$M>5L24l!mvz;PVG#%8SjAhn#-`CC#n_1ZT+3EBdQbIJY zTfYQulno`_+m%#E{rI~GpZBApK29g(E^EU-d3uRv?)lF7rChHu338uDu}IHR9Y6^0 z>6s#iD+~|6s)GE$pPAUoOnGgIOy^OqUatz8SRJer2C{=n09XM4X70!x zP=nksxBX*t|Eo^=LXfVBALV;}aB22yxxV37%U910;sQe`6$2jLp1 z-HL)cneSHiOV;_oWmwE810vK>Q~cwU1k-+lk9G^|orRJFRh*bo2|rqhTTO^weCc^c=$ zd$ziZ`0Z9!-=~q?6Tdr;E3pg#)1W=IB0Zdj zLS7U;I>PaLiX@(s4XM+u+e8!_{gdE%a|r zf%eUu@;j-#NIsX#OtS=bn7IGLHaQ@!a39UJ^Gxaz2ck{~=gMd81#TA-od)r%>G0`9 zU*mj9$$3L~(awY^A5Yyz=jf)yJdVf7tqB2bH@j!H;ONN`ly z0eT4}BZ;PC9!;9;fyG9;iJef+<+6-pc>AmNu6bEppdEK?qCuNLitZ+jGj}Fz1SC-_ z2sa16_eKTd1RI5qIN``0nIAkmvv+`rxBXqOYEg!)ZYOPwx2Ly%(!Y(>qPAg9FBB8I{O*qs~SB^i`5_QZ%&j!)n8e3Mvc84ia71)j${v&@; zmqC_}!ES#%u!nk0=QbQ&PtzTJuT^`_nj+~|S*|^~>?Ub2r@glP`#C^A5SN`RU|NZH zl}SUooaE+TE=eluI@5Z`ewFuaKo?-y6wH(m41crRw0C^S;ttAjspdTjw3}iH`CfZ)#r9B>?*or zlFa?xVm`V>08X%B?KS+TM->3Zgp^^=+EVYuXWes#I}=Z3@IJj-wBPISaf5Z7N&*fW zdxv7N5s4NnlX}okL%;sZroYX$E9$xaubx($ujK8!ahOkjq^g&l+hHVoKvYs?KNMg- z;FN{XP~&S4dx(at>fNN3Vu-kaa*!W6G$m=cVARzZJ)IAz>KxdGndQf;^u{d6pv3P5 zG~qmUP&yaF!HW>Mn}w-7f8C*&3>HA&r%8fgpZ(hLm0eRnXq52%)K}O5quFX2X`Sni z7{RS34upw(YX97TcpAjX;Q9|*?|&0@4&eKFI%6<7t|e5{u13T`ge4TK-I z;61khNo`zJZR*QdquDzUqWIz+MMXeU%5hTDLLLq_bHc|C1Ps%HJ+mLV)C%caf2of8 zqexWoME|28e;~5VF8IWAwCZt~GdoxP+Z3=KE9|%YwyzpuD8hufHS_~xX2X$}N1j=R zL%qI!3FohzD5-%Fd3T87Nvc@IZSMoWm4+gOqNvxL2|w;GHyhTyK`ShOwQPVkz{8T1 zr)7;wk#)d5;Un3T4_%TDsvzitLuo{XzQ=lmj+?JSH`*LqV~CsOwQCd?$A~+FHE5GY zRV*a~r|WfgamS+GV>%NPgqcO`?%Ls4u}g$w+y;Haet4i#(*{8XDJ=ET2?DGA3Q4i& z{B^)@!DNC*vSz)0FaP9_oZAEM%%8k3<>p0R)tC}owQ+2vOEn<~Ro}J9O0`wK6X(@f zS^F-;NYbT#xUaEh2Eu&E>y^^a`*ruu|0rJ zlq6F5?6j!GCj0g}ZiruJ@4i7abI$-?`RCOBX?UYk6S9lsno9^DrhzCBz72O1fuB`( zg9$^SJ!@F{H5VEMCxhCpuUeqqAj%dk#S3Tk9A*X z_1DF%3zzhNkzLWi2pb0MFpse$2NlDOs$j|b>1XU+zLPUiszi9>>VrNwjt-1^d9Oy6 zU6oedj(!-`T)kej78nJ1H{8%qr@z6VFNH`8Xo{H-#wwlK8qT7=7`=mQ)QlmxLJx2b ztdCzCxNP;79S3&6a?Abw!$Xf~6tk;=C|oBNV<@=zKeZ2o;lSmDui2Jqb0VFJ&iW05 zOi(eCr&AB$MJZ*7Sa!Q;4E5RWG(guK8=xFI!5TdlPE*0gBtVQVG?HrZnwOz)?$x(c>B%-QZlE7 zeK2Bb{d~@1_(A6gt*%)C%2t#Xd8NZF zEL~N34>Hqq-5u4*pkg>64B4*lnsm0PO`kMF1EX}BTZA=fP43iKnP5k-Q%b`gev5BV z)17pdWZ-Ax<$Mk^q|z47+Mwt^D5{wu&uCzI_iY417MY>mX4AXzx!Vgf&F@7xrnrAD zDcbz5&CzMPc}cz-PX)3Y_enWCmw{c36+_TikruO{*X6V5*!9kC>RZXyCv(P+##_R> zEb7hENIKFmb{=)ycNhPu5%c~PRTh5aew5f9n!DZ(A6R_QhQN5#_shUY$>mfWN5WLNYJ<{<+9J1xq0k-zK>`74Y^ zp45to4*x0D1S9VroQmY+a=~*39I)4oGKgmZcb(JvFat*$b@hUYj7HTo2GYBH3anK$ zWd1ON!~!zOLB@+8M>0QtXFxE;63IxDyNJUAjM=t|TKdW-G`$(Lm=dxVglypJ2nSqx zuZNTpWenUQL${~nE0#HQfFCahgP2>^v&et;8V*DEVrr)$M?pNPet!gu@0rQOxM8Ft z^w%Y$9iQ%(^l0;H_5NyuW_9@&yvMV~=_XRKzrYUFxW;BheX2%B1>8Id7PC*3W=Ba) z(7z>5ItcAhe1IRJjZv5+v~t!=utA;+!WD#62RHP znKGzPbVBTKXetMJ(UM@{E6kNhbFVAPBIbd!m|Poq5ON?6y=)fg_vD`=;qjSus?~X9 zRg0@~j^x<2lwjsLSywyrrqtnhN7LX{(v7cTQ6R zY2$!5w3evRiS#`Dj7}Z4v|;6<`FF|(PfxyhzmWtgr=3WKO3bH17Tr31U+Ex=Bf&pE z(QiU(rgA==`dLf6Wk$fxqC(52>|s9f6HHBxrymlY8XZY=hlM{}naYe=Ct6XELw2Zb z)UvjXzV$qo(vLdB{lU(u6F;)NdlT9BGU?I7NkBH-)`ysncw|AT8V&L-e}{V5Um z3!|E$9E6T4@K4WtvHN9O$x2DuSo<&q8v&5z2R*!=PJ6Ye$#7&j(qE;3RHHBcncH=T zDUV$>wA*k#n_0u&o}$Bm9YzU6;b${jzghxc1EsSW87^YXzmvKp9`)d{wnsjJxwcDR zgT?=5mVc!M6E5Fzu2cybXoxbn?bmbU3{x4N`#fs$<<1maUGJnpvu-5``fDWI1BD5?1@(a1G zcCTAsxR?}JR9kU61wZnKENBA#SI-B0c`GQEj;^C^TQJic-N)hG6dkkcEN2?UT;(rM zX(+53cP$OpPCp56E#RIba5R7nB87cGE16@_1xx! z7ez*VGTT?TC8UVs57>sZpmvn*7pxmi{N=WEjD;x3ZGx~DsZWM1puw3$&JIEu>ao}O z`X}E1s72(vwT9|2+~gSANRw4`Wxa)(ShTY};TwPM{PmD(OBwP=hU@vNmNNf#h-@^~ zW$@f%3>WL7OAZLbN!bPW|CXbt|gI92Ikg z1U^g(9)6u>f`WUPvoM3hP=dCpwk{{w-^EPxZNam=G-!&4JKwJ6zefrNt&2l4B1FPq zscgs@i+{2L8U+3rDib zY2|=JONx_mq4yJJHyhd_Q#vj#i_|F6HMA|VL#uk>-RRTpX)`q&h7J}q)BEzAwk*dj z(SC`8vUu!~z1I6oH@&5F}b^9fuYr1e?cX)il6KdwC7uu zF-3ow9|VSP5Ym=J8-3V_Q};2RMdK1AJ@V-tPoSU~NT7Q6-%SGGb8DWfHsGV{tVs#O z%(5Z2KpNz+nxj?suwopmcz9>7|MTh0lutM@GeD@)W|m@zdNbZ7p#rvVYui0{*I)9} z(>+C{`Q|^AXk>{G@a|TdY-%!oVo{PtJ69Hl1`{_FfA@FgWhR2chnVWv~X0^1c?g!1wy!imxwI49yS z5i@LXd3ijptPc;5rnd_N>UEucpB53s5S>5uqZi3{>hf#n6r2T5W{GPNZZ`E3Uq>!B zIDz^%^uElQl{OAdtr4-EHOjd-=0?McHqE-cDQ0rZ{e$fRHvjA9*}TNWvipmFTw*WB zcsu^giXSuEZwcOuzDfZ`?D~#QN|auKT`melID`$gMSd?EYEaL@q)tSy%xxfo?F4Ao zc97-C_G6Hzu0$*4t7qI2y8u_WpyumwS#nCsiayiUw_>HNlq86mR!hUOh)!xhkZD=F z@m%jWrHEwKD1K^lf9AO{jzT%HPsb=>`WU`dXRri9EnGV;$pzAyvt8yCIxMz~-vrC6 zUx6Umao|E&YJ&jwz4bK;RHC~}(E&3tF?pMhr@9!Wr(96WCiG^De38mIw7oyc-tvrM zLZxWPK)n@DjNucLhdteBo*5sW#$s74*_|(;95kmy%$njT^sdz>EH1mDR<~)OR2(+y zU{jDs57r7ZI_5$3L_)t|42+DEI#p*stU09kX^MSn70Fv<@Ckz)|L#mcJrK0BvT`3m z?{++=00_a5P6dDWb&Df8u1Ee|$%Oq3$26-1_8sO3Xmw&f|7qps0H~8bnEIO$YM>eW zIMy4wuj_gO#rj{Z|7$|iuvIUN`SSI43H0?q76BBzNLf<~;lrr{gOV%)q)qA*VTb1^*DM#e8Jkw=*U^Qm0^SoZr7fff^ zUunmoR=G=!WU*R~u<=u@o7$~VV84~9Z{p5ur*Jz`?Zv@hHSza_%GK=hQ<+Cxsy^3x zp#+_FNR9TCH12MuEzqpYC;fr5P}k@*IaG2C0)_I(@syKg1Fz8OY8il0RIr$YM#K32 zM14Y}8EOAS$=KX*j8-@95Q!RD$FWm=55j(Zc zKHIOBd@wiy{TFxjKT7#$FkqqLj*jgv=b1DFM%>a|-`4?{8nkosMVSDFo`FJ@WY2~S z^=P3YnH>E;*AvcePaqNX^GkOs(`j(~mIKz!s`1|R1p2%XwKwF+Bv%unsiC8F_GH8@cMZ2A7YoI7!5!q$Br{9yIVwiJLBdrT$jt|mbpy86QB7nx;s-T+5wM>I|S5T zK^<;k5nL~3^C5&QYQw=GOK-zt-CmqZe>q_Ueq93wJ>%pJ3)nk=q`bTen(Wt=iI`)} z_Qz6AK2ZRGk;gK714ANmeNm9<)z%L$dXI3dw_k@+v_Gme`UsWijgR(Uu7sp9#779Z z4OzL;BT(q_z6`PLf~e=aMpAtK-sI!!s#WayWU-F-V#zvPIySg#)a;cvr{vjGbJ)V6 zb?e4)G0@85Y;!HQWuq&6Z2LPwJdekq?}ghb61|Aycyt3~;0ZuU6c z119FUW*Z*ZX&_~ksoVShmy|nTYOZzFfyt3mj>fM|ozybG#*%myz(f%qj&({`7U6Bk zjX#GQ`s#MFphxf)AQmhP)M@3^C8j>KU?qh5nH5Vu0NGMDcD7QL5*l0Q;Eo!jx+Anc zHHsC(LQem(4Iv=>PnjxQEIH?#3;UH+)%;ZwoEwsRN-r?FlGM>Ho79R-=P8q6cJZ-A zTvlFT-)@!G*gFIgD^~GCTwT7yWkwYh>12aIcFL!!dRxfgqp134LWJkaSv>ti+*kz~ zZT|b_<88h~&YOnF;y+y+?_G!kL~MD4LUx>USp5mJnTI(Or6sJUz`y(JcQATC8uzR< z`!Q-?8D-&kb6C-NJyFn{AeWi&CBEnJc{R3fS}7qmRCiWG9g3^BsNG(9Onm zyDb9VV164`(GcpcQLs^`6QuC6@|5T-EA8{JGjJ5n^=SzAs^;MZ1;eSo)2I9saYl+< zdOph=JCC2u9JS^m(-x@ot&D=4ng_`Jv z)C@Xs$L*=1eQ;QpgNT5-^vf><`(_5{=9~QNm=f?-h>h0eb&Gz$4n;=QwO;OU|1VA7 zzsFT>O+DCcw-1AC_s9MmPl6#A9W5_oL5eK{Tm;-R2f?IjE1gJ>T(pmJbIT-uB4jUz zw+TK~gJ0>ZRR~eaMHN6x8|%EZPqUh55%_Wc2bc*s0%MZ{3u?9Xo9?(F0-UDMJx?fb zJ{sA~;!EQh>zC%QCoFhFWMtsV#XWs`p7=ro0@5qhd4Do7T9D$mX2S5X!kfr?ug&4I zW6u3&(u`Mna{A*?AUbdhvrHRcNO)RbADmoA(7SL5dN^cfI^{@mC}P443QN968KJ~r zcT=Pd*rnQi$OYe`3dbpbeLAIfg%^1$A9MD?G~HkAe<@xp@2nr-TC3r;9t;XqPuF{P z{P9Qedr_v}U=+_7o{_G3oS2bGVr08a*BZ5LSYC)d%*K7rD(JL4B$|V?Hok)OZ>FcU z{}55NB-TR7(R9Fn7xW?tHqy!&Bh0}|4&v-WCOowFDwueU>hIY$ipX#_VXkoSQY(C^ z2U8Vd`W-c*bXyraRYYlpp=e##b2fN?B%PAXI9VdXrli?Csxa1~o45 zl^Iy1J*KgGXbMEw5EX3ziI6IKR_^{+S1oPjUPHWhRK@dks%NzJ;$AL9gLK(eaS1(qcdW zlBf(@BN_~rA`;W9&dzGmC!7t-X{xy@WZrDDYE-|<*#p*gsi_|gtciBBsRD7NbLPcK zO#l6{%8AA%4Y|usG^S!@YO?PHDiy4gO|0;XFj3Ur$M~e1CEF)bmGuTQp zpG<5S{X{juBCOSPmNV2HrAqg!S1dIC{l=cZ$F20GhD!u?h!@BShvf!OMMx*tzb72v zWEuln=@{GPIvVRN(1wuDhCRF0yDuoq*ZC+a>&#$%E6}6MPZ_?TTFj^k$c)#R#H0Rh zkfp)mE0fi)7@HIAiI@Ps4N9SK>ACjWhQjS~CFMb63dQ+j(Yx-AFSgkImWDb|myj6`6GBE6lU^^o{_iJ7>Gc8sf$#p) zTLsU`vpeo!2%I;GSP3YOP-sgCXEkAmy~k0QgL4B8K=DT?Tqpd)7KQwZaoPP8y07K@ zberK0CIx_!Kp$A-nHkA#7RkJ0YSqfW@|#2aljQA_Cz{|yj-u6rzRhDPI77_1=Zi5# z@vXluz5R#BD(UY@SxP6k>*eDzz7$yajU*y2-2JIfZzVAm*N`BJ+RwoWl|fXkEz=BaoZM7m;}Id*FmLt!*fGQ9V(Rp2?wt^v^2_hjWD(hvB9~ z#TF6zL}Q@kfszsUTP_2m({FqRLk-SzmfuY-M*Lc-i}+0n-QD!QAVm>5R6l?ze7Uv4 zuwHcW8rA`o_bOq)U79@$2At)6%D<-?3n5~a#ooD5DK0Z%V>e!ewcG2qQX3@cdcM}~ zoRYm_ueV@Y6*w4;=h%eFSTHg)Lqc-Zsqvo5fx~1%_w#(}5rXlO=8+R>b}sMI1+b?| z)=+*lrhCA>dE@)0oWziK#xzXooN5Tb|K?ik4BB_1n7Q#Q>bI0Y)8{?uhGhgqNw8jh z0=^5_)r2LVscD7%%&@1lkea~JL>ag&m{RnL#8{ZVc}F%{QF+?hDh|%b7Y=wf&`_Lem@pgR2;#m-3Saq3(;Me zpWYV%`r$xNMdY?H(v5G^`#xNbdUE85>=V{#b!$s1K&;U5{!2sz`(hLoCGX;rC>Y>6 z^(&D5-yG=@!iT;=jm~P4Rbq#~u*a;>7!18pnYHr^c{TPv=jq<64DHr!-mdg0w3{8$ zdQE2^%ejnUr?ib{&##KqhlQ}t!g(L6nwq+9|CDZK_OYhOb}2xyMsv>fZISsIte`xV z48W}9nBKHIX3(f)yJ2xs{t=1}U)ufZA``)mShyTR(SL9YM{UpwW0t;&S9xUk@Y#w` zqoCmB?sEB0L`<>~v&2zJu#_C6crz?eV3}@1!a`!0fgp*Cp7X z-ig~SVdX_TDJUvd?hHyRbV_wF_HTLFy6JY+FHkI6Olr(aFq_$#=!>pr)6A9KHJ}#O z@~t^E3f*e@>ou6=xV0Sh3tmq&E*4ej;}#jke|gXZzM*iwezdHY)cjvg$J_ZKSQa`{ z^;*DWsq-v*ZE?QWIQ?sU>&Naw5J`E)jmbih)CE909P)zCcAyihYSgS zDLHqeb+g5(Ox62!^R2s9pl6$=?>Bp}Mg+^6|J(dr;h>iMl|Xuy?G`zus_Nf0%WJZ( zRU}W$R&t3UK7=@S;b%p(C3|;17|MKyz1oTIn0=WGIKkPtOuqZgvhL{j>>{oG4Q?3} zr?AwX=qDrOx*BRWEG9YPywZszKR-tlew{G_;ine5e0s|i@=hj?0WuF1Bi@wM1v>aw zHyYlNQIo-Uvjsy z$lpHY3r@twW%Pg)rI`-ggSR;oQ~u_dUS|rr#y6~yFc0KM+mpfF7)Z!qpac6rbXQ^A zcbCa37L_ux1P^}!ps*&A_?LY|-D_FTi{3Y*!ZqOxBjKdWE4Tj&6F0n@5+_Zx< z##6j6c{{e*uQ=E-ArbAP@Z9>APsKp-p`eBW{}{2XYmXOWbJqnt8NIh5*aNwRUebj31hb;o1XlA*}_ zIFhumR!P6@JC#&)&4ow9FrRO7Y4H_SXI9=@BM(2kxhY!tr1+_5!T=vRDP_qTag(6` E0cW*&lK=n! literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/text-pitch-scaling/line-half-roll/style.json b/metrics/integration/render-tests/text-pitch-scaling/line-half-roll/style.json new file mode 100644 index 000000000..b8efaa4e1 --- /dev/null +++ b/metrics/integration/render-tests/text-pitch-scaling/line-half-roll/style.json @@ -0,0 +1,65 @@ +{ + "version": 8, + "metadata": { + "test": { + "collisionDebug": true, + "height": 512 + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "zoom": 16, + "pitch": 30, + "roll": 180, + "sources": { + "maplibre": { + "type": "vector", + "maxzoom": 14, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "road", + "type": "line", + "source": "maplibre", + "source-layer": "road", + "paint": { + "line-color": "#888", + "line-width": 1 + } + }, + { + "id": "text", + "type": "symbol", + "source": "maplibre", + "source-layer": "road_label", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 60, + "text-rotation-alignment": "map", + "text-pitch-alignment": "auto", + "text-field": "{class}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + }, + "paint": { + "text-opacity": 1 + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-line/expected.png b/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-line/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..131832bbffb98e34a4818c04ade58794b1c716e4 GIT binary patch literal 973 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)Ffg})cyABJ zu9(Nn!2aR>?>+r;Vs?P%bM!m@k4TQ)rAnfyKHeC56W@70wEgGK#kj|=1l7%rWm z`?*jsEP?gh;qVIP$6AX-_J$;JhCMQp>G#~zHiZ-O3Z!NHXcT~jg5O>xp#c$W0q+@#) zH8c9GS{d}`okdKC<{IbYc9XN#DArv*B(n4%O5dWT2*<-Cp?kqe77Xm*RUkseGcwt0#+;+ ze!H3F`D4{TN@*FZ=8L^MB**iJ$EJbP(0OHCRL0_N!", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/expected.png b/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e732c1bb0f40630da200eb410b467c5899f321 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)FfbZ{cyA9b z%nD^>Xn6R)YMNTr#6%I39SH`#Pk%Br<=*fSJ3Ps*;VgS$#-@mt)ST)#j|gzgez4g>kYW$y9EXN8-uwvV-1 zyD#+k)xTNWqt@G*1a4mTw(#p3Z}+QirQ4Toe&t&la(6}9s=KcOzwZ4Nv3D)djJ288 zEA7HJPumwJeYJnuxmA<1)>j4HUEQ}j{Oa;o<-+FO(N}k8t@*b4@GFzBQ)#$80+cB* Z)nj-Stiv{ClWR0cp{J{#%Q~loCIH#Hl2iZy literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/style.json b/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/style.json new file mode 100644 index 000000000..7255383f2 --- /dev/null +++ b/metrics/integration/render-tests/text-roll-alignment/auto-symbol-placement-point/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 128 + } + }, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "text-rotation-alignment": "auto", + "text-field": "-->", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-line/expected.png b/metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-line/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..131832bbffb98e34a4818c04ade58794b1c716e4 GIT binary patch literal 973 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)Ffg})cyABJ zu9(Nn!2aR>?>+r;Vs?P%bM!m@k4TQ)rAnfyKHeC56W@70wEgGK#kj|=1l7%rWm z`?*jsEP?gh;qVIP$6AX-_J$;JhCMQp>G#~zHiZ-O3Z!NHXcT~jg5O>xp#c$W0q+@#) zH8c9GS{d}`okdKC<{IbYc9XN#DArv*B(n4%O5dWT2*<-Cp?kqe77Xm*RUkseGcwt0#+;+ ze!H3F`D4{TN@*FZ=8L^MB**iJ$EJbP(0OHCRL0_N!", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-point/expected.png b/metrics/integration/render-tests/text-roll-alignment/map-symbol-placement-point/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a95f047f919b5550eb209763c08af0bd98a4a1ee GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)Ffe+7cyA9b z%(_y@aNy(lyZ@xp1a#QFm1fj19i6J(v_`@wY4?j{9p5jaIo@jBj$$>ewt5)?6B`|K zj9;)cb8v<6{;su`_nf3cB>HK-?Mc4ajEAd@^VIh1Z0NRq^T<-p;;~wCbb@&Gj;_x? zI=m~g873FLP41N4b4Ym40o^wbW-|I5EA8;NIK+0pkgt5>F>B@-J=x8@_OZuPZOl4$ z{&^>7_h6~xnWt=ekGS$4bHwu`8EsPy`_bcG(Yu_@$atMK7N}GS= z<=^=2a_7M_J?WjlEsn4qXEQUOrW$tRP_>NoojVrWHXhRC`{34A%zm9%PkB!I!*0*; Y`HVLInaO^", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/expected.png b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3602942a56098360821f36acaf9a327685a940ad GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)Ffc2FcyAB- zXIy1vVE=Ib{olAFX_KU#1O=aN-qpbp7W5^YchM4iv6WLY*)p%JToSCVc*whyTS>zv zg>w>T*@@Y10dC@noR-4bD+5+ftKYWsXOr9yzO@RspWUv$f8O1NC~$m6E}O>DkefYE ze{iaASrOzjRr`aA+;JwJ2Q6a3T`P+i*>4wjg zR(ElKY*{Aw=CG0P{YMSUtj;|UsWOniD^WBt^k{u4^E{`84@I6m67-FK!0_y$;hlcg zA6&0Du&ZxvaEj@eZNi((VCrk1WIOM8{tuz|$@a&#PH^&>dc9D{x*(urP7u`f*_uaP zxN;6!^etNFJ2gp3^;(j!SfYS?;Po^w!--aQhZXO1UERUG|AEG zLwut|v&ACbDOUcCQdJkbnPPWv?R|0Z{fcah_PUz>pAn6EZ!WNL&3(>jvwS7LYTlIs zzWhXez2ib_jtG586yNr6hFQQ1o9oG(T)g)m2E{B+l#zd&Y16a1P#}6Ed-E+1MKvSo z+aH;?CF&n@bzj-p0Rb}?NcS)D{CBZ~Tv`_GT3Ra>IK(>3GT-Q3Unn1U$iPl~RocAtg4u0W z+mhBU3(PrWuqvWs>JDk!3Tf-3@j$t}E2dWpInOOxl_(>+=8)E%#^xKXZ!J{LX>VEX zS|Q$C!N2?C>K7853)ELBLBOJ`Ul+Lh> zEWKvZ;=hr9^9Dxk3$1l?#3nB?FplXqz0u*g#%2DG`>U4SPYW^WdC0qG(Mz7V%O$FE zNA|urR@c+v8ECq9mjQn$uksFAQxU1QfCR&^WXZOVym3eC=7<7K`THzG>bU%j#hD`6 vAJqCDFxPZAFG|)qYIR0b5I?y1@E?o2`6B*IziCf^nTNsC)z4*}Q$iB}9foMW literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/style.json b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/style.json new file mode 100644 index 000000000..a895f27fb --- /dev/null +++ b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-line/style.json @@ -0,0 +1,47 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 128 + } + }, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "LineString", + "coordinates": [ + [ + -40, + 0 + ], + [ + 40, + 0 + ] + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 20, + "text-allow-overlap": true, + "text-rotation-alignment": "viewport", + "text-field": "-->", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/expected.png b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e732c1bb0f40630da200eb410b467c5899f321 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSOr9=|Ar*{or0Om)FfbZ{cyA9b z%nD^>Xn6R)YMNTr#6%I39SH`#Pk%Br<=*fSJ3Ps*;VgS$#-@mt)ST)#j|gzgez4g>kYW$y9EXN8-uwvV-1 zyD#+k)xTNWqt@G*1a4mTw(#p3Z}+QirQ4Toe&t&la(6}9s=KcOzwZ4Nv3D)djJ288 zEA7HJPumwJeYJnuxmA<1)>j4HUEQ}j{Oa;o<-+FO(N}k8t@*b4@GFzBQ)#$80+cB* Z)nj-Stiv{ClWR0cp{J{#%Q~loCIH#Hl2iZy literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/style.json b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/style.json new file mode 100644 index 000000000..e5a3a7b8f --- /dev/null +++ b/metrics/integration/render-tests/text-roll-alignment/viewport-symbol-placement-point/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 128, + "height": 128 + } + }, + "roll": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "text-rotation-alignment": "viewport", + "text-field": "-->", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +} diff --git a/platform/android/MapLibreAndroid/src/cpp/map/camera_position.cpp b/platform/android/MapLibreAndroid/src/cpp/map/camera_position.cpp index 2d346562d..b0e3bcce8 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map/camera_position.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/map/camera_position.cpp @@ -1,5 +1,7 @@ #include "camera_position.hpp" #include "../geometry/lat_lng.hpp" +#include +#include namespace mbgl { namespace android { @@ -8,12 +10,19 @@ jni::Local> CameraPosition::New(jni::JNIEnv& env, mbgl::CameraOptions options, float pixelRatio) { static auto& javaClass = jni::Class::Singleton(env); - static auto constructor = - javaClass.GetConstructor, double, double, double, jni::Array>(env); + static auto constructor = javaClass.GetConstructor, + double, + double, + double, + double, + double, + double, + jni::Array>(env); // wrap LatLng values coming from core auto center = options.center.value(); center.wrap(); + double centerAltitude = options.centerAltitude.value_or(0); // convert bearing, measured in radians counterclockwise from true north. // Wrapped to [−π rad, π rad). Android binding from 0 to 360 degrees @@ -27,6 +36,8 @@ jni::Local> CameraPosition::New(jni::JNIEnv& env, // convert tilt, core ranges from [0 rad, 1,0472 rad], android ranges from 0 to 60 double tilt_degrees = options.pitch.value_or(0); + double roll_degrees = options.roll.value_or(0); + double fov_degrees = options.fov.value_or(util::rad2deg(util::DEFAULT_FOV)); std::vector paddingVect; auto insets = options.padding.value_or(EdgeInsets{0, 0, 0, 0}); @@ -37,8 +48,16 @@ jni::Local> CameraPosition::New(jni::JNIEnv& env, paddingVect.push_back(insets.bottom() * pixelRatio); padding.SetRegion>(env, 0, paddingVect); - return javaClass.New( - env, constructor, LatLng::New(env, center), options.zoom.value_or(0), tilt_degrees, bearing_degrees, padding); + return javaClass.New(env, + constructor, + LatLng::New(env, center), + centerAltitude, + options.zoom.value_or(0), + tilt_degrees, + bearing_degrees, + roll_degrees, + fov_degrees, + padding); } mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env, @@ -50,11 +69,15 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env, static auto tilt = javaClass.GetField(env, "tilt"); static auto zoom = javaClass.GetField(env, "zoom"); static auto paddingField = javaClass.GetField>(env, "padding"); + static auto centerAltitude = javaClass.GetField(env, "centerAltitude"); + static auto roll = javaClass.GetField(env, "roll"); + static auto fov = javaClass.GetField(env, "fov"); auto padding = position.Get(env, paddingField); auto center = LatLng::getLatLng(env, position.Get(env, target)); return mbgl::CameraOptions{center, + position.Get(env, centerAltitude), padding && padding.Length(env) == 4 ? EdgeInsets{padding.Get(env, 1) * pixelRatio, padding.Get(env, 0) * pixelRatio, padding.Get(env, 3) * pixelRatio, @@ -63,7 +86,9 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env, {}, position.Get(env, zoom), position.Get(env, bearing), - position.Get(env, tilt)}; + position.Get(env, tilt), + position.Get(env, roll), + position.Get(env, fov)}; } void CameraPosition::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt index 9a9c7fb72..d07a3d18c 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt @@ -24,16 +24,21 @@ class CameraPosition * Constructs a CameraPosition. * * @param target The target location to align with the center of the screen. + * @param centerAltitude The altitude of the target location to align with the center of the + * screen, in meters above sea level. * @param zoom Zoom level at target. See zoom(float) for details of restrictions. * @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float) * for details of restrictions. * @param bearing Direction that the camera is pointing in, in degrees clockwise from north. * This value will be normalized to be within 0 degrees inclusive and 360 degrees * exclusive. + * @param roll Roll of the camera, in degrees clockwise. + * @param fov Vertical field of view of the camera, in degrees. * @param padding Padding in pixels. Specified in left, top, right, bottom order. * @throws NullPointerException if target is null * @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive. - */ @Keep internal constructor( + */ + @Keep internal constructor( /** * The location that the camera is pointing at. @@ -42,6 +47,13 @@ class CameraPosition @JvmField val target: LatLng?, + /** + * The altitude of the location that the camera is pointing at, in meters above sea level. + */ + @field:Keep + @JvmField + val centerAltitude: Double, + /** * Zoom level near the center of the screen. See [Builder.zoom] for the definition of the camera's * zoom level. @@ -65,6 +77,20 @@ class CameraPosition @JvmField val bearing: Double, + /** + * Roll of the camera, in degrees clockwise. + */ + @field:Keep + @JvmField + val roll: Double, + + /** + * Vertical field of view of the camera, in degrees. + */ + @field:Keep + @JvmField + val fov: Double, + /** * Padding in pixels. Specified in left, top, right, bottom order. * See [Builder.padding] for the definition of the camera's padding. @@ -75,6 +101,22 @@ class CameraPosition ) : Parcelable { + /** + * Constructs a CameraPosition. + * + * @param target The target location to align with the center of the screen. + * @param zoom Zoom level at target. See zoom(float) for details of restrictions. + * @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float) + * for details of restrictions. + * @param bearing Direction that the camera is pointing in, in degrees clockwise from north. + * This value will be normalized to be within 0 degrees inclusive and 360 degrees + * exclusive. + * @param padding Padding in pixels. Specified in left, top, right, bottom order. + * @throws NullPointerException if target is null + * @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive. + */ + @Keep internal constructor(target: LatLng?, zoom: Double, tilt: Double, bearing: Double, padding: DoubleArray?) : this(target, 0.0, zoom, tilt, bearing, 0.0, MapLibreConstants.DEFAULT_FOV, padding) + /** * Constructs a CameraPosition. * @@ -112,7 +154,10 @@ class CameraPosition override fun writeToParcel(out: Parcel, flags: Int) { out.writeDouble(bearing) out.writeParcelable(target, flags) + out.writeDouble(centerAltitude) out.writeDouble(tilt) + out.writeDouble(roll) + out.writeDouble(fov) out.writeDouble(zoom) if (padding != null) { val length = padding.size @@ -131,7 +176,7 @@ class CameraPosition * @return A String with CameraPosition information. */ override fun toString(): String { - return ("Target: " + target + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Padding:" + Arrays.toString(padding)) + return ("Target: " + target + ", Center Altitude:" + centerAltitude + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Roll:" + roll + ", FOV:" + fov + ", Padding:" + Arrays.toString(padding)) } /** @@ -152,10 +197,16 @@ class CameraPosition val cameraPosition = other as CameraPosition if (target != null && target != cameraPosition.target) { return false + } else if (centerAltitude != cameraPosition.centerAltitude) { + return false } else if (zoom != cameraPosition.zoom) { return false } else if (tilt != cameraPosition.tilt) { return false + } else if (roll != cameraPosition.roll) { + return false + } else if (fov != cameraPosition.fov) { + return false } else if (bearing != cameraPosition.bearing) { return false } else if (!Arrays.equals(padding, cameraPosition.padding)) { @@ -178,8 +229,14 @@ class CameraPosition temp = java.lang.Double.doubleToLongBits(bearing) result = (temp xor (temp ushr 32)).toInt() result = 31 * result + (target?.hashCode() ?: 0) + temp = java.lang.Double.doubleToLongBits(centerAltitude) + result = 31 * result + (temp xor (temp ushr 32)).toInt() temp = java.lang.Double.doubleToLongBits(tilt) result = 31 * result + (temp xor (temp ushr 32)).toInt() + temp = java.lang.Double.doubleToLongBits(roll) + result = 31 * result + (temp xor (temp ushr 32)).toInt() + temp = java.lang.Double.doubleToLongBits(fov) + result = 31 * result + (temp xor (temp ushr 32)).toInt() temp = java.lang.Double.doubleToLongBits(zoom) result = 31 * result + (temp xor (temp ushr 32)).toInt() result = 31 * result + Arrays.hashCode(padding) @@ -192,7 +249,10 @@ class CameraPosition class Builder { private var bearing = -1.0 private var target: LatLng? = null + private var centerAltitude = -1.0 private var tilt = -1.0 + private var roll = -1.0 + private var fov = -1.0 private var zoom = -1.0 private var padding: DoubleArray? = null @@ -210,7 +270,10 @@ class CameraPosition if (previous != null) { bearing = previous.bearing target = previous.target + centerAltitude = previous.centerAltitude tilt = previous.tilt + roll = previous.roll + fov = previous.fov zoom = previous.zoom padding = previous.padding } @@ -227,7 +290,10 @@ class CameraPosition val lat = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLat, 0.0f).toDouble() val lng = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLng, 0.0f).toDouble() target = LatLng(lat, lng) + centerAltitude = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraCenterAltitude, 0.0f).toDouble() tilt = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTilt, 0.0f).toDouble() + roll = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraRoll, 0.0f).toDouble() + fov = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraFov, 36.86989764584402f).toDouble() zoom = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraZoom, 0.0f).toDouble() } } @@ -241,7 +307,10 @@ class CameraPosition if (update != null) { bearing = update.bearing target = update.target + centerAltitude = update.centerAltitude tilt = update.tilt + roll = update.roll + fov = update.fov zoom = update.zoom padding = update.padding } @@ -287,6 +356,17 @@ class CameraPosition return this } + /** + * Set the altitude of the location where the camera is pointing at in meters above sea level + * + * @param centerAltitude altitude of the location where the camera is pointing at + * @return this + */ + fun centerAltitude(centerAltitude: Double): Builder { + this.centerAltitude = centerAltitude + return this + } + /** * Set the tilt of the camera in degrees * @@ -302,6 +382,28 @@ class CameraPosition return this } + /** + * Set the roll of the camera in degrees + * + * @param roll Roll value of the camera + * @return this + */ + fun roll(roll: Double): Builder { + this.roll = roll + return this + } + + /** + * Set the vertical field of view of the camera in degrees + * + * @param fov Vertical field of view of the camera + * @return this + */ + fun fov(fov: Double): Builder { + this.fov = fov + return this + } + /** * Set the zoom of the camera * @@ -375,7 +477,10 @@ class CameraPosition } else { parcel.readParcelable(LatLng::class.java.classLoader) } + val centerAltitude = parcel.readDouble() val tilt = parcel.readDouble() + val roll = parcel.readDouble() + val fov = parcel.readDouble() val zoom = parcel.readDouble() var padding: DoubleArray? = null val paddingSize = parcel.readInt() @@ -385,7 +490,7 @@ class CameraPosition padding[i] = parcel.readDouble() } } - return CameraPosition(target, zoom, tilt, bearing, padding) + return CameraPosition(target, centerAltitude, zoom, tilt, bearing, roll, fov, padding) } override fun newArray(size: Int): Array { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraUpdateFactory.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraUpdateFactory.kt index c14e1e8e7..4eb5ceb1f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraUpdateFactory.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraUpdateFactory.kt @@ -22,7 +22,7 @@ object CameraUpdateFactory { */ @JvmStatic fun newCameraPosition(cameraPosition: CameraPosition): CameraUpdate { - return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom, cameraPosition.padding) + return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.centerAltitude, cameraPosition.tilt, cameraPosition.roll, cameraPosition.fov, cameraPosition.zoom, cameraPosition.padding) } /** @@ -34,7 +34,7 @@ object CameraUpdateFactory { */ @JvmStatic fun newLatLng(latLng: LatLng): CameraUpdate { - return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, null) + return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, null) } /** @@ -133,7 +133,7 @@ object CameraUpdateFactory { */ @JvmStatic fun newLatLngZoom(latLng: LatLng, zoom: Double): CameraUpdate { - return CameraPositionUpdate(-1.0, latLng, -1.0, zoom, null) + return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, zoom, null) } /** @@ -150,7 +150,7 @@ object CameraUpdateFactory { */ @JvmStatic fun newLatLngPadding(latLng: LatLng, left: Double, top: Double, right: Double, bottom: Double): CameraUpdate { - return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, doubleArrayOf(left, top, right, bottom)) + return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, doubleArrayOf(left, top, right, bottom)) } /** @@ -217,7 +217,7 @@ object CameraUpdateFactory { */ @JvmStatic fun bearingTo(bearing: Double): CameraUpdate { - return CameraPositionUpdate(bearing, null, -1.0, -1.0, null) + return CameraPositionUpdate(bearing, null, -1.0, -1.0, -1.0, -1.0, -1.0, null) } /** @@ -228,7 +228,7 @@ object CameraUpdateFactory { */ @JvmStatic fun tiltTo(tilt: Double): CameraUpdate { - return CameraPositionUpdate(-1.0, null, tilt, -1.0, null) + return CameraPositionUpdate(-1.0, null, -1.0, tilt, -1.0, -1.0, -1.0, null) } /** @@ -244,7 +244,7 @@ object CameraUpdateFactory { */ @JvmStatic fun paddingTo(padding: DoubleArray?): CameraUpdate { - return CameraPositionUpdate(-1.0, null, -1.0, -1.0, padding) + return CameraPositionUpdate(-1.0, null, -1.0, -1.0, -1.0, -1.0, -1.0, padding) } /** @@ -265,7 +265,7 @@ object CameraUpdateFactory { // // CameraUpdate types // - class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val tilt: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate { + class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val centerAltitude: Double, val tilt: Double, val roll: Double, val fov: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate { override fun getCameraPosition(maplibreMap: MapLibreMap): CameraPosition { if (target == null) { @@ -289,9 +289,18 @@ object CameraUpdateFactory { if (java.lang.Double.compare(that.tilt, tilt) != 0) { return false } + if (java.lang.Double.compare(that.roll, roll) != 0) { + return false + } + if (java.lang.Double.compare(that.fov, fov) != 0) { + return false + } if (java.lang.Double.compare(that.zoom, zoom) != 0) { return false } + if (java.lang.Double.compare(that.centerAltitude, centerAltitude) != 0) { + return false + } return if (if (target != null) target != that.target else that.target != null) { false } else { @@ -305,8 +314,14 @@ object CameraUpdateFactory { temp = java.lang.Double.doubleToLongBits(bearing) result = (temp xor (temp ushr 32)).toInt() result = 31 * result + (target?.hashCode() ?: 0) + temp = java.lang.Double.doubleToLongBits(centerAltitude) + result = 31 * result + (temp xor (temp ushr 32)).toInt() temp = java.lang.Double.doubleToLongBits(tilt) result = 31 * result + (temp xor (temp ushr 32)).toInt() + temp = java.lang.Double.doubleToLongBits(roll) + result = 31 * result + (temp xor (temp ushr 32)).toInt() + temp = java.lang.Double.doubleToLongBits(fov) + result = 31 * result + (temp xor (temp ushr 32)).toInt() temp = java.lang.Double.doubleToLongBits(zoom) result = 31 * result + (temp xor (temp ushr 32)).toInt() result = 31 * result + Arrays.hashCode(padding) @@ -314,7 +329,7 @@ object CameraUpdateFactory { } override fun toString(): String { - return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", tilt=" + tilt + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}') + return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", centerAltitude=" + centerAltitude + ", tilt=" + tilt + ", roll=" + roll + ", fov=" + fov + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}') } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/constants/MapLibreConstants.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/constants/MapLibreConstants.java index 1b3778989..10173ca64 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/constants/MapLibreConstants.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/constants/MapLibreConstants.java @@ -168,6 +168,11 @@ public class MapLibreConstants { */ public static final double MINIMUM_DIRECTION = 0; + /** + * The current default vertical field of view (matches mbgl::util::DEFAULT_FOV) + */ + public static final double DEFAULT_FOV = 36.86989764584402; + /** * The currently used minimum scale factor to clamp to when a quick zoom gesture occurs * diff --git a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml index eb367bd91..e38086bdb 100644 --- a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml +++ b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml @@ -21,9 +21,12 @@ + + + diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/camera/CameraPositionTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/camera/CameraPositionTest.kt index 9fb17a7fe..2847ddd92 100644 --- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/camera/CameraPositionTest.kt +++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/camera/CameraPositionTest.kt @@ -85,11 +85,12 @@ class CameraPositionTest : BaseTest() { fun testToString() { val latLng = LatLng(1.0, 2.0) val cameraPosition = - CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0)) + CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0)) Assert.assertEquals( "toString should match", - "Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], Zoom:3.0, " + - "Bearing:5.0, Tilt:4.0, Padding:[0.0, 500.0, 0.0, 0.0]", + "Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], " + + "Center Altitude:9.0, Zoom:3.0, Bearing:5.0, Tilt:4.0, Roll:6.0, FOV:7.0, " + + "Padding:[0.0, 500.0, 0.0, 0.0]", cameraPosition.toString() ) } @@ -98,8 +99,8 @@ class CameraPositionTest : BaseTest() { fun testHashcode() { val latLng = LatLng(1.0, 2.0) val cameraPosition = - CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0)) - Assert.assertEquals("hashCode should match", -420915327, cameraPosition.hashCode().toLong()) + CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0)) + Assert.assertEquals("hashCode should match", -1086105727, cameraPosition.hashCode().toLong()) } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/camera/CameraTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/camera/CameraTest.java index 15dff27e7..b67e9c1f7 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/camera/CameraTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/camera/CameraTest.java @@ -100,7 +100,7 @@ public abstract class CameraTest extends BaseTest { invoke(maplibreMap, (uiController, maplibreMap) -> { final LatLng moveTarget = new LatLng(1.0000000001, 1.0000000003); final float moveZoom = 15.5f; - final float moveTilt = 45.5f; + final float moveTilt = 35.5f; final float moveBearing = 12.5f; final double[] movePadding = new double[] {0, 500, 350, 1}; diff --git a/platform/glfw/main.cpp b/platform/glfw/main.cpp index eba3a2bca..4b81a0bc0 100644 --- a/platform/glfw/main.cpp +++ b/platform/glfw/main.cpp @@ -55,9 +55,13 @@ int main(int argc, char* argv[]) { argumentParser, "directory", "Action journal log directory", {"actionJournalDir"}); args::ValueFlag lonValue(argumentParser, "degrees", "Longitude", {'x', "lon"}); args::ValueFlag latValue(argumentParser, "degrees", "Latitude", {'y', "lat"}); + args::ValueFlag altValue(argumentParser, "degrees", "Altitude", {'a', "alt"}); + args::ValueFlag fovValue(argumentParser, "degrees", "FOV", {'F', "fov"}); args::ValueFlag zoomValue(argumentParser, "number", "Zoom level", {'z', "zoom"}); args::ValueFlag bearingValue(argumentParser, "degrees", "Bearing", {'b', "bearing"}); args::ValueFlag pitchValue(argumentParser, "degrees", "Pitch", {'p', "pitch"}); + args::ValueFlag rollValue(argumentParser, "degrees", "Roll", {'r', "roll"}); + args::ValueFlag maxPitchValue(argumentParser, "degrees", "Max Pitch", {'P', "maxPitch"}); try { argumentParser.ParseCLI(argc, argv); @@ -79,9 +83,13 @@ int main(int argc, char* argv[]) { settings.online = !offlineFlag; if (lonValue) settings.longitude = args::get(lonValue); if (latValue) settings.latitude = args::get(latValue); + if (altValue) settings.altitude = args::get(altValue); + if (fovValue) settings.fov = args::get(fovValue); + if (maxPitchValue) settings.maxPitch = args::get(maxPitchValue); if (zoomValue) settings.zoom = args::get(zoomValue); if (bearingValue) settings.bearing = args::get(bearingValue); if (pitchValue) settings.pitch = args::get(pitchValue); + if (rollValue) settings.roll = args::get(rollValue); const bool fullscreen = fullscreenFlag ? args::get(fullscreenFlag) : false; const bool benchmark = benchmarkFlag ? args::get(benchmarkFlag) : false; @@ -147,6 +155,8 @@ int main(int argc, char* argv[]) { clientOptions, actionJournalOptions); + map.setBounds(mbgl::BoundOptions().withMaxPitch(settings.maxPitch)); + backend.setMap(&map); if (!style.empty() && style.find("://") == std::string::npos) { @@ -155,9 +165,12 @@ int main(int argc, char* argv[]) { map.jumpTo(mbgl::CameraOptions() .withCenter(mbgl::LatLng{settings.latitude, settings.longitude}) + .withCenterAltitude(settings.altitude) .withZoom(settings.zoom) .withBearing(settings.bearing) - .withPitch(settings.pitch)); + .withPitch(settings.pitch) + .withRoll(settings.roll) + .withFov(settings.fov)); map.setDebug(mbgl::MapDebugOptions(settings.debug)); if (testDirValue) view->setTestDirectory(args::get(testDirValue)); @@ -237,15 +250,21 @@ int main(int argc, char* argv[]) { mbgl::CameraOptions camera = map.getCameraOptions(); settings.latitude = camera.center->latitude(); settings.longitude = camera.center->longitude(); + settings.altitude = *camera.centerAltitude; settings.zoom = *camera.zoom; settings.bearing = *camera.bearing; settings.pitch = *camera.pitch; + settings.roll = *camera.roll; + settings.fov = *camera.fov; settings.debug = mbgl::EnumType(map.getDebug()); settings.save(); mbgl::Log::Info(mbgl::Event::General, "Exit location: --lat=\"" + std::to_string(settings.latitude) + "\" --lon=\"" + - std::to_string(settings.longitude) + "\" --zoom=\"" + std::to_string(settings.zoom) + - "\" --bearing=\"" + std::to_string(settings.bearing) + "\""); + std::to_string(settings.longitude) + "\" --alt=\"" + std::to_string(settings.altitude) + + "\" --zoom=\"" + std::to_string(settings.zoom) + "\" --bearing=\"" + + std::to_string(settings.bearing) + "\" --roll=\"" + std::to_string(settings.roll) + + "\" --fov=\"" + std::to_string(settings.fov) + "\" --maxPitch=\"" + + std::to_string(settings.maxPitch) + "\""); view = nullptr; diff --git a/platform/glfw/settings_json.cpp b/platform/glfw/settings_json.cpp index 1355e16f2..fe369506b 100644 --- a/platform/glfw/settings_json.cpp +++ b/platform/glfw/settings_json.cpp @@ -1,5 +1,7 @@ #include "settings_json.hpp" #include +#include +#include namespace mbgl { @@ -12,9 +14,13 @@ void Settings_JSON::load() { if (file) { file >> longitude; file >> latitude; + file >> altitude; file >> zoom; file >> bearing; file >> pitch; + file >> roll; + file >> fov; + file >> maxPitch; file >> debug; file >> online; } @@ -25,9 +31,13 @@ void Settings_JSON::save() { if (file) { file << longitude << std::endl; file << latitude << std::endl; + file << altitude << std::endl; file << zoom << std::endl; file << bearing << std::endl; file << pitch << std::endl; + file << roll << std::endl; + file << fov << std::endl; + file << maxPitch << std::endl; file << debug << std::endl; file << online << std::endl; } @@ -36,9 +46,13 @@ void Settings_JSON::save() { void Settings_JSON::clear() { longitude = 0; latitude = 0; + altitude = 0; zoom = 0; bearing = 0; pitch = 0; + roll = 0; + fov = util::rad2deg(util::DEFAULT_FOV); + maxPitch = util::rad2deg(util::DEFAULT_PITCH_MAX); debug = 0; online = true; } diff --git a/platform/glfw/settings_json.hpp b/platform/glfw/settings_json.hpp index c89accb8a..93ac8897f 100644 --- a/platform/glfw/settings_json.hpp +++ b/platform/glfw/settings_json.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace mbgl { @@ -14,9 +16,13 @@ public: public: double longitude = 0; double latitude = 0; + double altitude = 0; double zoom = 0; double bearing = 0; double pitch = 0; + double roll = 0; + double fov = util::rad2deg(mbgl::util::DEFAULT_FOV); + double maxPitch = util::rad2deg(mbgl::util::DEFAULT_PITCH_MAX); bool axonometric = false; double xSkew = 0.0; double ySkew = 1.0; diff --git a/render-test/metadata.hpp b/render-test/metadata.hpp index 4eb48caf7..37efa7f0c 100644 --- a/render-test/metadata.hpp +++ b/render-test/metadata.hpp @@ -155,6 +155,7 @@ struct TestMetadata { bool ignoreProbing = true; mbgl::Size size{512u, 512u}; + std::optional maxPitch; float pixelRatio = 1.0f; double allowed = 0.00015; // diff std::string description; diff --git a/render-test/parser.cpp b/render-test/parser.cpp index 1bd2ad138..ca07021d8 100644 --- a/render-test/parser.cpp +++ b/render-test/parser.cpp @@ -510,6 +510,11 @@ TestMetadata parseTestMetadata(const TestPaths& paths) { } } + if (testValue.HasMember("maxPitch")) { + assert(testValue["maxPitch"].IsNumber()); + metadata.maxPitch = testValue["maxPitch"].GetFloat(); + } + if (testValue.HasMember("pixelRatio")) { assert(testValue["pixelRatio"].IsNumber()); metadata.pixelRatio = testValue["pixelRatio"].GetFloat(); @@ -618,6 +623,8 @@ const std::string setCenterOp("setCenter"); const std::string setZoomOp("setZoom"); const std::string setBearingOp("setBearing"); const std::string setPitchOp("setPitch"); +const std::string setRollOp("setRoll"); +const std::string setCenterAltitudeOp("setCenterElevation"); const std::string setFilterOp("setFilter"); const std::string setLayerZoomRangeOp("setLayerZoomRange"); const std::string setLightOp("setLight"); @@ -805,6 +812,24 @@ TestOperations parseTestOperations(TestMetadata& metadata) { ctx.getMap().jumpTo(mbgl::CameraOptions().withPitch(pitch)); return true; }); + } else if (operationArray[0].GetString() == setRollOp) { + // setRoll + assert(operationArray.Size() >= 2u); + assert(operationArray[1].IsNumber()); + double roll = operationArray[1].GetDouble(); + result.emplace_back([roll](TestContext& ctx) { + ctx.getMap().jumpTo(mbgl::CameraOptions().withRoll(roll)); + return true; + }); + } else if (operationArray[0].GetString() == setCenterAltitudeOp) { + // setCenterAltitude + assert(operationArray.Size() >= 2u); + assert(operationArray[1].IsNumber()); + double centerAltitude = operationArray[1].GetDouble(); + result.emplace_back([centerAltitude](TestContext& ctx) { + ctx.getMap().jumpTo(mbgl::CameraOptions().withCenterAltitude(centerAltitude)); + return true; + }); } else if (operationArray[0].GetString() == setFilterOp) { // setFilter assert(operationArray.Size() >= 3u); diff --git a/render-test/parser.hpp b/render-test/parser.hpp index a54af3434..61a6ba847 100644 --- a/render-test/parser.hpp +++ b/render-test/parser.hpp @@ -43,6 +43,7 @@ extern const std::string setCenterOp; extern const std::string setZoomOp; extern const std::string setBearingOp; extern const std::string setPitchOp; +extern const std::string setRollOp; extern const std::string setFilterOp; extern const std::string setLayerZoomRangeOp; extern const std::string setLightOp; diff --git a/render-test/runner.cpp b/render-test/runner.cpp index 9b1bb36b3..c476751fa 100644 --- a/render-test/runner.cpp +++ b/render-test/runner.cpp @@ -704,7 +704,11 @@ TestRunner::Impl::Impl(const TestMetadata& metadata, .withPixelRatio(metadata.pixelRatio) .withCrossSourceCollisions(metadata.crossSourceCollisions), resourceOptions, - clientOptions) {} + clientOptions) { + if (metadata.maxPitch) { + map.setBounds(BoundOptions().withMaxPitch(*metadata.maxPitch)); + } +} TestRunner::Impl::~Impl() {} diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index 6633b81e8..830f57417 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -65,6 +65,44 @@ namespace mbgl { * Steps 3 and 4 are done in the shaders for all labels. */ +void getTileSkewVectors(const TransformState& state, vec2& vecEast, vec2& vecSouth) { + const double cosRoll = cos(state.getRoll()); + const double sinRoll = sin(state.getRoll()); + const double cosPitch = cos(state.getPitch()); + const double cosBearing = cos(-state.getBearing()); + const double sinBearing = sin(-state.getBearing()); + vecSouth[0] = -cosBearing * cosPitch * sinRoll - sinBearing * cosRoll; + vecSouth[1] = -sinBearing * cosPitch * sinRoll + cosBearing * cosRoll; + const double vecSouthLen = std::hypot(vecSouth[0], vecSouth[1]); + if (vecSouthLen < 1.0e-9) { + vecSouth = {0, 0}; + } else { + vecSouth[0] /= vecSouthLen; + vecSouth[1] /= vecSouthLen; + } + vecEast[0] = cosBearing * cosPitch * cosRoll - sinBearing * sinRoll; + vecEast[1] = sinBearing * cosPitch * cosRoll + cosBearing * sinRoll; + const double vecEastLen = std::hypot(vecEast[0], vecEast[1]); + if (vecEastLen < 1.0e-9) { + vecEast = {0, 0}; + } else { + vecEast[0] /= vecEastLen; + vecEast[1] /= vecEastLen; + } +} + +mat4 getTileSkewMatrix(const TransformState& state) { + vec2 vecEast, vecSouth; + getTileSkewVectors(state, vecEast, vecSouth); + mat4 m; + matrix::identity(m); + m[0] = vecEast[0]; + m[1] = vecEast[1]; + m[4] = vecSouth[0]; + m[5] = vecSouth[1]; + return m; +} + /* * Returns a matrix for converting from tile units to the correct label coordinate space. */ @@ -78,7 +116,9 @@ mat4 getLabelPlaneMatrix(const mat4& posMatrix, if (pitchWithMap) { matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1); if (!rotateWithMap) { - matrix::rotate_z(m, m, state.getBearing()); + mat4 skew = getTileSkewMatrix(state); + matrix::invert(skew, skew); + matrix::multiply(m, m, skew); } } else { matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0); @@ -102,7 +142,7 @@ mat4 getGlCoordMatrix(const mat4& posMatrix, matrix::multiply(m, m, posMatrix); matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1); if (!rotateWithMap) { - matrix::rotate_z(m, m, -state.getBearing()); + matrix::multiply(m, m, getTileSkewMatrix(state)); } } else { matrix::scale(m, m, 1, -1, 1); diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 8b641267c..c24e6eb9a 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -62,6 +62,7 @@ void Transform::resize(const Size size) { double scale{state.getScale()}; double x{state.getX()}; double y{state.getY()}; + double z{state.getZ()}; double lat; double lon; @@ -80,7 +81,7 @@ void Transform::resize(const Size size) { return; } state.constrain(scale, x, y); - state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y)); + state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z)); observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate); } @@ -125,8 +126,11 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing(); double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch(); + double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView(); + double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude()); + double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll(); - if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch)) { + if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov)) { if (animation.transitionFinishFn) { animation.transitionFinishFn(); } @@ -146,6 +150,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& startLatLng.unwrapForShortestPath(latLng); } } + const double startCenterAlt = state.getCenterAltitude(); const Point startPoint = Projection::project(startLatLng, state.getScale()); const Point endPoint = Projection::project(latLng, state.getScale()); @@ -153,6 +158,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& // Constrain camera options. zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom()); pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch()); + fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView()); // Minimize rotation by taking the shorter path around the circle. bearing = _normalizeAngle(bearing, state.getBearing()); @@ -161,6 +167,8 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& const double startZoom = state.getZoom(); const double startBearing = state.getBearing(); const double startPitch = state.getPitch(); + const double startRoll = state.getRoll(); + const double startFov = state.getFieldOfView(); state.setProperties(TransformStateProperties() .withPanningInProgress(unwrappedLatLng != startLatLng) .withScalingInProgress(zoom != startZoom) @@ -175,6 +183,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& LatLng frameLatLng = Projection::unproject(framePoint, state.zoomScale(startZoom)); double frameZoom = util::interpolate(startZoom, zoom, t); state.setLatLngZoom(frameLatLng, frameZoom); + state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, t)); if (bearing != startBearing) { state.setBearing(util::wrap(util::interpolate(startBearing, bearing, t), -pi, pi)); } @@ -190,6 +199,12 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& if (pitch != startPitch || maxPitch < startPitch) { state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, t))); } + if (roll != startRoll) { + state.setRoll(util::interpolate(startRoll, roll, t)); + } + if (fov != startFov) { + state.setFieldOfView(util::interpolate(startFov, fov, t)); + } }, duration); } @@ -212,11 +227,14 @@ void Transform::flyTo(const CameraOptions& inputCamera, const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets()); const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped(); - + const double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude()); double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing(); double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch(); + double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll(); + double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView(); - if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || state.getSize().isEmpty()) { + if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov) || + state.getSize().isEmpty()) { if (animation.transitionFinishFn) { animation.transitionFinishFn(); } @@ -226,6 +244,7 @@ void Transform::flyTo(const CameraOptions& inputCamera, // Determine endpoints. LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped(); startLatLng.unwrapForShortestPath(latLng); + const double startCenterAlt = state.getCenterAltitude(); const Point startPoint = Projection::project(startLatLng, state.getScale()); const Point endPoint = Projection::project(latLng, state.getScale()); @@ -233,6 +252,7 @@ void Transform::flyTo(const CameraOptions& inputCamera, // Constrain camera options. zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom()); pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch()); + fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView()); // Minimize rotation by taking the shorter path around the circle. bearing = _normalizeAngle(bearing, state.getBearing()); @@ -240,6 +260,8 @@ void Transform::flyTo(const CameraOptions& inputCamera, const double startZoom = state.scaleZoom(state.getScale()); const double startBearing = state.getBearing(); const double startPitch = state.getPitch(); + const double startRoll = state.getRoll(); + const double startFov = state.getFieldOfView(); /// w₀: Initial visible span, measured in pixels at the initial scale. /// Known henceforth as a screenful. @@ -353,6 +375,7 @@ void Transform::flyTo(const CameraOptions& inputCamera, // Convert to geographic coordinates and set the new viewpoint. LatLng frameLatLng = Projection::unproject(framePoint, startScale); state.setLatLngZoom(frameLatLng, frameZoom); + state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, us)); if (bearing != startBearing) { state.setBearing(util::wrap(util::interpolate(startBearing, bearing, k), -pi, pi)); } @@ -364,10 +387,15 @@ void Transform::flyTo(const CameraOptions& inputCamera, util::interpolate(startEdgeInsets.bottom(), padding.bottom(), k), util::interpolate(startEdgeInsets.right(), padding.right(), k)}); } - double maxPitch = getMaxPitchForEdgeInsets(state.getEdgeInsets()); - if (pitch != startPitch || maxPitch < startPitch) { - state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, k))); + if (pitch != startPitch) { + state.setPitch(util::interpolate(startPitch, pitch, k)); + } + if (roll != startRoll) { + state.setPitch(util::interpolate(startRoll, roll, k)); + } + if (fov != startFov) { + state.setFieldOfView(util::interpolate(startFov, fov, k)); } }, duration); @@ -472,6 +500,14 @@ double Transform::getPitch() const { return state.getPitch(); } +double Transform::getRoll() const { + return state.getRoll(); +} + +double Transform::getFieldOfView() const { + return state.getFieldOfView(); +} + // MARK: - North Orientation void Transform::setNorthOrientation(NorthOrientation orientation) { @@ -479,8 +515,9 @@ void Transform::setNorthOrientation(NorthOrientation orientation) { double scale{state.getScale()}; double x{state.getX()}; double y{state.getY()}; + double z{state.getZ()}; state.constrain(scale, x, y); - state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y)); + state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z)); } NorthOrientation Transform::getNorthOrientation() const { @@ -494,8 +531,9 @@ void Transform::setConstrainMode(mbgl::ConstrainMode mode) { double scale{state.getScale()}; double x{state.getX()}; double y{state.getY()}; + double z{state.getZ()}; state.constrain(scale, x, y); - state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y)); + state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z)); } ConstrainMode Transform::getConstrainMode() const { @@ -676,22 +714,16 @@ LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng double Transform::getMaxPitchForEdgeInsets(const EdgeInsets& insets) const { double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset. + if (centerOffsetY == 0.0) { + return state.getMaxPitch(); + } const auto height = state.getSize().height; assert(height); - // For details, see description at - // https://github.com/mapbox/mapbox-gl-native/pull/15195 The definition of - // half of TransformState::fov with no inset, is: fov = arctan((height / 2) - // / (height * 1.5)). We use half of fov, as it is field of view above - // perspective center. With inset, this angle changes and - // tangentOfFovAboveCenterAngle = (h/2 + centerOffsetY) / (height - // * 1.5). 1.03 is a bit extra added to prevent parallel ground to viewport - // clipping plane. - const double tangentOfFovAboveCenterAngle = 1.03 * (height / 2.0 + centerOffsetY) / (1.5 * height); + // Half of fov is the field of view above perspective center. + const double tangentOfFovAboveCenterAngle = (0.5 + centerOffsetY / height) * 2.0 * tan(getFieldOfView() / 2.0); const double fovAboveCenter = std::atan(tangentOfFovAboveCenterAngle); - return pi * 0.5 - fovAboveCenter; - // e.g. Maximum pitch of 60 degrees is when perspective center's offset from - // the top is 84% of screen height. + return state.getMaxPitch() + getFieldOfView() / 2.0 - fovAboveCenter; } FreeCameraOptions Transform::getFreeCameraOptions() const { diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index da72d69f7..812218e40 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -91,6 +91,8 @@ public: // Pitch double getPitch() const; + double getRoll() const; + double getFieldOfView() const; // North Orientation void setNorthOrientation(NorthOrientation); diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index add4ea3ae..73246f267 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -15,6 +15,14 @@ using namespace std::numbers; namespace mbgl { +/* + * The maximum angle to use for the Mercator horizon. This must be less than 90 + * to prevent errors in `MercatorTransform::_calcMatrices()`. It shouldn't be too close + * to 90, or the distance to the horizon will become very large, unnecessarily increasing + * the number of tiles needed to render the map. + */ +const double maxMercatorHorizonAngle = util::deg2rad(89.25); + namespace { LatLng latLngFromMercator(Point mercatorCoordinate, LatLng::WrapMode wrapMode = LatLng::WrapMode::Unwrapped) { return {util::rad2deg(2 * std::atan(std::exp(pi - mercatorCoordinate.y * 2 * pi)) - (pi / 2)), @@ -46,15 +54,24 @@ void TransformState::setProperties(const TransformStateProperties& properties) { if (properties.y) { setY(*properties.y); } + if (properties.z) { + setZ(*properties.z); + } if (properties.scale) { setScale(*properties.scale); } if (properties.bearing) { setBearing(*properties.bearing); } + if (properties.fov) { + setFieldOfView(*properties.fov); + } if (properties.pitch) { setPitch(*properties.pitch); } + if (properties.roll) { + setRoll(*properties.roll); + } if (properties.xSkew) { setXSkew(*properties.xSkew); } @@ -116,6 +133,9 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne const double cameraToCenterDistance = getCameraToCenterDistance(); const ScreenCoordinate offset = getCenterOffset(); + const double limitedPitch = util::clamp(getPitch(), 0.0, maxMercatorHorizonAngle); + const double cameraToSeaLevelDistance = cameraToCenterDistance + std::abs(z) / std::cos(limitedPitch); + // Find the Z distance from the viewport center point // [width/2 + offset.x, height/2 + offset.y] to the top edge; to point // [width/2 + offset.x, 0] in Z units. @@ -123,12 +143,13 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne // (the distance between[width/2, height/2] and [width/2 + 1, height/2]) // See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details. // See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)). - const double tanFovAboveCenter = - ((size.height - frustumOffset.top()) * 0.5 + (offset.y - (frustumOffset.top() / 2))) / (size.height * 1.5); - const double tanMultiple = tanFovAboveCenter * std::tan(getPitch()); + const double tanFovAboveCenter = (0.5 + (offset.y - frustumOffset.top()) / size.height) * 2.0 * + std::tan(fov / 2.0) * + (std::abs(std::cos(roll)) + std::abs(std::sin(roll)) * size.width / size.height); + const double tanMultiple = util::clamp(tanFovAboveCenter * std::tan(limitedPitch), 0.0, 0.99); assert(tanMultiple < 1); // Calculate z distance of the farthest fragment that should be rendered. - const double furthestDistance = cameraToCenterDistance / (1 - tanMultiple); + const double furthestDistance = cameraToSeaLevelDistance / (1 - tanMultiple); // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` const double farZ = furthestDistance * 1.01; @@ -208,13 +229,13 @@ void TransformState::updateCameraState() const { const double dy = 0.5 * worldSize - y; // Set camera orientation and move it to a proper distance from the map - camera.setOrientation(getPitch(), getBearing()); + camera.setOrientation(getRoll(), getPitch(), getBearing()); const vec3 forward = camera.forward(); const vec3 orbitPosition = {{-forward[0] * cameraToCenterDistance, -forward[1] * cameraToCenterDistance, -forward[2] * cameraToCenterDistance}}; - vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], orbitPosition[2]}}; + vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], z + orbitPosition[2]}}; cameraPosition[0] /= worldSize; cameraPosition[1] /= worldSize; @@ -230,26 +251,39 @@ void TransformState::updateStateFromCamera() { const double dx = forward[0]; const double dy = forward[1]; const double dz = forward[2]; - assert(position[2] > 0.0 && dz < 0.0); // Compute bearing and pitch double newBearing; double newPitch; - camera.getOrientation(newPitch, newBearing); + double newRoll; + camera.getOrientation(newRoll, newPitch, newBearing); newPitch = util::clamp(newPitch, minPitch, maxPitch); // Compute zoom level from the camera altitude const double centerDistance = getCameraToCenterDistance(); - const double zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D)); - const double newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale); + double zoom; + double newScale; + double travel; + if (dz < -1.0e-9 && position[2] > 1.0e-9 && newPitch <= maxMercatorHorizonAngle) { + zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D)); + newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale); + travel = -position[2] / dz; + } else { + zoom = 14; + newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale); + travel = centerDistance / newScale / util::tileSize_D; + } // Compute center point of the map - const double travel = -position[2] / dz; const Point mercatorPoint = {position[0] + dx * travel, position[1] + dy * travel}; - setLatLngZoom(latLngFromMercator(mercatorPoint), scaleZoom(newScale)); + + const double mercatorZ = position[2] + dz * travel; + double alt_m = mercatorZ * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), 0) * util::tileSize_D; + setCenterAltitude(alt_m); setBearing(newBearing); setPitch(newPitch); + setRoll(newRoll); } FreeCameraOptions TransformState::getFreeCameraOptions() const { @@ -291,11 +325,6 @@ bool TransformState::setCameraOrientation(const Quaternion& orientation_) { const vec3 forward = unitQuat.transform({{0.0, 0.0, -1.0}}); const vec3 up = unitQuat.transform({{0.0, -1.0, 0.0}}); - if (up[2] < 0.0) { - // Camera is upside down and not recoverable - return false; - } - const std::optional updatedOrientation = util::Camera::orientationFromFrame(forward, up); if (!updatedOrientation) return false; @@ -443,10 +472,13 @@ void TransformState::setViewportMode(ViewportMode val) { CameraOptions TransformState::getCameraOptions(const std::optional& padding) const { return CameraOptions() .withCenter(getLatLng()) + .withCenterAltitude(getCenterAltitude()) .withPadding(padding ? padding : edgeInsets) .withZoom(getZoom()) .withBearing(util::rad2deg(-bearing)) - .withPitch(util::rad2deg(pitch)); + .withPitch(util::rad2deg(pitch)) + .withRoll(util::rad2deg(roll)) + .withFov(util::rad2deg(fov)); } // MARK: - EdgeInsets @@ -464,6 +496,10 @@ LatLng TransformState::getLatLng(LatLng::WrapMode wrapMode) const { return {util::rad2deg(2 * std::atan(std::exp(y / Cc)) - 0.5 * pi), -x / Bc, wrapMode}; } +double TransformState::getCenterAltitude() const { + return z * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom()); +} + double TransformState::pixel_x() const { const double center = (size.width - Projection::worldSize(scale)) / 2; return center + x; @@ -554,6 +590,14 @@ double TransformState::getMaxPitch() const { return maxPitch; } +double TransformState::getMinFieldOfView() const { + return minFov; +} + +double TransformState::getMaxFieldOfView() const { + return maxFov; +} + // MARK: - Scale double TransformState::getScale() const { return scale; @@ -590,6 +634,17 @@ void TransformState::setY(double val) { } } +double TransformState::getZ() const { + return z; +} + +void TransformState::setZ(double val) { + if (z != val) { + z = val; + requestMatricesUpdate = true; + } +} + // MARK: - Rotation double TransformState::getBearing() const { @@ -607,6 +662,24 @@ float TransformState::getFieldOfView() const { return static_cast(fov); } +void TransformState::setFieldOfView(double val) { + if (fov != val) { + fov = val; + requestMatricesUpdate = true; + } +} + +double TransformState::getRoll() const { + return roll; +} + +void TransformState::setRoll(double val) { + if (roll != val) { + roll = val; + requestMatricesUpdate = true; + } +} + float TransformState::getCameraToCenterDistance() const { return static_cast(0.5 * size.height / std::tan(fov / 2.0)); } @@ -730,7 +803,7 @@ TileCoordinate TransformState::screenCoordinateToTileCoordinate(const ScreenCoor double z0 = coord0[2] / w0; double z1 = coord1[2] / w1; - double t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0); + double t = z0 <= z1 ? 0 : (targetZ - z0) / (z1 - z0); Point p = util::interpolate(p0, p1, t) / scale * static_cast(1 << atZoom); return {.p = {p.x, p.y}, .z = static_cast(atZoom)}; @@ -957,6 +1030,11 @@ void TransformState::setLatLngZoom(const LatLng& latLng, double zoom) { setScalePoint(newScale, point); } +void TransformState::setCenterAltitude(double alt_m) { + z = alt_m / Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom()); + requestMatricesUpdate = true; +} + void TransformState::setScalePoint(const double newScale, const ScreenCoordinate& point) { double constrainedScale = newScale; ScreenCoordinate constrainedPoint = point; diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index c7d75e5e7..8b398af47 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -29,10 +29,18 @@ struct TransformStateProperties { y = val; return *this; } + TransformStateProperties& withZ(const std::optional& val) { + z = val; + return *this; + } TransformStateProperties& withScale(const std::optional& val) { scale = val; return *this; } + TransformStateProperties& withFov(const std::optional& val) { + fov = val; + return *this; + } TransformStateProperties& withBearing(const std::optional& val) { bearing = val; return *this; @@ -41,6 +49,10 @@ struct TransformStateProperties { pitch = val; return *this; } + TransformStateProperties& withRoll(const std::optional& val) { + roll = val; + return *this; + } TransformStateProperties& withXSkew(const std::optional& val) { xSkew = val; return *this; @@ -92,9 +104,12 @@ struct TransformStateProperties { std::optional x; std::optional y; - std::optional bearing; + std::optional z; + std::optional fov; std::optional scale; + std::optional bearing; std::optional pitch; + std::optional roll; std::optional xSkew; std::optional ySkew; std::optional axonometric; @@ -147,6 +162,7 @@ public: // Position LatLng getLatLng(LatLng::WrapMode = LatLng::Unwrapped) const; + double getCenterAltitude() const; double pixel_x() const; double pixel_y() const; @@ -164,6 +180,8 @@ public: void setX(double); double getY() const; void setY(double); + double getZ() const; + void setZ(double); // Bounds void setLatLngBounds(LatLngBounds); @@ -176,14 +194,19 @@ public: double getMinPitch() const; void setMaxPitch(double); double getMaxPitch() const; + double getMinFieldOfView() const; + double getMaxFieldOfView() const; // Rotation double getBearing() const; void setBearing(double); float getFieldOfView() const; + void setFieldOfView(double); float getCameraToCenterDistance() const; double getPitch() const; void setPitch(double); + double getRoll() const; + void setRoll(double); double getXSkew() const; void setXSkew(double); @@ -222,6 +245,7 @@ public: point on screen. */ void moveLatLng(const LatLng&, const ScreenCoordinate&); void setLatLngZoom(const LatLng& latLng, double zoom); + void setCenterAltitude(double alt_m); void constrain(double& scale, double& x, double& y) const; bool constrainScreen(double& scale_, double& x_, double& y_) const; @@ -248,7 +272,9 @@ private: // Limit the amount of pitch double minPitch = util::PITCH_MIN; - double maxPitch = util::PITCH_MAX; + double maxPitch = util::DEFAULT_PITCH_MAX; + double minFov = util::deg2rad(0.1); + double maxFov = util::deg2rad(150.0); NorthOrientation orientation = NorthOrientation::Upwards; @@ -283,15 +309,12 @@ private: bool gestureInProgress = false; // map position - double x = 0, y = 0; + double x = 0, y = 0, z = 0; double bearing = 0; double scale = 1; - // This fov value is somewhat arbitrary. The altitude of the camera used - // to be defined as 1.5 screen heights above the ground, which was an - // arbitrary choice. This is the fov equivalent to that value calculated - // with: `fov = 2 * arctan((height / 2) / (height * 1.5))` - double fov = 0.6435011087932844; + double fov = util::DEFAULT_FOV; double pitch = 0.0; + double roll = 0.0; double xSkew = 0.0; double ySkew = 1.0; bool axonometric = false; diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index cdae2e600..ce23f4068 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -69,6 +69,13 @@ StyleParseResult Parser::parse(const std::string& json) { } } + if (document.HasMember("centerAltitude")) { + const JSValue& value = document["centerAltitude"]; + if (value.IsNumber()) { + centerAltitude = value.GetDouble(); + } + } + if (document.HasMember("zoom")) { const JSValue& value = document["zoom"]; if (value.IsNumber()) { @@ -90,6 +97,13 @@ StyleParseResult Parser::parse(const std::string& json) { } } + if (document.HasMember("roll")) { + const JSValue& value = document["roll"]; + if (value.IsNumber()) { + roll = value.GetDouble(); + } + } + if (document.HasMember("transition")) { parseTransition(document["transition"]); } diff --git a/src/mbgl/style/parser.hpp b/src/mbgl/style/parser.hpp index 7aef89720..628ec3f3f 100644 --- a/src/mbgl/style/parser.hpp +++ b/src/mbgl/style/parser.hpp @@ -45,6 +45,8 @@ public: double zoom = 0; double bearing = 0; double pitch = 0; + double roll = 0; + double centerAltitude = 0; // Statically evaluate layer properties to determine what font stacks are used. std::set fontStacks() const; diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 88d65d0ed..24bd8fc8f 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -115,9 +115,11 @@ void Style::Impl::parse(const std::string& json_) { name = parser.name; defaultCamera.center = parser.latLng; + defaultCamera.centerAltitude = parser.centerAltitude; defaultCamera.zoom = parser.zoom; defaultCamera.bearing = parser.bearing; defaultCamera.pitch = parser.pitch; + defaultCamera.roll = parser.roll; setLight(std::make_unique(parser.light)); diff --git a/src/mbgl/util/camera.cpp b/src/mbgl/util/camera.cpp index 5ef8de7d7..1d72db103 100644 --- a/src/mbgl/util/camera.cpp +++ b/src/mbgl/util/camera.cpp @@ -14,20 +14,6 @@ using namespace std::numbers; namespace mbgl { -namespace { -double vec2Len(const vec2& v) noexcept { - return std::sqrt(v[0] * v[0] + v[1] * v[1]); -}; - -double vec2Dot(const vec2& a, const vec2& b) noexcept { - return a[0] * b[0] + a[1] * b[1]; -}; - -vec2 vec2Scale(const vec2& v, double s) noexcept { - return vec2{{v[0] * s, v[1] * s}}; -}; -} // namespace - namespace util { namespace { @@ -66,12 +52,13 @@ vec3 toMercator(const LatLng& location, double altitudeMeters) noexcept { altitudeMeters * pixelsPerMeter / worldSize}}; } -Quaternion orientationFromPitchBearing(double pitch, double bearing) noexcept { +Quaternion orientationFromRollPitchBearing(double roll, double pitch, double bearing) noexcept { // Both angles have to be negated to achieve CW rotation around the axis of rotation - const Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing); - const Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch); + Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing); + Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch); + Quaternion rotRoll = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, roll); - return rotBearing.multiply(rotPitch); + return rotBearing.multiply(rotPitch).multiply(rotRoll); } void updateTransform(mat4& transform, const Quaternion& orientation) noexcept { @@ -170,16 +157,24 @@ vec3 Camera::up() const noexcept { return {{-column[0], -column[1], -column[2]}}; } -void Camera::getOrientation(double& pitch, double& bearing) const noexcept { +void Camera::getOrientation(double& roll, double& pitch, double& bearing) const noexcept { const vec3 f = forward(); const vec3 r = right(); + const vec3 u = up(); - bearing = std::atan2(-r[1], r[0]); - pitch = std::atan2(std::sqrt(f[0] * f[0] + f[1] * f[1]), -f[2]); + const double f01 = std::sqrt(f[0] * f[0] + f[1] * f[1]); + pitch = std::atan2(f01, -f[2]); + if (f01 > 1.0e-3) { + bearing = -std::atan2(f[0], -f[1]); + roll = -std::atan2(r[2], u[2]); + } else { + bearing = std::atan2(-r[1], r[0]); + roll = 0.0; + } } -void Camera::setOrientation(double pitch, double bearing) noexcept { - orientation = orientationFromPitchBearing(pitch, bearing); +void Camera::setOrientation(double roll, double pitch, double bearing) noexcept { + orientation = orientationFromRollPitchBearing(roll, pitch, bearing); updateTransform(transform, orientation); } @@ -193,32 +188,27 @@ void Camera::setPosition(const vec3& position) noexcept { } std::optional Camera::orientationFromFrame(const vec3& forward, const vec3& up) noexcept { - vec3 upVector = up; - - const vec2 xyForward = {{forward[0], forward[1]}}; - vec2 xyUp = {{up[0], up[1]}}; const double epsilon = 1e-15; - // Remove roll-component of the resulting orientation by projecting - // the up-vector to the forward vector on xy-plane - if (vec2Len(xyForward) >= epsilon) { - const vec2 xyDir = vec2Scale(xyForward, 1.0 / vec2Len(xyForward)); - - xyUp = vec2Scale(xyDir, vec2Dot(xyUp, xyDir)); - upVector[0] = xyUp[0]; - upVector[1] = xyUp[1]; - } - - const vec3 right = vec3Cross(upVector, forward); + const vec3 right = vec3Cross(up, forward); if (vec3Length(right) < epsilon) { return std::nullopt; } - const double bearing = std::atan2(-right[1], right[0]); - const double pitch = std::atan2(std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]), -forward[2]); + const double f01 = std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]); + const double pitch = std::atan2(f01, -forward[2]); + double bearing; + double roll; + if (f01 > 1.0e-3) { + bearing = -std::atan2(forward[0], -forward[1]); + roll = -std::atan2(right[2], up[2]); + } else { + bearing = std::atan2(-right[1], right[0]); + roll = 0.0; + } - return util::orientationFromPitchBearing(pitch, bearing); + return util::orientationFromRollPitchBearing(roll, pitch, bearing); } } // namespace util @@ -258,14 +248,16 @@ void FreeCameraOptions::lookAtPoint(const LatLng& location, const std::optional< // Flip z-component of the up vector if it's pointing downwards up[2] = std::abs(up[2]); - const auto newOrientation = util::Camera::orientationFromFrame(forward, up); - if (newOrientation) { - orientation = newOrientation.value().m; + auto q = util::Camera::orientationFromFrame(forward, up); + + if (q) { + orientation = q->m; } } -void FreeCameraOptions::setPitchBearing(double pitch, double bearing) noexcept { - orientation = util::orientationFromPitchBearing(util::deg2rad(pitch), util::deg2rad(bearing)).m; +void FreeCameraOptions::setRollPitchBearing(double roll, double pitch, double bearing) noexcept { + orientation = + util::orientationFromRollPitchBearing(util::deg2rad(roll), util::deg2rad(pitch), util::deg2rad(bearing)).m; } } // namespace mbgl diff --git a/src/mbgl/util/camera.hpp b/src/mbgl/util/camera.hpp index d17e95bcf..d5119dc48 100644 --- a/src/mbgl/util/camera.hpp +++ b/src/mbgl/util/camera.hpp @@ -25,14 +25,13 @@ public: vec3 up() const noexcept; const Quaternion& getOrientation() const noexcept { return orientation; } - void getOrientation(double& pitch, double& bearing) const noexcept; - void setOrientation(double pitch, double bearing) noexcept; + void getOrientation(double& roll, double& pitch, double& bearing) const noexcept; + void setOrientation(double roll, double pitch, double bearing) noexcept; void setOrientation(const Quaternion& orientation_) noexcept; void setPosition(const vec3& position) noexcept; // Computes orientation using forward and up vectors of the provided - // coordinate frame. Only bearing and pitch components will be used. Does - // not return a value if input is invalid + // coordinate frame. Does not return a value if input is invalid static std::optional orientationFromFrame(const vec3& forward, const vec3& up) noexcept; private: diff --git a/test/map/transform.test.cpp b/test/map/transform.test.cpp index e98b82c7d..2452a8d57 100644 --- a/test/map/transform.test.cpp +++ b/test/map/transform.test.cpp @@ -916,7 +916,6 @@ TEST(Transform, MinMaxPitch) { transform.setMaxPitch(70); transform.jumpTo(CameraOptions().withPitch(70)); ASSERT_DOUBLE_EQ(transform.getState().getMaxPitch(), transform.getPitch()); - ASSERT_DOUBLE_EQ(util::deg2rad(60), transform.getPitch()); transform.setMaxPitch(45); transform.jumpTo(CameraOptions().withPitch(60)); @@ -1012,6 +1011,27 @@ TEST(Transform, FreeCameraOptionsInvalidZ) { EXPECT_LE(transform.getFreeCameraOptions().position.value()[2], 1.0); } +TEST(Transform, FreeCameraOptionsHighPitch) { + Transform transform; + transform.resize({100, 100}); + transform.setMaxPitch(180.0); + FreeCameraOptions options; + + options.position = vec3{{0.1, 0.1, 0.1}}; + options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-90.0)).m; + transform.setFreeCameraOptions(options); + EXPECT_DOUBLE_EQ(util::deg2rad(90.0), transform.getState().getPitch()); + EXPECT_DOUBLE_EQ(0.0, transform.getState().getBearing()); + EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.1, 0.1, 0.1}})); + + options.position = vec3{{0.1, 0.1, 0.1}}; + options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-135.0)).m; + transform.setFreeCameraOptions(options); + EXPECT_DOUBLE_EQ(util::deg2rad(135.0), transform.getState().getPitch()); + EXPECT_DOUBLE_EQ(0.0, transform.getState().getBearing()); + EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.1, 0.1, 0.1}})); +} + TEST(Transform, FreeCameraOptionsInvalidOrientation) { // Invalid orientations that cannot be clamped into a valid range Transform transform; @@ -1023,14 +1043,21 @@ TEST(Transform, FreeCameraOptionsInvalidOrientation) { EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation); // Gimbal lock. Both forward and up vectors are on xy-plane + transform.setMaxPitch(180.0); options.orientation = Quaternion::fromAxisAngle(vec3{{0.0, 1.0, 0.0}}, pi / 2).m; transform.setFreeCameraOptions(options); - EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation); + for (int i = 0; i < 4; i++) { + EXPECT_NEAR(Quaternion::fromEulerAngles(pi / 2, pi / 2, pi / 2).m[i], + transform.getFreeCameraOptions().orientation.value()[i], + 1.0e-12); + } // Camera is upside down options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, pi / 2 + pi / 4).m; transform.setFreeCameraOptions(options); - EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation); + for (int i = 0; i < 4; i++) { + EXPECT_NEAR(options.orientation.value()[i], transform.getFreeCameraOptions().orientation.value()[i], 1.0e-12); + } } TEST(Transform, FreeCameraOptionsSetOrientation) { @@ -1056,6 +1083,7 @@ TEST(Transform, FreeCameraOptionsSetOrientation) { transform.setFreeCameraOptions(options); EXPECT_DOUBLE_EQ(util::deg2rad(-56.0), transform.getState().getBearing()); EXPECT_DOUBLE_EQ(0.0, transform.getState().getPitch()); + EXPECT_DOUBLE_EQ(0.0, transform.getState().getRoll()); EXPECT_DOUBLE_EQ(0.0, transform.getState().getX()); EXPECT_NEAR(152.192378, transform.getState().getY(), 1e-6); @@ -1067,6 +1095,15 @@ TEST(Transform, FreeCameraOptionsSetOrientation) { EXPECT_DOUBLE_EQ(util::deg2rad(30.0), transform.getState().getPitch()); EXPECT_NEAR(1.308930, transform.getState().getX(), 1e-6); EXPECT_NEAR(56.813889, transform.getState().getY(), 1e-6); + + options.orientation = Quaternion::fromAxisAngle(vec3{{0.0, 0.0, 1.0}}, util::deg2rad(-33.0)) + .multiply(Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-22.0))) + .multiply(Quaternion::fromAxisAngle(vec3{{0.0, 0.0, 1.0}}, util::deg2rad(11.0))) + .m; + transform.setFreeCameraOptions(options); + EXPECT_DOUBLE_EQ(util::deg2rad(33.0), transform.getState().getBearing()); + EXPECT_DOUBLE_EQ(util::deg2rad(22.0), transform.getState().getPitch()); + EXPECT_FLOAT_EQ(util::deg2rad(11.0), transform.getState().getRoll()); } static std::tuple rotatedFrame(const std::array& quaternion) { @@ -1075,21 +1112,6 @@ static std::tuple rotatedFrame(const std::array& qu q.transform({{1.0, 0.0, 0.0}}), q.transform({{0.0, -1.0, 0.0}}), q.transform({{0.0, 0.0, -1.0}})); } -TEST(Transform, FreeCameraOptionsClampPitch) { - Transform transform; - transform.resize({100, 100}); - FreeCameraOptions options; - vec3 right, up, forward; - - options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-85.0)).m; - transform.setFreeCameraOptions(options); - EXPECT_DOUBLE_EQ(util::PITCH_MAX, transform.getState().getPitch()); - std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value()); - EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}})); - EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0, -0.5, 0.866025}})); - EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0, -0.866025, -0.5}})); -} - TEST(Transform, FreeCameraOptionsClampToBounds) { Transform transform; transform.resize({100, 100}); @@ -1130,29 +1152,6 @@ TEST(Transform, FreeCameraOptionsInvalidState) { EXPECT_THAT(options.position.value(), Vec3NearEquals1E5(vec3{{0.0, 0.0, 0.0}})); } -TEST(Transform, FreeCameraOptionsOrientationRoll) { - Transform transform; - FreeCameraOptions options; - transform.resize({100, 100}); - - const auto orientationWithoutRoll = Quaternion::fromEulerAngles(-pi / 4, 0.0, 0.0); - const auto orientationWithRoll = orientationWithoutRoll.multiply(Quaternion::fromEulerAngles(0.0, 0.0, pi / 4)); - - options.orientation = orientationWithRoll.m; - transform.setFreeCameraOptions(options); - options = transform.getFreeCameraOptions(); - - EXPECT_NEAR(options.orientation.value()[0], orientationWithoutRoll.x, 1e-9); - EXPECT_NEAR(options.orientation.value()[1], orientationWithoutRoll.y, 1e-9); - EXPECT_NEAR(options.orientation.value()[2], orientationWithoutRoll.z, 1e-9); - EXPECT_NEAR(options.orientation.value()[3], orientationWithoutRoll.w, 1e-9); - - EXPECT_NEAR(util::deg2rad(45.0), transform.getState().getPitch(), 1e-9); - EXPECT_NEAR(0.0, transform.getState().getBearing(), 1e-9); - EXPECT_NEAR(0.0, transform.getState().getX(), 1e-9); - EXPECT_NEAR(150.0, transform.getState().getY(), 1e-9); -} - TEST(Transform, FreeCameraOptionsStateSynchronization) { Transform transform; transform.resize({100, 100}); @@ -1172,20 +1171,49 @@ TEST(Transform, FreeCameraOptionsStateSynchronization) { transform.jumpTo(CameraOptions().withPitch(20.0).withBearing(77.0).withCenter(LatLng{-20.0, 20.0})); EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.457922, 0.57926, 0.275301}})); - - // Invalid pitch - transform.jumpTo(CameraOptions().withPitch(-10.0).withBearing(0.0)); - std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value()); - EXPECT_THAT(transform.getFreeCameraOptions().position.value(), - Vec3NearEquals1E5(vec3{{0.555556, 0.556719, 0.292969}})); - EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}})); - EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0.0, -1.0, 0.0}})); - EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0.0, 0.0, -1.0}})); - - transform.jumpTo(CameraOptions().withPitch(85.0).withBearing(0.0).withCenter(LatLng{-80.0, 0.0})); - std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value()); - EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.5, 1.14146, 0.146484}})); - EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}})); - EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0, -0.5, 0.866025}})); - EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0, -0.866025, -0.5}})); +} + +TEST(Camera, SetOrientation) { + util::Camera camera; + const double bearing = 0.22; + const double pitch = 0.34; + const double roll = 0.0; + double bearing_ = 0; + double pitch_ = 0; + double roll_ = 0; + camera.setOrientation(roll, pitch, bearing); + camera.getOrientation(roll_, pitch_, bearing_); + EXPECT_NEAR(bearing, bearing_, 1.0e-9); + EXPECT_NEAR(pitch, pitch_, 1.0e-9); + EXPECT_NEAR(roll, roll_, 1.0e-9); +} + +TEST(Camera, SetOrientationWithRoll) { + util::Camera camera; + const double bearing = 0.22; + const double pitch = 0.34; + const double roll = 0.45; + double bearing_ = 0; + double pitch_ = 0; + double roll_ = 0; + camera.setOrientation(roll, pitch, bearing); + camera.getOrientation(roll_, pitch_, bearing_); + EXPECT_NEAR(bearing, bearing_, 1.0e-9); + EXPECT_NEAR(pitch, pitch_, 1.0e-9); + EXPECT_NEAR(roll, roll_, 1.0e-9); +} + +TEST(Camera, SetOrientationWithRollNoPitch) { + util::Camera camera; + const double bearing = 0.22; + const double pitch = 0.0; + const double roll = 0.45; + double bearing_ = 0; + double pitch_ = 0; + double roll_ = 0; + camera.setOrientation(roll, pitch, bearing); + camera.getOrientation(roll_, pitch_, bearing_); + EXPECT_NEAR(bearing - roll, bearing_, 1.0e-9); + EXPECT_NEAR(pitch, pitch_, 1.0e-9); + EXPECT_NEAR(0.0, roll_, 1.0e-9); } diff --git a/test/style/style.test.cpp b/test/style/style.test.cpp index 34ac3a5fc..1f5020981 100644 --- a/test/style/style.test.cpp +++ b/test/style/style.test.cpp @@ -34,6 +34,10 @@ TEST(Style, Properties) { ASSERT_EQ("", style.getName()); ASSERT_EQ((LatLng{20, 10}), *style.getDefaultCamera().center); + style.loadJSON(R"STYLE({"centerAltitude": 999})STYLE"); + ASSERT_EQ("", style.getName()); + ASSERT_EQ(999, *style.getDefaultCamera().centerAltitude); + style.loadJSON(R"STYLE({"bearing": 24})STYLE"); ASSERT_EQ("", style.getName()); ASSERT_EQ(LatLng{}, *style.getDefaultCamera().center); @@ -47,6 +51,10 @@ TEST(Style, Properties) { ASSERT_EQ("", style.getName()); ASSERT_EQ(60, *style.getDefaultCamera().pitch); + style.loadJSON(R"STYLE({"roll": 99})STYLE"); + ASSERT_EQ("", style.getName()); + ASSERT_EQ(99, *style.getDefaultCamera().roll); + style.loadJSON(R"STYLE({})STYLE"); ASSERT_EQ(Milliseconds(300), *style.getTransitionOptions().duration); ASSERT_EQ(std::optional{}, style.getTransitionOptions().delay); diff --git a/test/util/camera.test.cpp b/test/util/camera.test.cpp index 7cb62b87e..f9c2aed1b 100644 --- a/test/util/camera.test.cpp +++ b/test/util/camera.test.cpp @@ -180,8 +180,8 @@ TEST(FreeCameraOptions, LookAtPoint) { ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); - ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}})); - ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, 0.0, 1.0}})); + ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 0.9714568644195829, 0.2372162738348117}})); + ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -0.23721627383481167, 0.97145686441958301}})); ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}})); // Up vector pointing downwards @@ -226,18 +226,18 @@ TEST(FreeCameraOptions, LookAtPointInvalidInput) { ASSERT_FALSE(options.orientation); } -TEST(FreeCameraOptions, SetPitchBearing) { +TEST(FreeCameraOptions, SetRollPitchBearing) { FreeCameraOptions options; vec3 right, up, forward; - options.setPitchBearing(0.0, 0.0); + options.setRollPitchBearing(0.0, 0.0, 0.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}})); ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -1.0, 0.0}})); ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}})); - options.setPitchBearing(0.0, 180.0); + options.setRollPitchBearing(0.0, 0.0, 180.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{-1.0, 0.0, 0.0}})); @@ -247,14 +247,14 @@ TEST(FreeCameraOptions, SetPitchBearing) { const double cos60 = std::cos(util::deg2rad(60.0)); const double sin60 = std::sin(util::deg2rad(60.0)); - options.setPitchBearing(60.0, 0.0); + options.setRollPitchBearing(0.0, 60.0, 0.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}})); ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -cos60, sin60}})); ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, -sin60, -cos60}})); - options.setPitchBearing(60.0, -450); + options.setRollPitchBearing(0.0, 60.0, -450); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}})); diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index 7d1f604c5..bf6fda78f 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -78,9 +78,8 @@ TEST(TileCover, PitchOverAllowedByContentInsets) { // Top padding of 376 leads to capped pitch. See Transform::getMaxPitchForEdgeInsets. EXPECT_LE(transform.getPitch() + 0.001, util::deg2rad(60)); - EXPECT_EQ( - (std::vector{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}, {3, 4, 2}, {3, 5, 3}, {3, 5, 2}}), - util::tileCover({transform.getState()}, 3)); + EXPECT_EQ((std::vector{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}}), + util::tileCover({transform.getState()}, 3)); } TEST(TileCover, PitchWithLargerResultSet) { @@ -102,17 +101,17 @@ TEST(TileCover, PitchWithLargerResultSet) { EXPECT_EQ((std::vector{{5, 15, 16}, {5, 15, 17}, {5, 14, 16}, - {5, 14, 17}, {5, 16, 16}, - {5, 16, 17}, {5, 15, 15}, + {5, 14, 17}, + {5, 16, 17}, {5, 14, 15}, - {5, 15, 18}, - {5, 14, 18}, {5, 16, 15}, + {5, 15, 18}, {5, 13, 16}, - {5, 13, 17}, + {5, 14, 18}, {5, 16, 18}, + {5, 13, 17}, {5, 13, 18}, {5, 15, 19}}), (std::vector{cover.begin(), cover.begin() + 16}));