From 1c40c44d7ed4c8ce36da2c370991986ece8bc74c Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 5 Jul 2023 15:42:47 +0200 Subject: [PATCH 01/11] Fix for negative coordinates --- apps/gipy/ChangeLog | 5 ++ apps/gipy/TODO | 26 ++++----- apps/gipy/app.js | 94 ++++++++++++++++++--------------- apps/gipy/pkg/gps.d.ts | 4 +- apps/gipy/pkg/gps.js | 38 ++++++------- apps/gipy/pkg/gps_bg.wasm | Bin 748078 -> 748683 bytes apps/gipy/pkg/gps_bg.wasm.d.ts | 4 +- 7 files changed, 90 insertions(+), 81 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 8646ba11a..9e9654fd0 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -87,3 +87,8 @@ * Reduce framerate if locked * Stroke to move around in the map * Fix for missing paths in display + +0.20: + * Large display for instant speed + * Bugfix for negative coordinates + * Disable menu while the map is not loaded diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 266a1c5c9..c94211c1d 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -1,19 +1,15 @@ ++ disable backlight during day ? ++ put back foot only ways ++ disable bluetooth ++ disable lcd completely ++ try fiddling with jit ++ put back street names ++ put back shortest paths but with points cache this time and jit ++ how to display paths from shortest path ? + + +misc: + use Bangle.project(latlong) -* additional features -- config screen - - are we on foot (and should use compass) - -- we need to buzz 200m before sharp turns (or even better, 30seconds) -(and look at more than next point) - -- display distance to next water/toilet ? -- display scale (100m) - -- compress path ? - -* misc - -- code is becoming messy diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 60e4bb5af..d34cb75fe 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -152,7 +152,7 @@ class Map { color_array[2] / 255, ]; offset += 3; - this.first_tile = Uint32Array(buffer, offset, 2); // absolute tile id of first tile + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile offset += 2 * 4; this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height offset += 2 * 4; @@ -471,7 +471,7 @@ class Map { class Interests { constructor(buffer, offset) { - this.first_tile = Uint32Array(buffer, offset, 2); // absolute tile id of first tile + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile offset += 2 * 4; this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height offset += 2 * 4; @@ -971,12 +971,18 @@ class Status { .drawString( "" + approximate_speed + - "km/h (in." + - approximate_instant_speed + - ")", + "km/h", 0, g.getHeight() - 15 ); + + g.setFont("6x8:3") + .setFontAlign(1, -1, 0) + .drawString( + ""+approximate_instant_speed, + g.getWidth(), + g.getHeight() - 22 + ); } if (this.path === null || this.position === null) { @@ -1374,6 +1380,46 @@ function start_gipy(path, maps, interests) { console.log("starting"); status = new Status(path, maps, interests); + setWatch( + function () { + if (in_menu) { + return; + } + in_menu = true; + const menu = { + "": { title: "choose action" }, + "Go Backward": { + value: go_backwards, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => { + go_backwards = v; + }, + }, + Zoom: { + value: zoomed, + format: (v) => (v ? "In" : "Out"), + onchange: (v) => { + status.invalidate_caches(); + zoomed = v; + }, + }, + "back to map": function () { + in_menu = false; + E.showMenu(); + g.clear(); + g.flip(); + if (status !== null) { + status.display(); + } + }, + }; + E.showMenu(menu); + }, + BTN1, + { repeat: true } + ); + + if (status.path !== null) { let start = status.path.point(0); status.displayed_position = start; @@ -1455,44 +1501,6 @@ function start_gipy(path, maps, interests) { } } -setWatch( - function () { - if (in_menu) { - return; - } - in_menu = true; - const menu = { - "": { title: "choose action" }, - "Go Backward": { - value: go_backwards, - format: (v) => (v ? "On" : "Off"), - onchange: (v) => { - go_backwards = v; - }, - }, - Zoom: { - value: zoomed, - format: (v) => (v ? "In" : "Out"), - onchange: (v) => { - status.invalidate_caches(); - zoomed = v; - }, - }, - "back to map": function () { - in_menu = false; - E.showMenu(); - g.clear(); - g.flip(); - if (status !== null) { - status.display(); - } - }, - }; - E.showMenu(menu); - }, - BTN1, - { repeat: true } -); let files = s.list(".gps"); if (files.length <= 1) { diff --git a/apps/gipy/pkg/gps.d.ts b/apps/gipy/pkg/gps.d.ts index 15a90b1e8..c881052f4 100644 --- a/apps/gipy/pkg/gps.d.ts +++ b/apps/gipy/pkg/gps.d.ts @@ -67,11 +67,11 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number) => number; readonly __wbindgen_export_2: WebAssembly.Table; - readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a: (a: number, b: number, c: number) => void; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1: (a: number, b: number, c: number) => void; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_free: (a: number, b: number) => void; readonly __wbindgen_exn_store: (a: number) => void; - readonly wasm_bindgen__convert__closures__invoke2_mut__h26ce002f44a5439b: (a: number, b: number, c: number, d: number) => void; + readonly wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137: (a: number, b: number, c: number, d: number) => void; } export type SyncInitInput = BufferSource | WebAssembly.Module; diff --git a/apps/gipy/pkg/gps.js b/apps/gipy/pkg/gps.js index ce9ebe5f8..39c2a6804 100644 --- a/apps/gipy/pkg/gps.js +++ b/apps/gipy/pkg/gps.js @@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_24(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1(arg0, arg1, addHeapObject(arg2)); } function _assertClass(instance, klass) { @@ -369,7 +369,7 @@ function handleError(f, args) { } } function __wbg_adapter_84(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__h26ce002f44a5439b(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); + wasm.wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } /** @@ -460,6 +460,21 @@ function getImports() { const ret = getObject(arg0).fetch(getObject(arg1)); return addHeapObject(ret); }; + imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) { + const ret = getObject(arg0).signal; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () { + const ret = new AbortController(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) { + getObject(arg0).abort(); + }; + imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () { const ret = new Headers(); return addHeapObject(ret); @@ -496,21 +511,6 @@ function getImports() { const ret = getObject(arg0).text(); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) { - const ret = getObject(arg0).signal; - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () { - const ret = new AbortController(); - return addHeapObject(ret); - }, arguments) }; - imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) { - getObject(arg0).abort(); - }; - imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) { - const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); - return addHeapObject(ret); - }, arguments) }; imports.wbg.__wbg_new_abda76e883ba8a5f = function() { const ret = new Error(); return addHeapObject(ret); @@ -675,8 +675,8 @@ function getImports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2298 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 260, __wbg_adapter_24); + imports.wbg.__wbindgen_closure_wrapper2245 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 267, __wbg_adapter_24); return addHeapObject(ret); }; diff --git a/apps/gipy/pkg/gps_bg.wasm b/apps/gipy/pkg/gps_bg.wasm index 6999cb94673213057a9f2e2d2480063a5033c031..8e0fbc07eb7d7cf8a96dd646305f808eda4e49cc 100644 GIT binary patch delta 135369 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 A$N&HU delta 135283 zcmeFa33yaR)<1l!Zm;R?^lcJI$VPV)AOQja*%#p=n~H+#sN*`W6Wmb8@f{sIB1%+{ z=#5mw08vqbprW9G0VFI6C_)5;fS^H8Q4v9cMn(93r|NbmL7n%V|LgyIp6_|Sp!eR| zPMtb+>eQ)Ir>Y;@S@6oXf`t)%(Gs0AE22NQ=xvAP@bvGeIrKH5Ro2zsHk~zx#_;Fu z(#or~Vor@|%*|ac7sG$}!$Zf@rMY#h(wiCcBX=rg(i{#Ae@t^TcTme5*bxbChfC8l z9h`HA8!7oNWN|xmO>?>0xVYO*nHd9g#!(U1LI`QDoHQ3Q<3FlTbMs93)tPA?1T|Mb z%{z=~ycK0|S_6{W#awRY&>Svqvh;M+rLi=mGcQoqn9C6b&Oj{NtFs;&*EvUaojW+= z6s@Cnd~glWb+-mI(PsSlTtLr>mg~snMl#o;T8^VYceyEZ9>t(0z}2j}q~w^}rEwP; zp^tEYJiy-R)mS(SH6^)Ge-6N;Qat{+LrZfdp;G$ia&^^1S}NKFT#3jEYuR*a3ovr| zsUc1x2L71VuB{s+)R0l{k1Wj9im{lBIT6`b(u@U}6OG7XxuCV)UPmubKWf9#Nz`|Q z#vlobz@a%*8~wQv$wPpQOl_&V2cS;IDVj0@45199yc%~CLZCb1(R-HwPvFAA9qlw^ z)2Y+(=U^j26M}+#5)4>CC;DhQNaEC7XyYiutx=2OS)95aRNv{Eb-KS#j7Tm2^nj)% zQQLs0rvqYDJkY`iN5A5kMsRp9s@5=c{GmdSfyxX;9ey3TBTi=q!Y-yK8AJ|0dIR)B z-%|_Sz)QaEVll&@j?(ng2Ww~xz*BqhCpn0~1A2L0I2Xk@2)Il|HlLH9K zCZX+}IBEinh@=ob2pXuOV^9HrBo!D3_{o+AW2hWmqN91lsvt5w6WC#N-1n!og zoWK=hjqsves2oTUrHM?$(CEn|d^z-1ttEMoPCbh#*-jnYMlHqYL>#xi33N`+!U)kZ zCUh?je@o7fkpR%$$*xos zq6!Fg{7)f=6Tmq&SM$jNUbh!C1X(~?k`d?;T?-1Hne1r;GkMnUCuZ#vvz2 z9AVI)1=P35M3vAV(_*^r1RWXY6b)>3AP9m|UjbK^oUA2N{*)BSRm7wq2IsBAc=|La z+5+wdg8*SCsOvm~z@Pv%fEol;F#rGyou(xL3skGJ0FI`|s5xi>p$#OcQ|OOK;8tjY z2rj1DFL9Q5qfPQp5~?*CKnw-s9E2cTo1EfuD?Xxque;A+sFg%$3ZNiSRy3MVCnN09yD!Sp4~XJ`hgV zJ)j?Gib#kraIzac12Xc_Xex>kYrFMSnp*HBD+`}+IVfL7fuD)j_!y#B7MZoK`@t8TgG_N(r^`$p{( zuaBZ`zwPG#y79JKwZkrdJb3e8Z@peS;!2db_O@H^y8hO?v~NAWJFcJbx9jh`i-2g; ztQD=&2A}i<2T60sPsb!&Hvm!U#asBn$^VXh}H1@4^ zETu=RXP;}YFD&eNL$6-f^zL<9|G#J}g5Kva`I*c&jn^6bd<~&zlO7L!>srKig&IO1 zg?5J?3R?X0&~x4|jTc<+x?`zY|Bzb+6vl)NkOgl}H*yyS!Uw>|KdKmNTV@J;g0z)JrtV}UU} zw9=?C_JlSVFG!Hzgy%n3+(YZ^Om>xRf@7ECeKJcFB z;ot&e7ZnLrgl2~Jgx&_W$3n4&P`#@(xF@vEcn85<{@0Qw`qtz7RsXTj>`BhE#}2G$r@V9W~LHyz1k-uHtIp^e4^-cp1%IG+jqY<$Hx zc^VLUJ~Suv9NzC6$Bl_z&@=R-zacc;yE*uaKeiEYPxy}q8bU84%S)ko62&rvpZ3-Q z^x`k-EkB zK<3?Od>T3m_{V}D1h*s|3w`AO$k^?FC$Kwa?D6k0J~qA$eVY81>u_j#=z-8T`~!)1 zz43|huy;#vYw%|3E708$6isnqw4Px+(X@0_bb zo1C+Qhxw~6%JmiSZ3xwbR^$1KYYhlH9sRyR6671>pz#>`BKCG@ZSZ*VCS#@XRq{%s z&R7e;2fbf=XM5_Bs`xC=vZM{63eQ64yP=t$6`_ZK&a=Ta!5V}3q0+nFS*YF}8(PuxfpeXKUz*&;tEA&riM& zLt8>y0?+C%x{_Y}B5T#6ooT}un_^|P{m8j0IKna1TGb`T%E)Nz+_XtEmRbEWa@kkb z6&d-hWZ3P$&=+Rrl+7}+Jfj!eyy*Rm3z=h8FmSmn+P}NcIyWmnv(XD+VW$~7$JFrh zj>@Dk3v*-Dq9?ODu;`Dz5dqB?*3DvGL{C;^lz8w_C_}u+m}>3JKG#$H?k3H28c$l? zb0Td@5HKbi0Vnd+u_Ci^1BI;{a=NC+lNf=lOn{tky_8ePYORlQqHMc$BB$SoIKW`w zvM>i`4S-?l#*^`I@kWGmo5D2|&Q@WCtizaM-P>-Qz9;Cjg6-N`U$hJ4#0!lGWctD! zE!E9>7H`)St5x`cAxj*7iQHkdbWc!az=toanMngQ4P<>FX&_syBZY{uFX+FC+LBik zc7|QX$JUnc9KOL}?TSRKrz5S|5$n~+&yHElf3ej)+SXbZoz-FedZ4NSRU)+mq(0~h z_)ScR07E2UCI{+|wjapWS?{!;0)kwSdk&t@<`y7kb8eXJu@2{EwW&-BFu#d*7#kcT z0z-Wg))6b(q1sUs44f|e`R5M3SdCTKv8?R|sx{BtPkk}fhc2Z~M!o?mU&p_uZ4YIb z&IqTL#%W>>F}5$dx6_T1bIzAJ9r?Zf9oJYV3bL%*@^8wjAdZk725=DvsKd=lJI*?q z-=9sj3Ob+Z+!ORq@DboePj}8`Y?bv^mkY8Qy@4qIJ+>_9dNt*Hyx^k$29HTu%bVe# z**Mft90#Y)>edZ7yx&dYaHGPZfA@Jhx@=$1vsmS#Zm0d3IW{-~`Bsn9+F1*FDK4Hu4db;QN8a(=4HJ*^q?*^zD)`Y_79?g_qtwWu0}nG8W9+1a}L^xq!M&6 zH;!1LKJ7-WCsbU-vw7fghgnNu4~6^5a2g8Osff!jG(Bf!|Z4r0T!}NWBbhn{BQcTZENQ!*JWdQoB~%!DqDm5_x7USFC*5i zwC?R6LqGi7zc+i=>MOi~`J za$p)TSu!vi&+P;6jaH%`TG6wYHKQf?=2!yDmHjdVvgRZ`c!~<9(7|=4z(Wsm}~9#B<}|SO|^t z8%=HU8`hOWqt^YWC!z6=oIb^=qT}iG9lh7jHX%73^Du3D%>b)vnP5?2_v&cGl=-i)Fc22REjB? zVxvqxX1OqI|Kb?9UgvK(jk(7;y{3D4j4m#X352DhU6;s4PEm0?Y-H6Rgw64f1sLQTKqezZvzQ%w3tE zK%ZziK>L^O@8p9xa)9f>u%`mRcu@fEKT`onv09%s^*4Zo{|%6wIFR7k#lW=m?7FiC%XQ?2lMZBX5WbEmL<)`@dTAQp_xZ-+@O5NF7JnED(v?0gin7Q;@F zv}EjWwbA#xZ9D_@OdNN~?_qR!Tvzsqm3>}YWb1q001S;g&g+5iIp-ZqJ>UxKdE6gy z)5{Zf0iW&G%jd^>(zGnQXsM3=kpjZ3BwlXxN(r@w6%}Y2k&T&i)G)g8f}WU4pSYmM zAdDHkK&#|t(kNs`VKi+WsVw-w6Em^R{3uO>jz$EM6yKk?VDQlY1VDUnwg7VPg&k;G zzc9V;e*mflgu@rM1%#xFFbEe7y6Dwzv3>pZV9aqpzpuS!*6ZV=j$92BOFt+Tm@9N+ zDuj;70xw!)E!3}4RYr3T%ggl2$&nUQlt6> zSd7r`q7RLx_16mlsIbV`=tDDALw>6TF0<5bfs0z;iMV6~JhB1cw13+GuWG>hL<3^f z0xH$K1qc^tz~0#mdlzBvYJt7f1_}4{;TruF-%xYIh;Sk>^x6@3$SkH&6iyGP83znm z549IxUG#Kgvg=wf`q1%_5K>m@MG?XT3V4KaLQY0Jcw?4`c<}~*N0RVffHy|Cc^vtI z6c63SaYv*TK9?ZLkM~-<19-1TmSo+U(4&Jeu5>!aYG{&gq$3t);gIoyhoqF|3_E@1 zNjAYu8X0v0U+O*j2hL$(XJ*uiJm^O+jXAgZ&!S}5axgwMbJr|u-@{!4FA*-l!C2+= zx3TtSbb2z=6{F5JqHc6|I(0Xy-3F4&Ntj4N33U{K1&CBl3bb;E$wmfJd@`+`(&Y3U zlHvV{upz?-;=lsnW57C?NKcmG*x^KmfQ)EJWbn&yVm z`|6NKW-+({^G}-18R7QKnBG)z#>hZt$YuzwO1-b{0g!wQLF?m3hA@|v^JtsyPORGe zZQ_NQsc6#@`wU`7yJV=WO>1CDHP-`4hq+KM%Gb4MsxGazt_f6>Tz^qvmP&oI@3|L z6g~cc-lE4#Rgb5trnaXgvEumOZL9T%^jeFS%G&>+mgfF9Erp;WsV7^i#`^!|mV!TT zdpyCPvZ|?SIpv@PE@HtQO_W1ZGt7`u~L% zZ7BUeXwfD}mI93iSWfdMHJ2s!0K=*?-Xt|8{YH~qf3R5m;feo)Cdo;HT9nniMa?G( zyG68EA@u<&`$S0wTki^Fpdd{QB(bg5Wv%}AVzEziv6!5w*Hn}LDU=);NVgIAVSzK2mh*a z{~ua;L33ttCn`58TXc2OA6jP%YP%A3+KYr!M#mqm`n6CZWaWR55<$AHQjmXJU2@Vg z%2R|jFiF6cgUtaVbkXa0Bn_!bp@#a2Rr1iVV;JkJWA#mnJXWZIG_sQ>(ln&m1T?>r|Br7&5PS-Wb&$+*3=oDyL*gzM%YV_BzpMh5j4s} zVLyEZ=#gx!O!j9G_Ej@Q+sP~v`yUoUt>Gd=g#6gev1N(TYynBj0<6T%a!xi(e>yb_ zR4CE=>*G|jMzPM=`EjK_RqE;nkzOtfj4)y}znp21KkBp&RkTf!7<(ua z=mYzP7WP=Ho=JgLLaDO--sY)Zi&d!9%n(SB)I2T8GG$t~nKI4SJk4i4P?2(3D{Os{ zSr$vTNmw_~yO@(bCx{i*^H{4>d;|=EkK@_| zTh`3X#`EKuIe7juvmKt(_9KoYvvwnd23lj>J&^r`&-l_%qx~$+Jb+bh zAFO5Vl_R#hip(^*cEOr9oZerq+_0t%#@Dmvky+N#S=sD!Ysah!W0ar@yP80domjK& zQ@;lmV~7@n02@evEuL+a&Az9HTh>jQb-XY&BtD4yH!XRnb;QWl;L6CE_S$nHlo*&? zG`035TFC`9oM(a}EmoraXdzmutA(h`+FCgqM#4LvJri5RE1oT6uULnk z?Tu&dbM(ASK1=2E9r-*ipFN*P>Y|&Te~sM;QvuK8Yk8y%y=!i9JEWr%# zC1V0omXmnFdkGJxAQCxJkYgcoc9`-9Mg&#S}k;7qwt>BSN%2Ce4ASHo?#oH`J4ws#yj7PF4M=1{{ zArd)~kc0LPbUVis9?1^#q&!~2F^Y8TduG2lkNu^7OO0l>HygfynUl6nO*^9Mz$p<2 z_MZ}Q_|zQ5U!IaaDRkqDKQB{mxmDs!tn8aty8_uVEVb#z3 zGpn}-y_Dl$2vqE>_0|n9^@HVp_Dh}EQ`Wkd26XwJcDD{VvF&Eo`-;pZ3{9o2o5N1< z-C;CWrkaf~_E=r#7lJRYnBN)ClKFk{teHO`6{K+>Z;huY>Nc{#(&iU?tmFj)uRFA- z1}>JS9?>BJ%p)q~K*-}WkI7Kjg^=54HmWoVxqRk{rkpShPW)IxaU#v(GbhWCET{X- z$3M1SUXae7vEEq_$$ce6{nfxCI=v4wl4dYcp=Y*vsdcc{zh?Pf&R|QdjxYD*KO|e1 zz1*EGvL1aoKWQK3C`m0cimeqdcXA!yhB|fYNLr=!=quO6CVogk?2nnj2*ke%ja8x9 zDs-;T3?S_l6*|Xfh7nq(LSuYp7D6AX(Ag^Vg9@GHGc%E9ZI?Bi=`*tliVBVPnQahy zFQ$T{d}a=UdsS$p%6d|TM)*Kxhgq>hf*J0^w!FiftwLw`OgBPvRA`t&biN9iDzrp} zhWboDa=xuXLwqKB*8E^cOaeXKXC@)|sY)E|GgA;cs6vB$rh(8&6&k2Q)9Pfo0X{Ps zX|q+Rze;;qh5D)LSEx{5pP7c7@2XHApV=Ovy>&G)B7Sd$+oYWmXfK~xs=iKBEuXGJ zJ$gF@ODD#R66{s@RD%6!iG4!MgcEQYwtSeQh zvm($s70Op_sZ*gmmGd(d>ZGXkjS6*CIg5A6rgTshF5D_ZxvJb$mDXN1I%Ynlf>E?U zeMNlcT=f<9nXQ1*%PQ1PRkTutaukbhQK4+bYI{{EOYz8Y70Of;dEg_7M20F?qC#zB zDp9Cly3h2X!dWVm<}*_fnx{f-d}caAD^#epqR)pal&ZM-a}`SQnXQrYxC*uMnNRGN zRfiOp2^BJYX4{A(WIWvATB2H4A-Nt_F#YMTp?HQ!< zQ+qlvjs}ncM6(kX)Sog>EZ=8GK&my;=A`kqmAp8c|72JNi__Sr)`-PjPkR|M50h6a zb^thiO-b#;*SCSlA z^J0>)ZQjUb)z-i_hOw8e``>75yowD2Xq%XYfsWHyV$FRc8>D#qjl4qHTIhXn+9Jrt zVK26!Y?y{QriQoMOOBk!(%?86l=KHc!kz(9g22%hp}!ArAaw*;@N7saGJd4g5;TBY z^=~@cJW1tf@0ZBP!n{D6!i;0q&u{h}w;RpLF$t>ibr5}83KyNp4e2%5Pb<)5~mUD~D_@uEiR$sQ=kq;jatrS2l*3F_3fAwu|g z>mqX_C}MrKG;$Wv*l+xR1&e0ZIg5-BsbLiX%2$eBpnS*ym$Ly{y$330tvehZrk{s> zFJL3HA+rKE?qAW?N;_K4!o>H`o6H($OxEOob!_)Gx+jn45##0YuCP|chv#Cc=vf>8 z)xO}=k&JrRLv44Nb@2{704FWYEOitazgStz(lL~JFFUXE%M$1%QF!`|B+T;o<)EyN zVa4I=AqQ413evz@xNJliLkNS~tVt&NB!&%$Kcj~{5EC2v|WyrB52BVNkGusfNH zl@)ePXZoYm+33uCZkG=iMRjxev(}KeB3M&i^VS%j8mb4-FMF&dZ)JB(FwKu1tZfzt z!*By2C}c|J<|Idvxt}=j=ePQ&0B^XXYH(4Y4=me$4qBsDo)(lKuf#^Z z4;6zIqOr>AU(<=bWL;BJXv|Df@*fP1?OkJ{^=wVyuy`ayRvu3^CK_1v!z3hIMq>{2 z1}WkajXp3OA%^A0TKce3R&_{k1nZlvk()-!5@^+C>1UXlzOlxw%EmNw>#Fi$`t%6j5<%L}jxG z3@*wuhoZ2%Ze9IO8&4Gluv3men!D+{jb{l3$fKEZ)ldNS$iR9Ez%5e-YAFy0wyjy< z;yMbn#2bTM;ayK@v>#7+lVgrrKszE*7{JK}QT9R%yYad*EMQ^)tyYy}iHvudVf9)! zmQA-Nt$Uc)9kTrESMqI#toQM%J!Cz;VI^OG$Qt|ZN?vowGTvLsmmIP_z-!^5MNe*g zh_Om5ebc|X*|g~!=K1;(omTf|iI>C|8S68`ZHDplfgbF0<^f2>CpWhaQ#4I4wUE5C ze6%Gmtr@Z$m9lzoKFy^J{no8bpK~|oW3u^RbL&4701g9?Q4XdH!%X3UsQ@#RCvLAK zdjOEY7$O|}*o3ufXv(4u!$!YGNjl7p7}*CwO?X$DwKLnuNp&r@kLMX`QUxqyp4Cvh zJo>Q;mAJ`H2^osQ z55|X!j3IEfG?Er1k+tE?zz9;n%q0l9RY-CjhF!frBI=SZJd|NS%6)7d*;>f9TG4IY zQp@01B}e31lvKm3q$;en+aq9~&$ov&B>RvxKxH~8Gl36`XRPoJ$~0`pG)R&yJ4`%N>*#s0 ze3sXlE=iAKE3z}yI$D>9q^zBk;mVzbF;Efy(^3oj-_R16NGQ_W8z)Y{m_f$wVhuiTrvuiM(Cw2`^*@0o=FdL;SmZ_N)%-CU_`_X%7 zV8-rc=N(wMSu?ZDNw00TZBk1pBHOIPOBt!As2o*^h_$aGM<4SzUMk1&R}j(HTr49N zb=>m?b1mMF#JWX$KOP$Yw)TEJc+qwBPL`_D+*s9kaEL&Lb|JO5_4?kC!0N}nWJJpS z6wgItKOO19Yx;p}b+qQEuQTv$eX!G*3e;%SwFE&SZyX?{nIZf;{&G{!kU53Q?P`&8 zmsN7Gn`?ng02bF++28DJhGJ42BB3~V(WpZ>&EC`iUyD@h>2HUPZAwhEP9ln99d=u( zde%H-t_8Ks43#X;Q$TFS3Dfy-9{T4LuYZA@@m0vvWbWYqS~; zhsS*d-@H6NhBUG~K04x}F$d2(_?d2-fjPoqOfxW0U;u)P8zPh?96TyP{0nRgC0~!? zBnVczPIJ!;>$)TTQfQdOy_YDj598z2BjjxQ+E1rhp6^=YE9X0epQ>Ozt%ct^t$V-g z@|&d|`-w_@^4*zu<{u5S?cxdt>t)@4)WGLL2cy*czwc`uJeua&LkR1}G|ShJn@a7H zK0!bbdrWzJxbZLRjE1N|WKceynC0A{!rvO&|5Z_;7d(8CvkY(C9m$J(cSEz8(Rl91D^zQ@|3DStZF3D4NE zJUl=7Zg97bC{EUCr`b_*j>iZV7Iky{Cr||ua zzJ<(D7*a#1yRDzTUkF3}{2#K}G;6~T53?VvagAB*GwYVdo}<3AGZ||8sT9=so}yet z4e~)K&~5~UF$F2&>v6noS2i#Mc0Vhu&l{=Z{67w5Gp%!eq?X_E<3Kjcn)hQazBm1t z=b88nzCzuHQK9wVaq5-@ z$2;Qr+E4AR!XGm%~64sPi+ab!emo!yUoWjaIGiWNGezWwC8{vzAKau74f!EBjxK0;0MMg*KJ z+w(l}Xuy~zdOEQ;xJ=yYWP{jrG2h7w*bibSvcx`14qzWwih${MZlg)KJVsGI8aoL= z(UdD8PMUHh#&1)U9KYlz>V&ajSxCvcdQRF8cBIAMT<`^bN<8agZP|xnnTxge#A~Sj z+{Mmea;kTk3qcjf9rjc&Mtay>w!Qk0hxKB7<9v~u#4ZW^OcPF~J>gV8lEkiOaZO;( z>zE?t?v$+wlqDi;O<+CvL&ml=Fg55c8I&$qW5=*=i0K)(WZHwp{lYtUSX)w<{S#` z5{KHd?x!u5-@lsUA@-Z0)N;lb<1(0eb}=S$^DxX+Grjo0>OckU1r&VU%RMlLz+W4y zkcBwKVtUBiM24ZX?uSburgfZzwj6rdC+dHMrW-_A7?{Y_T|#jTwAkJ=j?{zou8Nu_s`sPuqa7(=4M`c2hJ# z2E(bBu}2KfV(0cH%|SxNvl3PhHSNWlMlm=MD)W$KL^gzC#Of?oouzOkNf!hJu zZz{If`gW!sE{qAy^!5WhG*jh8I)k^iQ*#=llnx#otC1Aav&<~++w~Kq*vkva{8>N*+%tau0@O>E|yOqvff)mQ1yg}Ai$&aA(YFKZ z$EJweaoCK_6PR(wq<`=BMHx;HL|tLL9JFF%SOm`$*K}kB?Udh!(_Bc!gEWcR6)8l; z%N=3y0%K=W~u z`PeKb(9JTGs&E9GMDqM^^GqO=DGw$~P?ajDO}rv=pcToCfg2?}W1l$Oh4m=d?2?KzXd1#{b}SVHq!P}cx@jdMt)PB7 zE-X*Yh8i)h8yn0gt*V~Zja}^G#jC2n=*0>gykt#voBphVDt;OPq4)`!wXV7DiPuRDkSr?Um z8@vmOz8$IKND&6(dt%~1c5BKLhIwyMSUWpYRpBrXuM_$p*5$OrZ^r?`LP3DZ^m2eA zqnso$f_*r=Ny0F(dC*2Gc+DWz<}c6DQ2!F=VU)7axUQCN2RkRAb5tDv7uE>P29*}Y zf#RgFW|V|(GWlI*Q&Wuhuu2f6h3zvFyA?d<@BZ)>=j~h8KvE@FG5%d zYQ{F&nNsvR6Kh6Fc@*7-Q=Er`X5%{1bueq&We$wAVRqL9sxb<=e^->7iS}ZqQG}H3 z6}Jy&8ScYClp6TNV3v9Y;9-$~3gJCQg@Iz!p}@kP=Q?LJrfkg(3i#QWm-h% zF#>d8Fw5q6yH96*BF_aWJ8#OaC9*S>{mRo-_J5wv3i=QzdFBA^!Iy(foVuboR&}SO zqLU!DG~-KAe>%(TJ;#U>eF9M(yA7vOJwn+@1b34v?hty{9gh!EHlpplO2yUS=ez7s)D1TWc4s6WHrE`L&zA&z+wl+ znXy;gHN{G%L2xqKX=^M?({6cO$QxK95q5zr+{4s)%LU@>yR9;kFhv5>@B-1_y zrWr{LNiv`K7L7H_R8I5+5&FzK0uEY7X~z2@(6O~4qwtAJs3z<=tQVb4mhGm3 zC@s!4Sq86rTNIfrx2G(n3X!!ujz@&sc<`Zw2>^vT5kfN_6E!Bwj6D{L25nMAlgFEX zMI(u_Op%F=4^y+j>558WUUX(AjJg=%EbN8@Dh3|x%u0NC<1z~y{VK*U(LnQvL!E%ZIT}F~~FcP{fPbRM?`?) z;$m!XVIL1zJQ|X%#J&}dYyx%IIzW7T8;gYv>~xv?5ko$Tnz@_4=-7@dL?TQxLsTG4 z0EtYnCww7N&R}_tFG3hRL(YKMkkT)rhNNOk-9*ou`rUU$wF$(==v-SAK6nNj6GMwZ z8WP>a;0WPl8ikynUN#lu<7<rMtPKIBq79+1G+@6RcEo*S3x=w`#}7|g(NQZP?s!dbH$k?cvQ=ECB!61 zDZ1p24aft5D)ThK1KMEuTSu_;m`#)f-q=$);wG;+2|@xR*%%iE&D%wAZE0!eCzU&q z6{1>}q&U>i3`}Qsdmy9H3JP__3G0O9azJv$A$0>A}LI}`X zB_S|Lc9@;g*gV@5N-4&J%Fx@SWD1l~pu|q0cOqK_1xoD{dLvs*6`Dh#ayyCMiF^wv zP-&;oJCSWM1*+^6dMC0ir@%rxh2Dv5YbmhAPN8=!k#8d<)!0e&PUPE0f%SF@y%X7X zQ=ryPp?4zNehSptDfCWcJ4k_gJEi_&3dQpsXr5$??}W-3pRu7eN=adhZzaqixS~3V zZ`lQq-d)dPoo0A$PuxQ91N+2dV_09`oc#nl48>-f z2%QT{-W(CSkY$PFb6D87`*#xmL5YpOllVC$R(|%|V9!4niM79z_!cD=fBxGgJCDVb zH0N_scn)knHj>jRZhgx*J`Qp9E#q#bxRNhgl-)&fb6UoYJdbtE*hU&C=z&)*UiDwZ z@%d2nzXWERIb!4aEL&_n59OyEXaRG``K*0tI|>d*K?rs7U0--W+<89CRkGM%Ue~h3 zMHdj1FIzxaMRALp#|`59n+LPR(-$I3?N=>|IWI!o@s@EDD6aDBc)nE^u|a%!A}Blh z%!`rbV9U5SC~gXDCe3Ia`4i$6CgLXi37~2dK?&-|KY@HZT9U8vV4QqAT9EJIOISzI z?h@u{h53`{Jm;Hu#*N~g1f8?0TV2Z1nP>6Kt7u>4UD5M0wm7E(A!DI8jGHy`aM1-` zU469ZBF`@5ar~LR+G)LmcKzvq4wP8DJjz(&FQTOjGZzp*4>IriGuxVTGKqlXVowUM zaKu9^!De~hyPVa)b~NP*R>n$1$jjotr%7!B@n*D_Q@) z)&Qpht&Y*|hD%;&G{Yc88IjL2szh1`jv^J>q#V*=oF5@w+H@nCnepZtkTo7Ylz_nX zXFbltjJN~pwacuOzL<}Qv#%oCd3DiM>{^yxH=oEt_gHwxN8LE9L9H20k1@uR;;XA! zR{s(|QN@LGCD_IJOlBAwb*p=WnAzQ^K(OI&_E+d{btZ(!|x`^Xt1w+Nc}K5^v@tQX(6 zRqVcl^%fg$U}rHQQg4L)eT5i$BkRDAZxyd*@vNAS1fkr1uLs58lR(Q>d>Y4eKkRL| zW5|e-tybMNqMIe~<%TA$oOLK+3Pr@djwqt^)Es4}=BRASaSdpVG6yJzuIP}&M+CWy zx2UH4)EqSwk?(QG-AHg860hCGQuv{*abKYv@%l|{D0W7U-ozf} z*4(R!Hc;8G(g0iv8GW)!jP_DzR&WmZ2fDv0MOLzOsY&c&v? zS(;cqfejIbpynW#5~C>Y8=@vUavIUhLtDis?RZv*7C$te6aZZKD3b1DnP|D#d>cChR?3vy zVfdOSMyc1m>h-F6UGqO<*J2kzJe1Q3OK@Oz-N59r>3MM>3shl&1g##?$~Tz6-?P?_jwJ4ox;b6))Za4t-E^XbjAG z`JJpsoH^^7GbiksTzV$k%n2uDVotR5{|a+9V>FvNo3X3n)+%B>x?bmZx%EF|&Uc%b zbNb(4hn+5l{f#}$&Jr8`#B}^V4D}mkLZEv}>Qa)?wGHg#6 z(r=A=6_bJT7|J6XbutFY3~|=o*h`^4*U5=hmXP_yEMCdQC*ua~%D1VsM9T8qW^u$O2UT>W1tieZ4rl{5?hs z+Gb-xwo?fu)$hMM79gxN^IbI-#ELrW20uDcaJE0~8ULt(4<1MUXG1oc|HgcuJ95&Bdq0n8h!g0;H58yv=}A zQba-?Cxr3ucyY>G(BwhU?Q`Oi*|5GT@rfhFtbc}d!&3y9E+uOXkLq+DJ4FnCfZaj| zNWi0#Ko^MzCSrCAS+n2oAPOe3(GX;JPh=OdU_9o6q_c?WE3pq9nEnt;^~6V%=XhN$I94QeA1vo1|1lNB&_~G+5q12wM)WCOe1naaT}9@)teq+ z|6pv4IP)>K&^2i*RIK^pjm-gRCXy_D*8RnvVz}%^e%YEhhc3peh!FhA7>A?dWqCr9K{AoX1Qui{5kNd>#iz3oYiEbKhCoyof7mm|=J7n42)`|^Ew z&5|+q;0t%Z5fy2U``IOKnaReu_uFM_e!J|P-zl3q3*+flF=!TRkJr_+SfTr%4e58V zcg`AHYS^ZFa&yy}gp9V)Q>$$x8Y>>{F9F5dpiVOe&smNCaIlQQPVwR-^;8Fr*Gh zND6kjIOAE?SNcp~8b!7xgz!%B=(DVV@4=q$bL=va@*KN2^toIPLYft5|Ango{2md0 zj$J48=h# zf8O*tETf&o9We`_RZ919GziW|*z$$AGDLO%IqU}BZkCr!N%G!Uve|=6U;7rf9D_TI zPkjD}nD8R&#%76cUS!YX^~7A37ic(4HzTVIIpVc>ELVIrmnFr@k2J-@c*V^%kXkts z<};M!uwhctHY8=qq}h}NlN(hBvlTaYA_+>eW@y?PEVQYk-7H;A_}*X2naL{VE&mjXf6RDY!b8E{2ijHSmYPO;sCc`%qzqoMbQ*M49i1dx<;1Ve4gG{A5C9*T0ZRhb!ZB* z;6_;uw>o|V1PZh~i~+!P;2u=Ga9W&RemMQ&WzbC(^pP6HS=$9v8i#4C=#8sD=Fl50 ztfV&%pTZ9cHm5$koMAH%n#O24wkEHANuj$?c#Lqu6}dIIOqGaN$NFd*Zpq+zK!rZZ zX?jU>B~{Xgq%t~$TsI8zKJwDVys+U&+SxJ+h8;-(sz){*vZQ;fC#{$6c7>hA8i7@(4^b( z0);Ne3k13ZFVN>gyg;6Dc!4@&!k0_xpgT;gw&9cjx+c^zesfmu`Ac&Gw(FoFgn zmTJ1)EP44DNPQ2HTBG|$x%qSanirZ3;^RoeH7WCOT0D*T! z-xYA}P^tan81y|sN@~{a-qq~JE(&o@t3_sPfUZa9A9NZ-basIex#Tp!(6Bq#fTX_|V&NK)6z@%IK+<0% zN#jEfA<}M@?qVR1zLuR9U9bsANRrac=*tr!xUHBQWk_7I79>UfkMI`#*RY`7t;8|OENT2NJG=pRR7+%L4<0je^hD()wc8WT|!cu0LjO+^TSs!dqoJCBz!G>;Y+^7#8u z^1Cdnp7=J@Ko{toY@}EV^L1mcWYQ%-Q3t={nUAaFtA%4Ty9e&*|J=-O%4h-W zW(Ddx1?eQxQ)l~05kmyc0C>VM^NzT;78aAK;;CAUlIO&sTDWe{6=!|GuI=>`aSGP* zuNrvc?38qF)@d9rC;XPLrg?J-p$tK=mZG+3qW%Ny5!H*NEzn{2h<;lb7V={K7MMq> ztIybqog%haJg|+mNnh%dtcu%1aJ|AM+6v@wrU#4>T-hio|C;9dsu$kO8VYFBZrhF(rM`7FrE{)>_J18iD&j;8C5Ob-NV|m zwc^ko*0XS}EKjEqxal}k?vLQUr?~{wjwEkGq?wbdWKH8fW&>Ph^u0lpe$3h!?}BNl zp3(FeV>~QY?qKP?m&hEzC65n>zK$1l8SY11j|Ad)N`KET+K28#nqtV&-s+) z=1fwsBE&XOSQFsN6u4=hVw<5{wA;;Y;Zt4zf8Rk!|9`OW3d9-vSeM|W$*VN_g%@bK z@CKYL{<)8hbv14y+nCt0kDc?Y4BhrOWq5EuyUrj8V0{S)R^OfChy5&v160~)*t5X< z&!4eJn!tedsh*>su~&Z;`P%1fDV4bS3pUy=nI*P-!CFz^^Do#HvGs(RI_rc5S{;St zIVhLuRUw&%AIC6DKBP9#Xx9-cPlWzmngclxBx=yiUQmxnz55cvEQZ;kGzH6g7p8~! zo<42ui?6JbTg=3AFbxhjhmmFhP z=g)wy*1~CmY~18Yfgg7veZ==kK=;J>{SL9}0L#gkGaGngW|YU2hN}}sgAl5kEq*+} z&OeK!f7pq8VlffWyOjCpj!zgB`s)iptq67|oZ&!U9ZP-OMM{T`unsb(R08bFI9!CE zVxfl-35ln^WIba4Ej&8D5>8g|Y+w~lz&zOG1UMjI#$Y9Z3-;B{v9^24adJV z?S`m}Ja|L%(E?M$e_>ZMz9jM{%!*7Q1 zgPPh5O2qvi)ZxyQ2*m4%c*Rp+GqXKtO}qOMzm7Q+SkYcYIxhI=!*2@X;0riswfOOC zc1!yE-rqdj^qjc=AdX%J#QuY<4_=wyu+w5&ap$eM0DT5FKZN64wCKKE8d#@TiKQVj-bryg2*~OKS@}aKT^# z*CxD0_z;ZUheYo~&`b`AYY(yBu8L>D)N948L#z#=-Z;ead%Q|VoBF}W8hQ-qry0d= zJl6Pqq?5Z%PA8Bk1HKm$7q1n;Z$YU;V&u21U#l-216ebyxN%67e#^46Y6+qKW(}2) z*C}9OjHMg(R;}puJ?ocIdlX3+wr)~rFAuv(pS=*Fx})OZ@7aA~#SiRaAC?PlQu58^ z4dU7#*k$||E>`})uHYLRL{1|chrPSo8l_Lj^Np-$(g{~IYwiT_f0nprD@&>Vu95wf zwcYpwYAQ?auE8sxTw_dT?v_#FuAf*g45?551X1*qsQ(GN+n4y|Tj-T9h=QM4Rktbl z!5%6Sg)nnOy`T?1z!k8|;cB+039gragiuPup@8))OqG&( z>V&xKIJ@`sCBu);JF?}Rzg#^8{iz`}&>|+RN|G*(%`RJ)CacwQ!njTp{KBepc9YDG z`^HJ{E!#Ja4CT@{?j})m0*%=&o<4!qtPq<|u=CU40S9Ah9&B`YTnP6sHFb!7C#Cb& z9VgivsSl8pB|n8nAi(9vQQuPod6eAeWMN4GIEzs-n1b#`D>B}H>Rz7JPc8`&Q%`Cd z1`V0l&m)_hiehn-h#iItn#%IjX5O8heCj06KbLT#F+qN1ZsS209|~I12?jcGqV^h% zZvhaiM|xx##a_7+D(~@AxV#m>ITO2OcQZa6LOIVZQYb)3JOrlylhIi0+T zHJq}Pmx?N#=Nb*zih*Z>YDMD~o^A+fkTJ5G@itilMA zbY7~k(EW|Un6f*eGO`dSk%J1YIP>`JWTKbrJ--bRmUCLQ15NU^gB6+9Muap-bKetXGZ$1 zL1};CCX71)vI0pXaXPwZ=y)K+%_l%U2T=_5F;e~X_)n0zntp;zPy7T~OqV!?u^PyX z!k$mR9Y()BW?P7z@)yXMGJ=6fkp9xOfavvgGO;3-G6q?rZSyb|w;(Lt{*`bgDWQnC zJBl<5H1o=nzDJl|VZXEQ^8z;*Va;rKQq1=2oy6rX-ZKCrpCka9Sz?K2Tzqg0>P0<6 z>+xQQw;UsZxd3wuB0)9G7Px+!c2uq&K@cSyQ1_C34}3&h7!kxaf^DR=mTa;B28xpN zDV~^>C=PlD#T}4wF!xhju&?lYct*^SNRU~Z@PP0kZATW6Hp!rj!?7eOmvn4FDzl6v zq)jL@?%PocHwkiQA%~6sA$o0p7eC?i@XSm8bM&NHP5P8fq45FlLo&~t+VLXh;iE1v zTdO@)bQqbJX^caimiw@#_cxPZK1JJO0NHBe8?{wtvhPniX^&3d^5_gJ49s@sx1!!j|bJr-Vx>Ol(eq73IrL375AFk25Qp zB51V9LX{N9nt;rqu=*`snO@Z_>_;O5qwawH1JxOu1P&zUbS0mZxZ4ZJ5HLRU;)Tm+ zAoa}w^zzcx8*+&w&hzuW{V4;PT_nA_8@qh*WblGFK->64PQU?*Ei#H#exB`G0E~?9 z#C|{T?OIOXM@72;ADvRQn7*Nnz&D&8xki=vM}Tk6Y4F-8w5*iWOj>W*BRf&OE5tct$Hbmid|0Q(w^qp` zHMEIEv%F+L`bAr_bh&I;4Kko%BUAWwI`1sn(~PJwletbfyF?>EHwAy1-crU zP_2usCV znLLLdm?J*UbzYZFBg0MUWzI^qDl^NmBLh)f zLsiof_v<9?R?CRP35OeS1BPqELb0zCzsz0v8nVJYZ+IT>(xqV`QKp=v5{Oks?;mAU zDSbjn(CPx^jbB`1MjjvFuChyil*bEV%}RssW$B-Pr!+Bs9<5ahw9%Mt@sqBYg5fZW zej@cB-di+qpqRsFL7mfz<5Pkf=^{1cqfA|qNtc;w#Y6eL0NXc<@{xCuQ+%Dzv->KX z2x_|R*MTiq{F;-zFZ4@{B&P`{m0vE7b>WYuZFc$S z7Ku29^g$?ABgb&3tL`OOX?KeH9{lZsd9cyqN;OQqkjSK)JHw!WVJ@B_;{iiM$CB#g zz%?pcdUD*Ywp0x4#lsz6mZ!z>o5olX#=jBO3$1O~z8O#o@#Pc`^y0npt9HbV#!iwo ziTwBlpu{)9af*+7@igD^mgUo`(|Yq%c6z*)Qz5+G43kCnH>g9H97g+^VUi|_`|yG` zQ&z&;v`gQD|G8_Vzm2lcZGxt+qEwUKjxG>r9u-08mj^d}a?!vU=x%-ZR z>`E0`UD}u5!ubAm;$%MvP28H2)gME0z8KPPvVOg~q(A>Ni&O!uG|h8N zmvSVbezawqE)^p{aSVileSj+7DTWV(WO`EkIFMiHKmHy~a|Tqcns>#egZQ6ZQ#Q(p z@6AE{D|SRYJD9(N*G;GM-q^65bvnNt`+UJ6{KlMGl!U8p1!b29=j@0DGD4IM;p?zL zc==HN0Y8>3x|_VUtCo!B--)wKp2NNne>L&jNKiY1!_@nYVf-VUMSS86{-<=Pm@p<_ z{->7~AtwQH@|ZDwI4`6!J%{s)=>5oW-UE!dY&fU$c*lmrMe8k(%#JyaG-}a)1n=i5 zg#`(#iIw8o5xm0ySXAV%qrqLLh6X!UJ!TEWeHq!ACL5U%u)BlIbT_F4JP8_kzONp^ z2Vwd@If9QxePc&L3wU4LHj;@Xt@$HG<8naCqzTL zCoNrBpQ26)78HX>Afo(ZG&pm+n5kYhqj@LlnlDH5A!J{ZNRxd{eIRAfnY>+Qb9#|V zD(OYJaU`$sd{B9sFwW%@@j84y7YrNHW4ReB2}X^%Q9dCGt1^Sj;O^>wj>Q1Ljd?rA z!S*G@?dS2b#LAc=iF_ut0;|;MAF^=XWN`4f*jMTk6-u*xKGU|WP%P4;@%l>X)lIYdPMWx8J zTStd5I_Ad_ZR(1@-YagYIkFe))1EuiGZHeGw}Za`5~o zM8e;9qTk(%PI81Rm-(3@@04dCFsQmxKd_#VnMdYG0xdg@LY@Q1I#=gYr{_Q(eM$u{ z`Ikd=I`z8v7uLk0=P`L3^;d@s@C5nR#Mdu~uKc~-Ui{1VZq(nK^pbd$_Vn{F30`D3 zRzp?#Y=o#lyDcAQ0`#ktN&?ne4_iC{hIjlA3h-WfA9gh{tx)4 z1~>l?8r?gOVn<5!9*>T;_Psc zq1>$~i{s?hjJzo80q{v6Cf3}KB%zRr!r*Oa;f2q5}5QE&`H4gF6Q8WN9?a&tf za2IY6!XWDKaR88Zyq&TZ5Wo7)97FCh}sQ(!F8B^__tL+kFjAoK7_Wu0|kPw4x;DS zF|S0j#E>$?H~g`t;@=fjQtTL2fag^aPtm*}fd6DaP|5xgDgVAY##0K02cm5_Z0SH@ zXb<6b?C3chLI*uy>w5UR-HtG*u0>T{x-15d5FBsK4lW`s^1dr{5V39ViU!`CfjDn5 zICbuJExI+Y(6AIRbq8ocil|#LYYviGcN{#6Nz-5}YYzRA0)%;h>ZgiYxZDS9A*Rit zlc}O#*fcKQ09rSX(_Bo?4iX<2i@k>iiB}D5zV3rH^e7mtp?=B;0FF%&p2#_#zyY36 z1{{$Ds%Z9a!Mo-xGtNa{cx_(JXrGD(1?vk!@wO|ET7d zHilEy2N;C<|H6V^s4f{v2e7`#bIUo69QrV1^&}o<4j#o;Sv zrk8KeF5r) z=@Xv%zwiydrjzjnV2AN*PjzxXb66;KgSHi`;pGoRlPG&Na5+%Z_FBlI#UF?PuC2_Q zW>M{d;=NiIbg&aQq878#xAh0t ztJw*o>HEpz&2llFZ&ajiB!<)|Yw}uv2ud;7xOJ=xdkxl05PDi8UFII|1%idAYt2;6 zksbL=o; zT>yjjM0Pf+E8Piz1pr&@uxb`1Fc!WT(GF`W zz>Uy?z_#FW8hs%K)%gvzII>|~hohwfWA8Tf-D_?KK(5^ZCb5wAeIZ^KBNkD&8KQ3W z5sMgt6&NTK+{S@-)7PH3uyX;W%@9qUfH+eWt(hVE#*c+e@hBt%{4uGxzlM8jh}33g zXov1k7dzVK=57Pq8Vdmx^Z|^4`N`aD8@Jk#_I)x?m@Swi0#OkBoKAvV{OOsZMh|WR zQ(>!uv4mM*0KOaY$|S53ToOHeVl8_IVg0diR^sG2lww3LnG14rPykk`AeVN`6n8;B z5j_iRz;6>(V|%*8Xoyr{&tZd z^H-3Z%*ASJUuHOeoCW*pVw(D;NYDdO?kk`waO$sUF5>tbVz4}*z}8(l7j|$D8^ZQ) zF8FPLNSH@gz7+K$^yujJp-RQ*G|3QkgWy`q{UI>V^BJH>w$jK95b=f7;46_F$seF) zJotsu*&IM0eFd2Yc;K(ZZGXwlr-NTXM^r?OXN#?3$fmF7i1W($nl3DW#rW5hl_@@f zP4DdsMJg_97K+y}j7f_`Gl)-~StN!>rfGc+EI(vdCX%5=xw1$!LTG^%w84 zLc~qH_7#gH=tDXLb1_JFO z*Z!eMfL&z775`WLwIi2ZWa2**aoR=Z{zDO^i&V}2w?)YGVmyleZGIq!NWpmAR`L%; zuzLZJz^-AB>7dI{5s&nWQI+*3<;@N=d>eLP4sxj8N>M!yhZN!@^49E6>(0%Y7}-o^M1m z*Q7{y0OU52t3*uYqhL~WMf?J4*r0JfwMU-b!IE)-ulf?|vkLor4h>%=dIRztSS30b zKT`Z^SeX>E>W)z;LhD02^m;-tAfm5d;0pT)0tT4;{beugub|b-^YyC$*i4Gl{_q&4 zvszW{z}*GN5DnmXWOA$8;Ur{appYp6tQMn{t3_PQ2I$dkLlC!friUGS)l5%>xq(iv z#(PhtkTs&SF@t)q5he9?S(XnQa2nAMSwJoU_2tw2(}#b0^3TKORC?`O@m{M>4OaCt z!Nmuf=^7vb(8*1;p;3n_jei>PPb<`p|4zKwq8Lhb;1d2xLMvl2s4%SrnhiC54QutT zuVEl5Yb8$|UgT|9wIcsMbFY=;b7zy{92CW35j0J!TyB^yM#xMI@y z9ML>_C9D)#Ujd0gfBW)0U+7Sd*eS9vQ`9EpWICzGCQ*gtMp5VA*0450@a?N%WK!2n z;y0s|?#zYkvD7;*R}2uw4w73$msVQ(psXB-n0>c*`0rptTybV=Ob4QBv;aawsmV-B z#NbZQkS&mNWm3Tw(e(CzZbX3>y1AWny3tO%e9;5#jLsKLLP~XdBi(*pKJ>Gh^k=@P zn}S6SWD3d-mngpjIrL-fP7W_Eh=>z_<$W-h6AQ+EX8o^6K7mZoPuQwV`g7R^^*v!!nWWEDIjbR4pAS=hhj*90X&Mb#?l>e z7b5+`(#8@A(B11sq&qwW6ffHYLT3d&0q+8)DA+2%O{Z87dOt8OdfrDz=F~D=^Fxjj(@ZE7$J5A7{ZK9KhBU$^! zN3h?wEP}D2=0*f25@|B5RW?idRYv1Ofde04&&|*@J>SuM7zgWeB_rQSswv_$amfy zfci|z*+XE5uy^Y3Lg!9%7y?8C-uf0{t zuv&eu#g*{}d|As~gz@2TEXLclLnO3dyz4Og^B(Y3{M@Gg%zYk3y{&k53^~+5H%dl%J=fVocJmGz7C(E%Xc?azhTaB0@ zU~z3il3$r^dWLL6$~W}l52And(ye?CMd7Fbp{th}hO?ftCx^F@AqjBoOK@HVOW_Jc=)ZPPLF)6^Vy| z-NzM)yYRH92r@w&-m_O!##7>6z{Em&e6P4@9HRU8i9*QZ>h2e*51s*>vUUGB4pcv| z-7)lO`aYQN1MPclFB@v#Ye(Q<`yPu^;6N~49&kdyCi@Is-w!?P2s(K{R5K<};6YqQ zQ0;@j>Jz+o9Te@2*b!f>W5bNnnd@vJ9j4CbwVg*Z4vGH8DsSjve3&caP&Vu_s9v#{ zY8;`B#bRisBZ!sHffq?*QL&azS^ha)`bn%!x{4geS}>2f-H5RePDMj0pG^VVNb%Xg zZe0FYPe;J=)*ZA#*TD2zK1t$nv|)=L)3~z)t4JqMy7{r!4 z*Hiv6EWqXD{#lHMaCFhnqKj+VuXyFv^vBO4)|JWk=c)QHqDSyl);6-9em-qIE~?Rl zU&PD6$$$JJde=Oy>u9xKDB}^vZ`iMTUj$g!>{i}B{+CwV5b2&h4zcDMF5;!?CqyGx zzJ1^O1Pm`v@VRwXaY;5$*#XWNO^Z*6j=@Ecoc3)F#0eJUl!&LelVXbRa4=eRQuOhi zZ%T=$L}{27Fll!?mrABFr$ij>J1L^*k<+4ON-0O^66QDtEtZ?bhR}F$8V=UbY1#Uq z1jtJ>kr?1hOf~G1b2%~GpD66aVor?ZL`|ud6O8@r@>>Bx0`GAG8c@U;@enkzFP#zn zAj;i!Ml@%7n)jo2Hw)1xZdExe8sYeQ7HqLWJ0zo=sk5FutBu7zIV<9E5K$Nq7l`&W zT6Pwz=pdatE0Q2zh&u<0Zy5DIC+-i?W-i>kdC&N!LpI{*FIzpR4b^>3@hSM#|Q08+mAO z5?tsVA=VG}Q$;-89%3E0pMvq!Cd4{#KQUl$9b%Q*PYiBbg;>AaPuxn&5Nq&u-DWjB zwFt4&?I&)fd5AUNehR}=Gk}2<`!NEKO+&1W_ERXHnuJ(8?I%RN4{gkg{YMe+)Zf15bKiy{Tfa@H3+djwV#6Ul!U_}?5EHaJk}4fGHztn z3$Yg3PZf|?H^f?d<0&!3+F?J1Ag@k{b-;e&7p{%*vY+^cYk~H#pSZ)CA=aQB`epd3 z242m6s*sQ(Lp>qZH2X0enF$yo`-z9o9b#>=pLm4gL##ski3hrRh;_(*0v-*m7GnK) zqhwr&^{f5FQyB~G=uX`|k4)7NYm)th*G>to5@OA?A1fm>26JLRRl-v=OtkGM9*fH8 zu&g3Nep)B(C+;FD#QMX2io#RH5Nq5n-65mu3LzHRPuzWE2*Tv*JRYYMGsIeHKXQjq zvu?dnG#nk`324{)!%pQf!PDovbpu=jh-n@dO2hSexx9enlLXa^OZu z9CdOEPaU!Lt0EJ$;?N&-qtSQ*Z8FV%;;#X^%Cet$)q;~#J67uZ(9>94epW+;jCtLbMY>)agW;tscD z2g7E-t%#RV$0={WIe(y?|CY}84s=SFfhZS)U9yvF4esM!YY!&pdX9U7(&#@zvBJoqKA%3QN-2b{g0%$PVZX-(U=x zPC>A@mhw2;hq2YGuNX6*cs>p((ihCqGS7D-+a6H({lF?6uUQt3Nq=Fe?efKAxm(dw z-Hoa=J4{C0QQkx}*nZ<{s0=qAo)ftC*d9Lk7?%7U(^)nbjNsD0hDkC$_l^vQ7rDs! ziOfhD^=QsdwtBsd1NPMz+Q4_Lhym7(RLmq&LvKmNT0p9KODY6q+!2>wo%q!c(+|o6o^su&=%y z_7aj#z1pCEfZPI$M{`!1VS+Ri1dR?~sFRI1SQm%o1{zj?c}xg_1QxwtO;R&#U z*=nb3RbavJs^B;06N7jG3Jbw1fNiu%vrlAzcn(avh53`gyt{nite{MUc z{%34}HjX}c$58wW1oiS^-*90F-4RrT=ni0lb!(C%3u@UZ&QS9u`Nv6ba$N{17%oR= z9fg3id6*FKR7aY_eVO}%&auYg!(f^2R_rZm7yc;%6kw>$8FB?_X+4!lPO%I4>oBdQ z*xp6q#$i?EZRBr(hlQuOkNq{;!j~t2zW(ukdthGNIRe(EGiNs9q`3OA$vJSVl~p(tFXc?Fetc!Yzu$#&J|>nm<>P<5N7qn zdSGdwB{*=gG)L0-3Ni_j^R+1CDgrzQDgRpqS>1KqzK^XaTUOk~N;*V`Dzhtqc=m;g z@*Sgu4pfx2Vc{#IbOgFfb0CQD%sASo@)+?~+eXijFWU4Q-3{N=1Ixmfq zFW&wmAPr0B-J#~bpb&f?!X#nLY=(9sqn8@iN`4b<-r`Hh)d|$Al5Bxfi$AFZAw=shH-9bCD|P|&>bqv7vS|Zx3VmBW&VZ+(rHMvygPKJHvb0-2!@0Fo@iMMJj)-^ z!2WZoQH*paZR9OjKK{T)T>)EGW4y7K@Jg+%Y24yy@4y%t@U8f0G6 znOObPT!zxITOhQuO%^q^2)Q56{v=FUn4x81+IN4EsHlfU65bMYcfHb-5cY zPFe|f^de~MQ5dYBU^Ti12T)G%t&{e@C~H(iSJ~VZs_QNx?WM?hHj(vdDQ16*(qh7X@7Z42LW*}^40(d==!<(hI3(Q%s@s@nXhet5M2m< z5+1g<<9vr5OO(Nj&O@{)+%T8`3b8xh?Q1*{nq?jh2g9-v{vP4V@2da-8P}iA@n9IDZbFw){Kab_35m=6B0~UOuO~F|7IVQfoPPcOSJ`*APSW)lI3yz#-j z4wOBXcldEI$9#Y|TN^nbkCxR$u+}Tot(J_5+o*Le_VVO)mbQa6!rH@WBc;}o%_E9A zS%X`kG`vqvE!n05cjK_FMW)!c4`kMsRazpzfz+|y;lYV0vA#C}!#~et{Htw7d?7Mn zMZ((?2p#4LPuG?;vHpkFmiIv7RZv^TLWp&`wrm$(=J6=tw7i~L)sYQir)z7OWXK`_ z2U&0S zf=~I8UTq?C@eM7T0$OdLzD;GrU>K)4wYVmWW;KmFjPsbELms?ja#{d|PxEa+t@yAxJt($N4>1$!#uRPG*d|%03%FB84>%N7 z=W@rDCx+M}D#;?M$;{N@x_~}tDZSAe2b(%D2b68>TmqyFm&`7$egC59yFD4Bo_C4TGCp266Z6OQwDR84#_R=TL#v3;fKzEN|*k)yMEN2Y5Q^vA@vG$$V1k34(JF#+B(bzj>Q`f}9=yx1#zEgH|W!U!> zJIXGu<$Qmcp6w_nL!x-4qfDu7OZXX6x>=UUF~PL3Rd#}!{Sa&eIsFiQahJU3flVyn zhTo{0e-h1Y(#>cH6IsIS1IB}o#c_(T6sK1IfX|T-_%Z`@bG*(Zx0XKCL>Bfz+fH(v zv7WYf!p8Gb#NF6&UV83sc?`qerL$}Y+o}gS8#hqoX zvi&A`zunwVd>1(u=1W;!q%UG;HL87&Yz1iY)IG9)%N>AxObO!`e1wnj4p^7~X#$dL zk{{XTdQHgA)kLNf-HNUtazCTwuCh&p4!Fs%{AbwmLNhk9E5fi}jiBXSHmrur=-j1h6kPP~@H%x0t5*QtcIcsL=oZA2 z(>gSjM)i^pi7T_|XfN5-od#Qbn;Ygx0=Aky)n(!TYqOD4MECZVuej3Kq_xPqzPEhY z5T`Fug9kzLUAsh^9+b0ibXebqu(IdU%!lN2iEMcRq6TIql5epDnU>WwaWQro2#7SB zCQf=-?p3JedIGy`uJ?f_WR#JTx(c?83N95d?v;uAU5H(B~?xjk(K^%dtt!9wlDr)5%;Vt#_HF~9X` z+;cJPjk0#GalY@X$9~88l=HOQB%q%h{S27Wh2B-q$Qnl2LcKpZP>V7bdQUzpuNbkj zIs68UulRsJZMke%{v`VId091hI`{#=A8YP+n;2xTS zC|CJ|7Z3-aO&{4rd>l%z_5t!qr!9TtZP8Z(I9j@76JdlIV8ayis>La)@0GFzh){>(#=&d zEnM|3-Rm%}tIGAt+_%53S>*0Kxq|Z5beY zG&#ht4RNa-b{W`w0yD0lwkBxCf@vo-4qg0PyyRp*RPXwRtPOARbq<8?dG`$nYl=4N z`>Aip`mW>lef}HrtwtlbjQ*-Xh`29vj(!sUZLBS{U53G<7)%G+KFZiwiYpz{3_$_?gN&d!YVQ!~(Z^@5HVFWHVJyAGSXf7I)Im5WP_Nbjw1}<8QEHslVSa!ER4Etp5yj!wO72t-Ll{#j ziI8mZ%a{vk=G!v5*^qgtlw@7Q#jX=(8)dmHJp=63a^<7ybHV64uv^9i@ zM*Z^4*}<0sCad(_yK*Snc{LY&3itwEu6#7>!1sW$x6!iqhl<+3}OXeG* zAdk+yFW0sLL8Vun?8UjYzv*iqp^>=4wRk2!%ZA08-?tHK)_y*2Wk62d4IJz z;ciyR0+efeKTX_!40Oh?aG2VCDF5{+Y_Wt$=0ErUYv0s_rr4x6yQc~WZ2jhO|A(%r zIKvSpZQ3gqf{%ajP1WK%-ut)srn1APc1Ohu144kU9Ukn*%S;Qk--AmhfAkUxW#Le7QZX1iIPp;gDK!&;jJ)x5ctbU^v59S3CrCH1Y6(Q zy$iT?G$nluAh?7%CDxE2g{7G zY$QSCnYNYp163UYiR3A2IYbUJF3~qbWF6Nggr^0M`tuO^8hU-GN?gpx)?R0PD5i-DlNm%uf zautFa?Hvic@rl>?L>dNNP6N9CGn|q}%jUFao{XUPM?v6FOw&inNzks=9U~iwq5yjB z6FDLFI~bu_5I`w}YcOv-0Q-GM)kXp2UH|8Xj3V!&V?bsbAJc`gUvLAk36Zt+HKen81^fk<84Qt3BJe$nmg9!|UiUb4|Z~bHA!af#o=adz6bHSky z>Ty9Jfu7biyCC9tvS573b$^rK08#W?mo*Vd7FaIL6Hf8xyCGyX5untX>(4Gh^-O;@ z!)xGSd?RGIU3RBVlVt?TJ~&w> z(7=gUQ`z+0M2Pa1(V>Yl#@I-^rpZQBXOg_Ff{pz#w}_ypqfd>YK9jI;%6EZQqSE(jg-%4aeNk zZO7t)hF&MQbprQo?m+@XO%PgP$#PeQX;*^zG}i@tKY%4#TJCJu=UQ~vRQZdzx`7%_ zldk|8@^Hm0`}?`brQarE_~dliz(*jK4PGfFPY2-IOYcvY^?9`3rmA1)xv2Mrd?++E zJDcG=J`Osd(O<|0#{IPV3)vxbO8JbS83ICox@l<8&zQ-Kr>{52Y%N~ zosl6A7~=AH`f#>%S6DL>kOat2FDZQ4%9*ryw(P|rywn`oA!ah4U<1(%?|%GZiKsU$ z=zZzQIkFWWJc;uqFaq-^eU9u1NP27zP~2XMoGa^sdbxeBygiEGm7_SA6>ARs#Xp%V zs~byb-dq_MVV_s&YaGV4yXVT!NsIzU_#>QV&4Y0GFcr-M$8eZ}=F2y+9X^~d8%b;= zTAL{s-ZpU=fB@pId3it?Ec4!CcTId3M06nnJc*6$q$cgAE|lG4XCkm0he2bmndNzq zg z?8k$VS{12bFnISTX7f9SL74FCV(AH;iy~$HRnG#Mn?Vm`$tFo_$}&F28|tXVfCJ2S z15$%2Whol(e-QW?v@A;|v>uV8BTYg{XJvpEYv6D&0mQd_K(GUBjE`93ci{)jWCA1! zxfHfU)~m1^)p^b0%!BIzu)j5rQI{n$Hn3zUB!2Mnrv6Lhq9!+&LSN=F4$%VO+{U5h z2=FKlENh)RrEW{H%4X4rOJ#J;G$4C^bHVyyo|0>0SRlyACV-iQK!h*zu3QQt29d&w zm&tcrIqTLzd`OQkm-kiQtHEpxNYIi!ngq<+=O-E@`1~!)W&2PVAZg1kK>IYRyaGsN z4YgbW=KO1VdWCEsTRxwQLQ24_t~(GB%2utwccjy`72p{rk>_h!yX~dD=qS-jztNEe z;DmK}QOW=aqy;;}nOe_`pbZ8*y~>OUzMM8;+@Dn@Y{xmyhv&=qyO=gXuyP3SvMSS!B*t~lIc`M;hFM_Yx*9o`QC@d-q9Q=~xJkjwWa_|XKl)^1t=4JPY4Y1Q-2s`T0h z2>c2ue*@T`T~swk&cxH29GH>}AfZwr**c{Q%0f zYWz*BJ-qT$XcrH8Wffd5d!dhA=@zrxfipX957@c=%KjhQJ;(CO&RFlxmDFsde30({ zUOtU=x9ocmfKa~$ZUVJ*gx=XC8^$g_1dQX8UND7)QNgxgOjSs0Hvx_m((z4YLW%~{ z?7-0gTy_~?GXu~W)d0$nX@rl62SYZf^$tqgUZupL zHCrOt&47JB$4s1%2bOBcchq2uY!J136)OA~swW*kU_#8H^y(Jby~b(eSS^tTQX*qD z?o$!x*^c$dO>sPCI#wGjaM&4t{Zm_|r`m6}cQcc_&0tNyVJ><5Ya#YF`eLhWde1g66CeWYL;R4%P@NlK zL=Wp=*++jK1c6ZAf(){<7iL4{fFJV`M#?(PkEQyNVzzvv!xIbj3_@%Fc2rn$@!k%B6tTM44^nQUD&1l3y+b!eyl7-AJ+Eh{#E~HpNxr|6Om~WJV~D0W?dT$Q3EPmGVJE(S@>dEYO`kUc^95bH%|uP%mx!1EwmC}WR|gv$NvJ+eK;?1x!@CbifPN?{*8 zx?dvLBGl4|?K=tg1}s#z9FSe3=lP715jNR>n}l0xa1h{g9zA|g#?;OIo(V^69K1YJ z!=3pfT8NIg#6eq$T--tHu#~1Bl%C|WaJa05*Wqv{Zt@{al!NEHzoT3F^u5VEx_(eT z(D<*N`g_rQI(8Hzb)d8e%Z`of9ePL>8kJJBAqV1!a^PCsDt#7aFg=@IFP7b-_XFVY zsRn>VVEFZZS8rjp?Jky{lvPY3BbcGRh%AU!zsE*S3-^G~#Af8U0=&8aAx3Qc+YE#% zx;4Wr4YLpPL5xJy2IAr-%umr2vu>JG$wej0V|h+p`M4{6KZu)swDl)htKKwit;X6u7&x*bGhHXZ z_)sK+B;~g81EyvS#T}I$Dt=W~KG-h5UV!L(mkp&_$K@!x{fK+;by@VEx}5WW*qd=eZh#}{_a`9H*+Umj$cA+@;9zJUzQ~Ye zNcks5SDZLeC(=D9NSC0|yQ zon`v7zBwlwMccIk?JRg|`7$S$$hLv0`bft7qjXmZy4gi9mB@N+Q@K81yS`#`rRw94 zZ%J|s7H+d|0L*B0E3Bt;8#Lg&tkLQyXht5RntF@yo!f&kO~gQdq;?wj?-B^P8ZdgHwG{4ZbiSov3_QNP0N?Z1A@Wf%U_TWalg z28EQuy6aEsSSoh|!d1B_A9SVhi6%JP{Gxo}7QMGUoLWrd*Ly?2%b#AR_ufjOmt>8G zw%$9KX=iK}=!wl48Z%>vFqTY8nloJnsQzzqo*0osg}=#0 zVQfhqCRv=vR?-zO%cf%42I_oS))C)rpaGYmX&O!wFUvh}g6n@pHVdDO&B;CTyFs?O z=n6#nmucG->8Uvg2f4X9S`7aLny}mqV6qGQoqcpnUL**Db5(j;4`TduOJV5Wn5Y9m zm?jaQNP9ySgO{>x|G|L(+h&k;TDE^UBO{w_N%%Y_pfslbVXMJu!cG&uwE5-Iq%SSA zTwOlNh3jT<8jfP3qSLYiWV5knp2N+oOJ7lrN)bJXl8pr9qYcd((d%n2_da0BSUX$O6?{jGSAF@iyh8&ipjbO?E zraMFU3JrZKU)Sj@_*yd6{+^w|E|+U{`s=s@$=2Ua?}N*?I{kUvfx>&*PJbMi)jIuQ z+<_qMZKwCd9$3YsB5%17a{Sf?{|GHLmr$o+~g{V89B2>Xfaa!d%Ulmssp zR@Yy;F3-0Z2hfSwb-qw72|%yDsK(MC$p+4kvXt$u{i)LyCNzI_Api?oC}a`$Fas@ z*AZdBtPG={3|05;kviHn2g>ochEUgxX8|ub4ve91IEoBoO$ZC@`clVpahN9}6QE6l z?^7?Ky1S=sW&%0J5=_YlSYMPqV*dnK>6mgPz>Jo^8b^}y-}~wf$>Vph>!nGR3{G{cR2;GJv{Z?(6sn}u zC-7=YSIPr}*$qm)7>{_efPU$Ghy?~Q9ZI1rzach6Dr2a#Q#~L`XV46%Y6LT`0;g(- z%XO!!Qfc`NKr%m7fMvo!G>;m%R7+Qm&Z539)u7Vu^2M;cfW%QOlZ$0=RuP?Wspkx@ zw|jt!Ho~&DVUJg~GGT}gl59YrYAm+qP)4Bgi2NMNoRm8LS;pj>_K^b*fz(a&`?2I9VlFV+8xyI*45<2RS5V7a``tgV#SC-3>4Al?a${@twZ4R`KhdGx`k-I#YfZ&- zT|1(w!Un>$g{UR!WO+pvq zEf1oLjD?697>22APRtrdhbpRCRWkw;Y&RC5M-NNInQG(BSU}aHu#&b@k0@0iF!%i^ zq!LYyQq7?FD~M9ndlxOks>ljr9z`Uxx**Ua`4S4L)a3>1{1}CCw3V8PXZGmU89(^0>k!2YKEJp#w!4d2*&&d{ z;HJ({F7MMEjmn0O-;?lp+_}&>=9(g%H*EdbO0p&a=RyUA|0))>ZIi8moO*s z>w@dk@2p=YPna{DH)bn=M*{^6E0=3H?GHqIzOT@0$^R9;{v1|;zq0_e#N4@_Z`K65 z(b7*Nx7UHS$X}YUPK@L(?Ds9fJwiJ#iB?fq9vh=o`+z+FXx8pL3XM^%L`EKUict;3 z-aL9cM%5K7^C&$=_2u-?Dj4_!dDO6qdN!EC0l7hdn?#>iQHc>t^ud8zSc{lcOT0U( zs9_?K*c}X50?hoip#U{F{U#1jXbk1YsU%#k#Ho2Tc=L|)iHi$t632os2UaDdwox8v zDOuiKv2KUtgfmUODUSbizl=|ROLmTz8K8VK{q7P7jdqXpx$w&NX@=@}FVOrJz0KH5DQLv)FV43U$jK}f>RU>F{Fj~Nd%B121)hBj;xX009 z5Ein@IFMHtCIwt4P75Yz5)u+p!T9lN0tpEnwjMF|j6~E22PkgydKi7>QFV+|+Uij+ zRh@DaR$|M8d4ibT!zGZ>dtVSKJ!+^{C^o!?Y6FI1eGT;k5ag7auwC0i`)aDGz?0`{ zt2%*MP<pWC)TXZL00{kIUG;i+CiC2r!u8gM zU-j>GRpZD7kubSR09#`p4`dd2+tyRfjgZuACT20HfV_+9tJ-30HhooJ-6kLu+(H%ZmR()lz=HAuk}Y52l}4iP2%Sr1~6*F<2tu?$%;uAAd&V|Ew<{sIRg0>B)J z?+`s302kfyDUsFzq-cD>&wwhs?Xqw<*SAPSGyz>+7saz#*+wv^;8raW)>HR`SG=#m zjUgV(LyVJS^$RLy2v{jckOOI1}lC1qYibwFfi)7;zC zSE4AJ?ro&T!U(*$k!sO!>}q^JC?DP>h-;v?6oTcNmq1Pc%_V3dBkZ}sPqF!2$HrKn ztEqou;JAg}nT=J10a^Z%Ch7^Gyoja%7==`~srtY;Mqf2m7mV+{pEXm#hOxt&(OiWa zsJg6$suz7yYkC}v;;|tydm!A{!>F_crYg@{r6pDaEo-gfqSnCHoHr@9Bj8) z^nS8RAk{{tfq);^M%@NY{kk@Qt1wA?q?M{3#Srn9O25*mR;o9kVR0+G`WULz8pyhs zTC`So0%yP98jGojuC`H&V&)i#Xa(*OT?R9@cQJTyBEn4e?P|OEL#QW!2}})g0^k%d z;|^@1LOOAWf`PENNn2ITFlO2#GTlESx$V@4AXYoIS5M(GyFJFRgtoO;)y;XKpu92Y zZ0-jLP(s(*gP@&9@yV(x_CuRwmBf&6M{CuTrY5V#SjStFRTHChG7vQ0Kf&UWqUSCB z{-0ZFGBoCmJE$n<1XkqG;+anD=UL7c?CrrA!p=>c0K_IO&HF;^4B%^6Q?~G~1ez%S zNC!Kp$Hd$%)aFj$C0rlBQ$5VrukXZW!}ZXes!k=2kY{0#aF8na4yz7W3<&e8c2qeS znZq4bed{yRgE%&dO^3_K1!jdI^JG#Fc^Cvh-2l*HOt<3Qt@KIR4$Ez2;{ssS^)B@+ zC~F+7V;HdL&g!J@!{eg6_%WX!8&KoUSl9T#EgU)903Z zq(%3rMl`*PdcOJM|$b6r(zrKzAuz@@NQ8Pn?4Hdv_?-%Y($;TQ)rLnu_|)i1^%I^9!s z3Of?Yz?j)?hdYE`?*SCP@P5_nw)|oMnq^u_6~#mY7!h++um^#oP|$}H0m*swG3=$= zdVxI~K~MGq`X5a5dZ`Za=>ghj*P45HBaF55_gL9{zlPgB;|=bO_lCx(*8^&QSk?@z z9ysC6)T`(A2h|@q&>PE2Br_&;VC9uRotk<*q{h$-_o=FFW(6d`37owal6c2p>u-lu zvHeLLW;O}Lj86#6r@%3Evc@hzFFSE`n|ef?K`8#^sv_07Uwz;_h~1@V|3iR}h1B|C zHJQB-n5yK{6B)Q_W&992`mjn0wE4BMhWEWkl{7$e4SYJ`Z0X87L*HC7xYHKvML;%g3p5#RO?Yyi)&`u zHG4g(>Uvi_rqT^;m&cw^+3?zpds01R)2v7G=;J3?OEi_pg*5g zD~v1N<cv(fggoV0*TEC>?jW6kum(+7n@URk0yurZ=fra=X z)$mq$S#>pN_e-jK#a$3BG7SREqxXmtv`Di)_=KtSKp&NGH@v)Mh`A@6XVI;_5?HBm z!0&R6LE)e~Fzv?*=N)$1pW}DN&gX>D^lcyY2tCnPEfz%(0`yZ&14=n~aOo!M+z$Zs zI1T|+4J)jJ+b@=`z=ky%I1tce4R!AiA;WLi!DEBg5#OSJaSb zxI7?wms>m+>j|=*RWV#p&PTqg9_vyBt37s=1#KhHGXJ4Z`_L;`T)V*&yV!^ZQ~-?q zfc(!zBw!#|LWQV>K!8yA0h;4ASG=m}&fi`IKAuEFUQ-ucc4%Hod0oYcDMM-c>#9}k z9zNXJ%Cv0_wPM{I$4)Xs|W>&?OcXu2sF)-UN?89o|tb0h8W;M}S|Ux$mi~vC}jg1D;FMYXFa= z@@Pkjsw0l&(bW|2Vc73=Q`M7Exv(R{S40X2CLho{$pV);HC0s+xs&PZRCPC~pFdN9 z;iGov;*GVih+{Gz)lXD?uyGV2X5he>>*|Ts|DG@{G;) z_aC7#Bh;H=J{v#G7@PQok5nDQd^U9@+NO@48VQ&&nud&o!jIuN8@kYuPt+FQs|1|Y z(Q8ca){*KkleYHgQpjl4J7hEVon`<*)%G2&UQ7XS=RJm0HVYX6z6C~#D%nCxyMTI5R8drGysBNXVJP@Rthd>Tzv?F1e<8;`@Lswu$7y8U_O+Mj2)KTo$mhz?Fs zv0!4ZOaVeIp(>xL%3#H66B03}ccTj@2Ysk*DuD9bU4Ad5*?^ zrg~GoFYxuJ*LeGX!Hi-deK14yh$@V(=c*jh&sp@e^Va+U7jKJv$)_Dx5nehexU2T?nFlIIIiEzXlnZ#-zH489lKK1@mCDG^%RX++=(=3SNK!QyDVtIi({KKoFVFu`A#E`K7 z3v4HqWTDs&$%U8mT*_^E!K*io}wi*C8r>xoPF7d@s`favK4%QeK$RI0+tU2nn|A1hF{RlQ> zFlCQXmA#R3fnvqjy;OgmY8ZZn&BY+m(ra}*y)+Np)e2fSPsN0kK!(6rR#+?O=seZP zbzluJ02sje*uv>_=X})x9Qv^NYBL7E=K}1D?ey^ikoSeOWC6x^JMCVe?!Z%IrdnD7 zI1EIprkcS#4rv0-kyES|f&IG!KLJ^)ugG-UUZP4N&&Gl4d9bB6mQ@_JT9E= zV2!mKgCu}kL9;ks==Ph=*BC*$?VT7C}P(VdgOzS;L&heve?v zDI5m1ToyqSFu*`ikA0*51kLv7Dlkqspl7ve-xcyM-;gWhS4>-114bUE3#-)=;ithvn_p{G4%IY0xkfdM+@diai<7XvLPKl^ z_ZV8eM)mebU8wY}!huH{0a%aIo!_eQ)kbW>y-$7UqwjDMi7)&Do&Q#ia9y**SEPQY z`npcr_a)y!#58y%#jJ&ParjE`M2Z$EXJc_Zk3L$f?h!}xC~vKb70Ytym$e|oLG5Nk zaB9GZ7F7GiLk1jws{|KG-FM z8Oa;iWhNc?UcCVese3m<3w(STy}1$ZaFix(R6QG}t^nPiinH;S1B^m8%M%Y4j2D3p zF$irR3e>PtS#~>`DsBR~P)MCOscJP!pfSVpVu2b~0ah5p0Y8TD(iqR z)xk1d`~;e%R5c&4oao_vRX=hGTlxV?Fx_k5aEEC$F<*7!&4@AJUBtRuv&efgACzY0 zjVvkRpRF}hu|r1Fv;x&CnRtt4EMoV*nz#eC_?3RZ7G=tOAB&4|y$UM;5Kx+H^tE!- zZY64mY9m(eqMkdH6~AT|;vhFPuq5E1XADSS(KTXnF@!*v;$4)rLsfI_-pLyj=lQ6b zcO2FCwj6k0xRq1(2-f^9YsBXDj=muu3qvwhS`1C1O_pMdAWS7S-KjRjUtp_WO=Gh? zIA$4Kf&_Q3lot3|cc>>ta`jW8YBV7|fcQK+@R1xRM$LJ0c*A2h39 z_2$>YO4pu^R<7=wp}+nZO*#s1!Vg$&px2%%1pRY}#uTcKaV+s^ zB;J6-DnwK*HG~spf&#z`fItH^%oE1@dy4;2Z4do~BTiMuYaqA>MIHfsE1+ISG$T9k z2-N7usNjgI6IS3tkYa2H7Bb&KSAT>UwUDBJQlDc*Ed5E4iR!qBjt z3g9)bTP8nX4$$o=bVx*R9R*D^jm{kf`Fw(sj;U_#0Opyr!baN{;Rb2O+&k`AjaIWS zBLRUH@Y71mJ+!xp&w&=pt%$xpralPF`F$H^8M4&YKdVQR{SsK77glci1hHD2S0;$X zqAAmjGAzJ4VS*KSY@~PB&lo$1gAX5vmg-xobONH$k)X>V;sINv{bg7rjwbZd;w&Gd z<+52w1c{joOlmc^iW#A@U%@Wp1_bi2dkX}I_EKeR3G5bV`C{o7>fi_5=EK|vgn#tI z32?h!I(0(56~MwBVHMK@r&Kp@TWS7_s)*Vr(l$DC4y?4QuTv&Xy;~d!rm88V@H8_9(@MdfH8FHjGAM7 zO%u*STRMj#&Z%cYmc!qHrPcskbod-NqYV1uoN5u0drNv0g0~qsa2T6{_alCA#vN5E z!OB@g?Mq<3k+F%ME&)Helm?c7GFn6{kQ$c(&lP6u@NK1Z zlm3j;!lg+16P5&fGy2E@IXuQQM2^qZS9gXAH`sA^XlD^sd#8!5A?{ z0a5T{(?Ldfp+*6KLZtiaMdhyi3$IQ*fnVf>$$MPjo9kRsEy8u4VU7cWGt2|j?-ImS zpVF2~5Ju$F&zHa$eMz-{Q+KB@U4uP`8G;zz020JqKaYVCz=?sp;8_Z%O97T}5xf*k zGZ_+gjRm^?r|KY^&<>8u;~8LL`<+$y&<1tXhl1d34rt#)?CE z^ty6JQOp%p1Izr5E2|K=}o8 z3jtyZKL9!fhpF*30L{7dDlT^%Ka7vTi6~g}R(dh+ui;0#!4?~QdLaAivy-h2X|x3E zJE%<<<~5>Eoz7_5@Q11#@C(bg0{hZ{%~%Zk>GwZWa(QQWD4VX(J%2)h{$)6&JDqXp ze%+s-miN=)Kb3v1JJ;9mX`XRX`~%Kd>T(_8lhu@VUA^rp)x_AO0Ov?!<)m@W2jW(S zdt5Ji9I@c%LD9tmE4p5YwML|2JTpibPTn+0hVx}yCL7KiQ8bbs7tW;+Oh!m&T;yjs zvB<3rJ$f{WOaV^=gB(cM*ptZQy9u+Lj@s0<04KvHW9Lie3uaP#qK!6@g*E^VpV};3joR z7z{h(qHXz(Zq{4|`GP<5_dw?%$_jQKhDme2aJHA`if}TeGq%F5bBy%1()kj78iMt3 z*r4=f%JX+Js%^H6D&LmAJQIdtKSOiEob9PssI&QP2U&v!Z_;@tuqRvTvcZvdF2bQ9 zI%h(F&Ow=cmqABsN)mxU)7yc%8zXuY!@yo zvuVK20QB;61bz37(@ldToKK14tLSLNf4QKe=q1yc1ljyV)7b%+J*KmN*czzOt!ma$ z)~1j4_Kb8o|3B8=1HOu)>mR>&@15BsH)R7M38^VGBtvy7-2(^Mpu#`aJxh7{cOx zi0DPlL&bLL8X>-?vM{lW-l#3UqY=@fJmj=MNjB>&c7;p(7<>M>$TdV zXOZa^_u5HM5DkL7o~ITIHJ{QGM2dEF7Y$7iVZKY08Ve_JP!8H^xw}<07^FQ35Nc)@ z)Aa<=IQ9!xGcg2aqrjAL8CUQYhx(_am>Sm;ZEvA;XF3U;O6!95eYuVf*AI-*MoEgK6LXyLPRH(?8-S`w0jA;^Cdjeq| z2TI@vVuFcy5h!4e)EBKhWCx4M4kUK zg23n!1VNFDYImX8!tO`()izofCE69`HWW|TX`lgC4 zuF8WP9CR>E3`WFik0xR;HAoY^p*#Ff8orxF^V39%z5r~623oYZiOAGeIHxod9ReyJ z_uirFO+~u*6P=rh*TLtkZYJ*2Cm@@Z2F35f76J!=Hpk%=>#2QnF&RJKH^(l0r={qn zZTW__wiL57Sc~A*aK!VuB4BS6osy=lI~4c1R%Rwo$x6&LgA+6HA56IQ1Q-CUV+ba8 zGknllZWJCx2PfTyE;<}P?CI)Nu*4%f(4BZ`2rF99TlHN_VSyNx>#amgV~;uoZ^EYB ziDqhfLxOD(ovkYvSnypJdW>#TuhwD!(CvcO;vpCk^)}*FT)opqO!ojK92PJugN#{J zzpaR=${+!@zNdTJ3imB7-)k%4*>$q5xTEehyCQo~H!?~^3QPm%09bp2Q-^US)Rlql zL}$9uUc5nJwSZ^VcMwss_$U;nOX+9_(H8l5-5tfWpub#DJ3=p}ti;BPC8;SD>;~{yugbn+2anXsZe~(Cl;(8RSLdW+sFl9AWZp zx@gjfpW^s6aH{|r<=$Xk{|8KFvbhvN+nz9aJi;zbbnZ@3D>@6YD9RR5p`~JMZ02$E ziS(!`a&AFFv7JP`ws|?-(MdE8+_xN`wLI&sVPg`&V?D*ja?YGrB#|@^ry4?Ct_qx>TNSdsg@d?-LzECt;jO{JP#~fsdjfYIqTHUM zwsVQy?xfOV&KSD0hxnLQ^c0cqt#BS^rpW$6phI)op)}+g_T1pv+4?nI>?!UE-?l_~ zJut`MNwb%|l(`OiqL&DZ$^J^+#W2mR#TJrhA9OQo71~UsNxeiO44P|tiC8(6^(g!4 z=UyT{C>NGp&~%__XdGa4ta+#CLv?$LSo>J&(px-*pZUE-2YT{O(1lEzai@4+u&Q)B zMLi|rGICg7QG&EtaA;u`E}yAG83t{U3CxFTaa6T9=8qP?@kTi4@U%GYEsRD1lZnIt zD8M@h>S=G`A_`zD71c4O@GbU+SB0kM)QfXKw2fo@;;Z=duPE?rx|oyJ(2#HBI+@mN z;>n*m7i;?q`P?P=sEn?6iJmbt4zdxORqvPKam55u+0xFT8j4|hs*iXPI_q71L^HVL z_3I<*Qt4gd;c8VO!An^g3wVZ%@Pe5-1ljOU_7%?pitgzLCaw3~A{ln3H|`cKWcim^ zlijrJZqW#A^)GjeB+BV0l0#rqV1Gi^@U!|K>OUM7wWFVilZ6H9iNE@(CldIHh`xB? zA?n%}%;+H+(^s%}!=k>TzP3*%zk7tc79xny7XSeaiE#``Tq^2#k9fe|dZxdI$pHh+ z$I~f|8+0O_d{lgcQR1f<4=@ifl`!uC11PK=z$h@3@3>cZ1zRPpLVxrR#oQ+r!!>y4 zed3D!7VLEW0kM=Gcu2HsffcsHr-N78Za(Vyea`b+3`;%I@4OoLTaqXxr%jB$QCZL!xfO3X#NkKd7qkfu!JC zh8@Yw+zz(w!U%z6X8qfV+9{lAOm?<;{s&H!-6UmY1~cqtzU|3iMy2gU${n1sjQK&l z#BKLH%kS})ai8B}uY3z~^YKk@9<&x36+S51|38c{ta60MT~$U%J02G8{g;knEg6IT z2578X$eJ}lK^aOmG9!;7BDywEhet%XtE{5wL>ll2=h`J|-d%cJ}&X;!b4H z-td^15Vq3S$~lBEbcl$IwhdG{XmPHiCS^P>65Z#{fq>dMLD@`JzXEWxGBY!Gx}c!u zy>5`JzvvP@dYZzB7{<}57D7~;`XH)-5MS&s>OfmKp+DrTX_VhzJS6wRyA}De^Z~%j z-##u%F}QJ0i09eKS?PD3K$`acqEGbEbzXEaRh2yGqPT?71zj2->W55LQXXMM#vzX-_l=Up;A5n&mV+Vpy8|hDhFN?bMw%YCX zx4|m`gq8i&UhH;!HroQ`@|V&-b35i3e+AJKF9@^N?Qbo6LG-0*gG4|3W;!`Y+~K3+x{4;X<)Lq&?*d>Z9!8ZcD!O)O_}j5i^u8H0w)KT>;u_`B%CS4C3_dmW0FL$8X*D1Ep{)^Y-9-K!#o#ts)_=!N09_wy@a zj5gj=-RxEIIJJ3A9MVR5YFfSy33N40eqA&H3%U7qtokgv{JQ8A6c0g!-M3gCf#QhT z{|EQh(2M^O--Ek1-vDhNM;UL3r^0Wj^-E;X_8bAQ6KP;ETmQTvK8e_aW#Gg%3_ITY z<<|X$ZvsP{pxtkZ{;rbKmanR2{z&cL0yWB`C*BgR8sKap#3iz*53ri3k|uaAK4RGb zld=;}nt{e2MWt_vZg%@t@_k1vq=!Zbe}7n}IQ|RiO#pMlMu;ZCtl(1~;jCp=)ip+l zZsETvj}8{{IU!>8hun1b9Z};pPc9t+N$d{_%>YFG;m68LU{ZVzf+h#j!EMOe=}Z-% z2R4^-t>U#A+%~8ydMyK*zdvbphN#!-PsG0cKfNR(Q?vn8Vap(=Xr>U_I&i>zk&`J} zC~h)$N(TVL$xPt~#_X?55DU2T9EPfA@JP{C?8I`dqJoj26d%&2ks=cqt^2zG!5#EI ze(06=L~zj@dJ@kk+jvatI}H^h^E?xeN^}XxVfdY_XAPC z#%61aTKip@Tq$Uj==?AJ?NIWgmp08%B~O1RA)G+@IDF`F18{o`8g85MDTd4F0oo;; z5FsYV9K{(KrrSKXhxZBRam$2{lbLE3va8cLnl)N1gfiY3E+gox00@YD7MIaOK$&mtmrO(Vj1HE z)y{@GGB{;L<7;~UhyJ6vMf5s1LXpAIAQcgA?XfJ)4E$Zn9Sai*_(}YZs zsuzw2=uh-7nllcN=i1H*MzK|pXNC5E1Zf+tW`oCzBz84}y=P)QIH@{R#yj3R%wbn< z9oE-dTSuqAbFg=gQ^yHlSC7*Z6QK0DI|o#`7u`P{NN{3~sO2pF4EgB`azr2M@UfVI zAf*EzgK8Ae&QC=x3Z4iAw2GQf6a(ZrB!EEh>x_xwo11}V=p=Dpvkm)r*=^i&l`spU z7WO;)d>!D#El{pxBKJFIMN)J_!BNrQlR$Jru72a)`0O5JT~UK6;;5EaKn980T5bU) zlW4Ey7tkOQ54c9JU>5-QHxTi3W-!tEvJeOTG7|^`c%<5=&Oo|0DA#P1N=2jEUGsi0y>a0@D>IL zXaY%OFk=!03}2#!#H zE>}b)=P=TMd@zzdtWXRY)Cn0pxHKI#3HXIBk`qy8ZKs8~B1W6EownzS6j07(P{F!+9-<~V|Og!EeG|ge63k>^a9#b&(Z)K)O*-sQC_>Vn)Z%!*j%Q{r-0^7=4=;jArG&=WI#d14^|G{L*zElm$ONC_=japTtv` z$}EV1Fm^Ves^Dhii7+7F#5{=obLoXV@o??+e(_p>9WV zE-gygoXRYx@q=WQmsKudUQ@L$)jM+%{~I^XW+)F$t0~dvA~EdLq7s|g(JWv#QypET zZX(OV=i**1?|o`GSJY)kF?7J2t%6s9S?RgJVdXStF6_6z(5kt>ZXeUZxuQq(Rp50? z_$*T;524!ms}iC*%@b|A{6A0Gbl=J;qxJK|jei51U-QM_TkiFr4~^wSn)w9`@Hz9v zeYZ5=7<)AE{Xz`;w|jVa=xy)$Ph>2sBE((e=v=<2L;b%L?Qd!8=|t(5kYc_ddx82f z3!vl7qc;m6)$OO%1>))OId;vSAw1^^I1+5y+X|rP(vXGXwa~eNvlMy(G@$4OOp0BA zpp;DL6OAvR{{yPvKOh#^HaSb^KMO>E1iJpXKqN4gULYO}9_!IE8i3_fpm(@fuuRuPhSNgMVX35&#{s$0~lmqM&S=4zGd zgPJZDO(AJNyBM<5=%uu3F(i>J+P4_Q6Ja<@pq=}#km66dOR-MNmf6ncntZZ%?(W@=d`M_7&6y zqn3z)MzKnjiya2p0%H3zy0%0_QN(gg=T_>t9GbFQ-x^y4`K6Hh6roWe4KEV+q0!bN z^k4`5Q3QSpIXRS{CRRzgCg8;o1we+3In=orI`c9bT&&cTJO?+8F^UpaRLoD+dmpS2 zWw+_gxRuaR9HlQ-;=Ma)_e#-D%f3p+DwtGr$58qz5f(Pj2}ak>G+B9%DAo>)kh@6gm=cQDLN&*J0jXXN){f&Dhphp;%%-9>BArTBi}>h0a6bcJn<`$J zJ>D2O0ZakFNI!F9wWzE8ZBV^6u=Fgp)6VuXgyyUj(HV!{D+M9P=_?!>2-;e4iPJ^x z=1IQv;k#_!1KHLfZ7>b~gKw~v7HPAtqRhv;LNY{va~5vELodh@>~i>0@+A+=oWhs< zHRe&6W>CjtY$blO z!{|QZYY{CoA&x*|nvJr3Ev@?+1mPH+|60@pFihV7H;0jw+FJJT`H^Q4mNII!LEKeq z9Loo;`Za{&=%1d zyjT2IQJiptX@|wa19%Y9umeq$$jSWZ!R@&Ek*;qQ&)m{7vtVPDmYHAT>PH&&tvC)O zJ9Hb!@iv?}Y?0%9Rq~MI+;xDiiL`ectn+2m;yb85_R@pji90PnVLK#%g6~8VEprT& ze+Okv9(C9*UV?si;dZ6W+qhjk z`92l)q7SVBVg7KXo|#OJUCL;Zv^F$}OrQTE^hMi)+?3pm-E7r6=V zLRjc7dzvVz!fL`8W%L;|VJQ_DB^zbWfzj|+=No(!RQVo}=I^nNvLj>}MgJfQ;KW?M zSA=UDedwjV5DUi9^&iCj^b^YT;$9HfDiv6+A$vtcBn$cPdL*Jsvgf45*$OEFuT{(w5>w(FhgfdSyFg&CPz7fgzT4D2b6_-j) zx1TRlDzN$J!??tGUSvNg(zWa(bjLx9FXr9O>M4tIb71A$2f=LNOwEH3ug|>N6OP`p)ZDU?Up9&T22#$$)F{c{b9zOy(2~EF9`A??VNZlTdH6ws5Ma z0T5N9h8#f_(B|`Ps1Qxg>yE@EPL&i7(5MuT|B$yru-lOQiP zxn8#-Cp@9D z98iMiCQ<5f=(nzJqDPLy4mL?;x-D9H9LzkFzq@}InNeUrd2d4Z%=-_eBjFr z8gc?+zziIOtZK_>`Uwc97wPZ`x@5f*fC!Zw?Etf5M2Nh+;X9jALaFB=G_E-(qC<-hY_gf-40F#F^;`7~#_Fq^ZRU2EWB3h6k8Pom z=R{43g69duL9_^7TfNC-or*62 zbg$Bbe~8rHuqA;xRzAl6S9JiMg8#9{0v!?fnB-;xxrx6e%^7H4QjR<$x;Z zlohn~qWBQG81DO1qy>*u`V{~>Kh}cjzR4}h& zB36cp2Ve#BLj5npE$}LpT^9GlrML0lqA?x*8&jl=Wmt2(8jzUfLplFIPln&Zf51yV z^Up0!-uE=YFPf~bkzsK{714hBt3;q`MpW4W5 zmO^SE`c7u1fDi2GtMV9%;6N&+$w37+ySd?L2_HxaaT6Ld$TFWIV8x73Xwr=r1N}0> zhFuBs%{Ogk?!uzwe_=n@;ix^+rm&7?ZPeuf%%`gvRjSG5Brc1FDLm1TCLz!wH4+@>QJo>HuHW^j@9es}S$iyUVLy zP2np({ZqYVAzv}QS65s3D#?3wfv=i+uRhm{a9J1Be$G``Z8BX{b`=EWPlAs5acuDc z*F+aK1~5}VkzF0*ku7ly5OGsqC z)FZ|RDV+pla;5#4phVly$&+R6hs)NXzaF-9bO}>^PRWSHgOKcDOAm7sCa1s(M^X0h zoZcqPmR|HW0jG4?YT_dIHIZ%&IVn2x8`dQ=*&MA(%&_2flbC6nNaO9YF3{jIyUfxq zZlSiCd?&(6|E`>n*(Et&rGJO7D$>U9)?@={+OBD`MXgDeh2K1v1#nC@SHBNzHI7Uv zLqI7Wb09jK>@;+!?9=U{pR&rTt*@ms-~meqYBoFDYM}t&#G^OihKqiTWmxQR^kVol zF8aBqIAv(9-!Vf>_<;Q&m2%k!Noy84WGa+a^+1a{^qj#M04v09VPT4S^ z967Ae(}O2fPycbsl88SsG5k6pMZAfX0N{NgnY1Kkj0y^63J$k#p+fWB#n^x zbQJ}XY3H@3LsIs`QS^;<%jm?cZ zwhr(TsAYhW#BII>XF;>3+eTxD%DQQo1#q1`kO7p%CCoaOfgK3c&*=dEsaO;Q`1-kM zqrZH+$5$4uwX>qY=KBA;7sr)h&)bW$H}?V(DZS$>$NFEu!Nuq`I(#93hWSbgC8h?d z1|`i>w7~RK8X#W|+>Q*%EnWMJTB)E1#|g%I3cBD#jE&ppF+r0zDoi zhf`s&9D-zL=)ei(G|L3g^|nQ&a=|{kLL+r`eF0pIBm_$)u=%Y8G-&VRjKx+T=muycHV@!o-b9e zyY)+aAHuiyOMD6SIU`H>zhaoftzlNU8liDB9wnvMlx^cwXIt?bxDs3$N6p|+P2Qg8 z6x*>YNZu1&t~5jH_yZfyj2mJSYwh6#hL~d0koVe0(-cFll$$whaw6RyEaStEt1_Q@ zB|vQ9b5_O`xyX9(lXTOkHDx3`-B#eIQ6{qVF#ity7_biZC&DL#;atX$p&6Fb8aA?m zlWAlRk)v=r>X;DOnVmX!gdmv_B0MjI$Z6yoF5}^h(=c4#?U&6O7dCWmLfnzevcF0F z<79Jc7b?4u43+I@Q!Uw%Muy5TkA@(XwuZ{aG%8FEHKs9Aw&L62DbU(MZ->j)&aLpD zqea<>(^jeV;^}<2oCyV*zT*e)MvLeAV$L)Jp9i#4sZwWIYVijF4$GI#NE4 z&Sm(@_vyt*8A(q@$*#s9$hU*RD?tV*cNDdYk`YjcJP;-O-F)){b<MH95m`30m?lmY3_I#OLLz%%# zCDvjxCumnM8InA~iz~2En9c%H(G|$-mB&XZ5f-%+SXa&pJ6H~7@mGHQHm5}^@pZUL zY^Wx1o>Od*N#6R$3?4>WrxTOKqHv9I6GP}^3^G$9-D*>h5`OWtZF{m3s(Hg4s`@aVD1_^5s{0@Pbp zZB!#SiA8i2+kTZbo>Xrm82Fkz{mI2s>c2ACLw`L78lf8!F+x_KOhA81mlWZl62RGFIc zs)cj`i4Ko0*xuM~GYa4cz;KAA2LQ242GqnxY0;cC+1p;*3Kis|Czyg1PW6V{2=Z+z z6Xa1;0JCmwE>ofH|Y7w-t0y)(dxfTCupLUU(c8H@#4q zOW8)j5r{gtvXt4oEo?@Enrl3|b)`>6*tJXOa=#=WVlih)&hP0Cr z`dH3MXbhqgt>vJM=Pk7sBw-d~fnR)7xGV2@z>UrL!Oo&=A^-tDt(YHB4#P}Waa=Wk zgLfP|SMm+8#4q6V5>}r9&ExE9>{AAJB{9ElL7dlpj`dPLZc6SU{pr;P)4Q0Hjt5;b=ehuugE&Hu&o?H6WhuAYwr9O#HF@*3O~wWZ=hY9vKuz%@b+@h zoeB;0t-w-Vv}FAv=z>F*szh5bhRj0kI&4)hxDncYTVIlH3H(~k+$>W^W#KX)|VF%f`@t1l9nZ>>xiI$Zzlj?|9 z(|5HD?;l5j9c6sR@4*l(d<;Im4TOnLK0_)8B(>&wgnEOCLKea$6?x#^-`J+207l_%5Y&N3+ynN@ibky(}LfceBA^RSJxe)4$yyU6F^ zEA(0y*+whbOiQ}Rp3%j;a3JG0vk)at=)nu92zl{ls@+u%2>bygS2(fWs!@@JIT8L`v;a;540X>$H|oTD$9jTZf-kb zNpN}_Lce)^oW?wV=K3uW%CngHbgdgiJcLVkmyv-uX9Ug*ymG*ef(p?0(2LMs^PaMd zQm#0dTG-fkygOLo>`mBIKVzRNpn-2(j~+5HEDyCRO-d_h6S!Qw8O=`9=pJ%h@J)}* z3ZkApWc~Pmc}4;bIg-SV$$qIPAEx}&(CP1vzmw{w7 zGx9LA!Zyr-HK+m2Km}0#BIO=AN!>vWL_K2xBGNnrgq5{RXvdxMzM%1Z008sdaI3T< z^rUHTSs!-KM|wjalux<6<>;ElLA+#e^TcGrvW=rwu29>%us8?kk-Mac90Z@<1u3MM zitmzjwSNry=`P6!clq}L@hqcyeE^FS=+!>*OM0iTY#9pOtU`tgCo+eF6~yFh?F$je z-BX;R`(^x^n?*sRInkL^TITCr-eKMXV-3N>t zb-%2Miq!jMBYO{e{C?TJ$2jKIGqLWa%n@IP->)h_Qnw1ny@hgb0jGFbx8PS}6<}R8 zO6lzV@B zSjs=nLEaMBm69dEt)n#?efprhGXnk{SWGm?u}WB0C*O5FMT<+3brWv$Pdo@r_7fc} zWxxH0WE;{Rk}2?@Nqb1PjJmlaR))kX9f?#ODZ~kiR!2M~bY%5-5)aFMq1juoUPwd% ze+8?Ad)Z&o;D@op;YaYWjDxeu;)emK;!&AJryrH!ayKXwguar`WQc<)#o9-q7Z}5L zzN@_BV>5PexD?XpJ}jf5Oh5UsjDpbP^N73;{ok1>8_+v*Wf;wRL^jZtBHy4XlbdZg z1uU@4OFUi3 zU8|5ElmC~;${v$%K3es_N07N{2R`!k2YkSzb(}EvsLdShIq;DWgyd`Q0g$*h(NNWS z4~e9OkIPX1%_~ab!ma^@0b}ocTqbCL`cT6|aAb^pLJkx-_tEv3MaBLqn*IckOX6k- z%TTT=3Jmvc91(-9VhJ5Kvt*Pd0)S7WgZ&{E9Y+PE7&$<8Nj_55UBh9Fvy6mh{-_ex zag^c5%IF2eNC5+62S^F;JqainM=uSKch~w_S~`(x?P)J=QRx5~V#x`hZdCgzh_Hq9 z@KcgKy?%U3rqJRiWn^#YtQ4fRvCZIofh9Fqmv%1D+*t!W+#Y6JbA7QvYJj1ScW@S( z%{7>ZYoTr6UvOq6_%+tC|3Y~rIi8kHYvCLNWNzeb-;5>prGP#d+c? zD|@<*b5KPk7LA7E%%Xw7=cDP@fk1BytgFQwejY-94+J-2RRxVv@hSidW=OG@e4mr` zeM$q|cpD(0)pPR0z+$*H-1NP`J9}boc z`&Hj`TGdTvu5#eh7|=~S5mu|pooCkawDImf(qEJ#Dsp0)Zr~5FDar?d4?R{oGOPkD zl+YO}1Dr=Nwd|`}I`;0xBed~F*{FUo@(L>s7Oi*ku`vJ%C5BEtjEl?oqol?u56N1T zG6X8z@2Tey*^@Ob2JJwG z#UQ#gOx_)|gHMmBsNF$-4U_l8?W|V0Gf+k9yPbRrg+qxywCQEpOO{;)jI5yWS7bs! zaZnsDIeQqI2dT#^UN$U+=oRd-)l~9|43SG$g0QZjkWkUst6wd|d_F)$?9R_tF_|{reYk8C;k6h$DlEpz zSsW>W>6ib=C*U-H#~bn?#Ik<+2JpZx`r{4od7n|8H|1+Mv-*=aWlG!2oE#Udyqk~3 z#@VfWnWN!Q4>)LpM+Tn_qz;r{1j6M;BASmN-7?iTihQZxf8-s$`#e3esY|S1x3}aR zf1F&1{$n&6b<4cU zQLU~Dlye%71#yhgH+;a_2te~auK)6{+ zwMWRL%IH6Jd?NK7A;ZEm=d#Vc68%6Y4#fugoG}96dz>bZkb`ggU`3u(WvC6}SWoN0 z4jJ-&m|iwy$h&U)VDUC~;kwNi`(y%D?V%Sk(PPD$wUB^XjF)zelnu4(PP#Et)~l)X z5G*@em=PxA7WVuXL|O7q5SgP{Sl$by zzc1VQunU00?d(In-j^Rx!Ur;;$%Ozd-K_!<0XmIr!1mw}@J00e0vn^<0C|WR{Kv=G z2%DBhF(1eVDVP5Jxl6Y^x0`n3Tj8huyl)4tG#bV+STQ?FfuqpJOiCLin{tx4F*2s+ z%`;uz(o6K{V3Tle38Yf#Y=rmDv=$3n{#z5-$jLOHzJ(M+8_6d2~ z8^D332P+PeSayase<)jommPpw!NVDuxd(BP2?tx~7(&O&A@u!N+1O=eF=j*70t(26 zJU5Y2vSn0tH|YDbfiCt_VYZwealoTIgd~HNVeZd#@+qTn4(cBc()#8&h$Y);#yHtO zb)kh`v81C^Sd_owsd%F@W1>h5uuy@ZKL}9Pp(&Bssyv z{RC=rj+P?~6a(BxG6v2)e-Uhfb0}`AY}klrji2JsmI7#z%mRoRn|EP$>T!bCJYOql zK`D{Gpf{(=ju}=?dQB$UOZ*kRqEJkuN;WlS^Q4D@}1?u|L1q!U(roVk5<61^a#(`8Aw$=O@{NrDq1f(8T;7R4z&FMW+4;ZS456)`!uFam1IEiz)=a3SfhBkm z3qZ=F6L3EgHcNGB>|&~e+rQHK8S)-P2!(zsW8FX%fJ1-F8s%da{tQ%6N1-O=X1Ry% z`&1@pC|hhHR8gvsILLU9RZ<9IiMMmIEITL5vYD$r4HC8IyjE0JR;g#Ms)Eba*OC!{ zb}Q6@N(3@MBRHtgq7h7N6!o*5@$G_AM8En5U%9|zMP`N}tq4-?5k1WoWEu!Z$4L0*Qa3k291Too) zZ)RjXM$)icnND)HeC1z(A!oKU|Me!oD1@%gmPy)2IE{OOOrSb>u;S17Om;&^!o<&H z-w*;8u(oY)rGmnD57MR2h3e3cnx=+Kd0Bpgb8$!}FkghATv#bX%0$`X)13v^EcX z_9{A)2c!8uYWKNpUE>HkWf4-$uRl&v-bAB52YvsC3O)znSy6QSbErF`izj=PMZkaxE{{udrHZhqBeuJti9 zFX4wF5pNugva#JewbWw)Sp18$Z~=tik@V{V*_K)q%ARy?k?czDg%CVSX~aT+`q%W$ zLfM3#v#MHf`SLwx0iSPW?Um6Ddo)}f<+ z!W)>01W-;z8M_W*88uiUTeaN$bLG7flFVhoW9fWn?V~eb71qe=Eg;N6ewmzmn|( zvRIYLhGw_j)r|%?_3)HRcT9!bE$<^6yUXJ}b~f1K)fLV<^!8F{&}PttHQ@61(#oZ> zn|3Xbx-66F+I8!<*$vB)0=fir5;+fH3<5#c2Dt59LQ16p!!TGgPf4@p#R+P)RxE=r z!-K`LgUDy&8I4&62eZuiB@lScsdKzdGGI?>y&^0&%)|nz>wdrib~@ zmLl0IU?aG5%U6zFqDB-UYu+3|jf!Mq^sCKQ z%7*pFI9+$%uH6R}?I0K;FHW14SdB5XaHXv8cbMnR96Siec>c6fCWV04QGx^fby-kh z_Iocyt%B;}GkScL>|y;R(#};fBC&uyw2@V;`u1PRwaf=%vu;xk-Be>Wkn1dJzFOu~ zg2AEHkSi*HqTU+WH0VUIF(w!?2+M~^3rEkbfo%M#7jE*`z`_!`7F&8R9bW@2{FYao z!GdW!WT3UOp}&ej#x7#f$ z?tgk(qt@%dolm0&*TE(BV_L9I)_<_TpRq-{(#P73vCMq3l?~cqC4*QhmTAhxr!U+O zl$RL>J6Nq?e-)OyT>#OWfO7I`H9zZo!-bR@gGEX;e z*m@)U%-W{epdXUbzmX}%NpzEuhsQa09F6z}R5c)uQMjV2y*JB5{RvEfSv;36Zj|me zJLkfnrkT2Ryv{ycneEK%e6Saba8jUIV%^3O-VEG8^4Q`mJKJ2{Y4#yl%*(%#fqE_? zkBkexmM3>qX<1FGjK0yKH12=O`JfJh_k=;~&$Wgk)87TF8_(l2h2i9wY#2sQ#R z66d$bP%X#b>RZxQ`9S5#6+l5a#!Bhxp>$B1MyNuq9idW{v%U+11+r4~4QrD#hSqMC zDcaIc=*(6iorx6utqiaGKLFt7aag`Qy6anLw0@#@zm*LVSP9DerV4$lplb;pT{&g$ z0AMcM1`u7lL$;?~_*7&rY+hJl4iaOo0V~^TFe`505lzl*@(Uc4;}y0oMQIQImaCXj#hgEdYRw;k;CY@86OzVO0!`E2wjxAb_brn)?pG3(jwGO0SE zN!S4`xl=wE zyc9|!W0`*h6wsEePT%gtOOKJh3w|dbQ?p%|hjBD~muz@j0ujfH!!pIk_69Zq9DmEaXeYj-Y4Y^2$&K zemzYmcgv^-(BrT=Zxdo)EEOMy!%r7j(H*SogQnSSb9JP&@3GG6%oJ=k5In5&^WV$X zu*l5$UZ&l%laDjT$K8XBy*MI6Q5AM_W&{Y_#ft-1X4hRdEuH@9(WWU;@7ZpVQdx2YiN3 zHn#h;wWIrPds{p6KWb&#DCF+8&x89|Chgk;Dt=|Je1kH7Ky1bxKVTEB`$2|-=9m2d z?der&vRBsA%Kd1-4w%8-+$&u^pB^c(nQ;*L#_g3|wbB6k0Zm$W$0Am)I4rS>dO9s! zfpoe`SIqE%GL$zU3g|J;SJ_Jo_5nq$qTTyszra!t+k?I2Z$QmZCQ}1{gu0qBcGbFX zs@KK+2sO;!zhw~p6r}429HSj&vU4abcU7Lmx+t3f4gOK4HTw}JE3f-#$$kJL>lwRL z8-xdJJ!4?7-GwKq*+E$=^b#hNeb{|Crhq{UP<2V8q5EaD4@L>Dtpw+Lb%vinv;8u} z$jir^F~5_K0^d3dNt^z&A8zxWb_p&l(%WX?rzPHlX>IZLXRvpRt(rJk`miYbWpcnp zoHB&7EVIAHt5%V50AP5IdLEFGcg|;H1iO5?T3UjJ<&p?N1HRAxR=PKI?OT?W?8X`n z|AEECdc^H8ZQWdV0A5(D1lP&M2V|clg|`7qKyd%UJ7jwV^MMEDi*=U!gWCa|SELuP zL9_4m=Tqve_4L<4=;d|q^VdZf|hh;|%Nma`ZLk=uqbw(UT{{)z`#0kI`^RdA+ za+Ih^=7BEWqQbE^3SpcE%;eQR!#UB-bmvq z_$X8<$SBTNnd-`q1z3+cs%$T?rosbexB$qS2W1FSa` z9arW3fH5F$i<`k>P{}XwK0^8xRyCaI_g(qt>gItqg8)HKzgbdMz3YK%_yx!541{P5TX6E;zSf zn<4}O9keBB4O`-HCdT8bgNzRtld3?3G|aVzj{tnOQ_*o4mZmPna$@CBu9XSE^WsjE zUBjTX7_a6f9%nIIFzM;F~2~lO9Cdf+T@#kk*3*AH? zDD;%97j%i0Ev>xlU=v!rQbtZWC4J)%PEzGYi;7KSaX9Q9e%CqdtU2f8phl?`qp5J( z!4%?o@Qub2PEg9)EMppb+Pmq<1(|xsSg&H!)esN6ZJ-kskjix%;06TYHJ#JPi!Afu@}Xsy`iM?Vl5;;a|I$uAi1+F>~#ov0llR3pX8CPm5Mr zc3^yH4mCX^A46cogflX+<}R4>G5TGB13kpb-itmx1F5az#xr!`3~>4`3Ox(`$b9O5 z7JFzDjXx{hUH(Nojw7HTqQhq5N~`v9o|#R`Mvg6KaNc}W?4XNhWkSLXCUjXm3E2L_ z2bn`TWeSenEU@YgBn)f8+*pYAuFD$no996Tt`nV?kJP482Qbmt$yhu>1hA94^qdKvQA=3qMZhl~mO90D|uDuajV*EN!w{ViKe`5OY%tH*)F z-u)XK$;UwyauIS>yGt@Eh!8Cc_%q;ZNb$&((r62=sTioiK5aTDrO zC2j)4&No~%>ukb{Q3G33K#ti+xtd zLwXWj%y=K*r$Xldq*c@m%tTafeq=})43{4jHQ`M6Cdv4>XB#Wz0T4$Fa;TlB0>lDD zhY1x!w;d|LN3P0L?NShpyDC%UM0l;4{xsn#u=+eYbyddlVLmJpS`!zgBGZuHfM@y_ zNIb1>p)F-!U>r-R9GUuQ(=}N`7UG;LZ^h(mG9(=NDIHLxU*G?9TJvDc5CpIPBpeEc_wrcU?BG{{?H3;4*A!tTw2E z%azQF|;JE#Uyu%pfhou8246?%F$**+p4S5$cqt$Dd+>mwCH$g$< z?d3(3J-uwMCfu}P!nJgjjN6fpTLkr-ii7GJVCOL#=O_2kYiInW4yuM=VI5eFhB?aV z4u$V5IJ6sE1bF?bESdMLYkCrVZzs3#bN1&O=Mi~aMVdp{U z>T7zypc#DAf(5fc-DGA;&B$%~u^Aq2NV8a_A67SR*XQ}$(Y8vTX{M$p`j#-yhKWB( zX%4-fe;)H)xXc9yOS3h-uJ7+wPmN_$9C~7%6W%N4S3JGC1tuMOq`fg)g5u;a*d=Cy z+LA$^u+G)%Na|`ryI?A!noi8uIJ;h(jyUurYVXwR@TEcJPW>hD#eIeDHcAlC0b^Rl zFey{$_k~Z#-mW5@?c#G{)fz0;^)5A$;|a)&RSdit;fP&4hkSkXNNp3$+&+3-;0^4d zitVz7GW~RS;NO_+o9d{cuO0xW0(Jj#_4^BbbUx5U-JZXdIsa8mxvQ!yWTSocc z^yqcJ>J>pF|LxIie5CanPB^FF0_4mNgC~w|GYe;xD0EXnAYij&4`{SNTQFMiil);- z4@WqGlzO8YUKS2pYb(abNAu2>dL*mF@DQ)3F^)Z2l#Eg3LsNlN8LvTK-`!ttKphP| z!0#G!!Oy9bH6#7>FjX1stz7Aa<0-lx0VXX{*XsxHXZRyP5u^O6kFGavKGJY8f%cBb zN}9wUf?kHlahnmj5|tcM!>JTa#B8V&W;!`ob~wsIBrO%K%rG3EeakUmBA zpxI=q$H*K?sgVZ8aWGg93M{Dh65{0#MqQ=Of)|9Te(ey5S~f#@`&2;qRq7s~*8>yr zQh=VSZS<{xnBdX3dq*r4TYF)9fL=dp#&;khfo1`IDyCtOt05+X^DqbM^=WsoUOO3Y zsCzP{|P31GP#5FdQETQWn9(gW%6z3<&7Y{X~M!&luSi6^fZ`gey^d&wV4mYD{Ii0TTM~C0u*no1weP=?Y+U4 z06fi8m%!PmB)V!9l%Pmohv;|fd2Cg}L2z4Z>1h;GOYfvz3!o=)5osmcR3ae1tP7Av zIXg|&f+BZeEj@;=)Y4zqE^npbp?V+A0lG0%?-f*;H4!Q=UgcEk%%{5>EDkO;sAOFZv{19LncrW?pnT8wg}%1EwoS~Co~ zN(RA;b7)MM-W>iV>%#QLK0lP>li}#`Utu^*=v#pEUvOU95w6!n1i&xhdcWYTJZu?j ziNQ4@0X=Q6t;f{ayy2#5S0kQXp3F(#I?@%{eS3=8()t!;0^o>5|i?(TLZ zki`aLjteSgtgBdJ;mq)z3WZXFj!y(cR%Ufvzi2K{^gOO^wo{{h>(o)@)TZh?Moh65xkT2iOz} zrl1v2ILLEBGosX&*G1vWW%PR#JZ(2J$QS^b;ArUB`bFtsxbtkZUN5L>FW`nieWO4g z%jmHfG&w-8#^|ZgdCrZ|V}QLk#^{~l9`6^65nc|Yv{*e_p8F2-yPxij)f?+9Jwg>o zi(}C|kLJe$=jYPSSiR@dAa&R*C46*NBK)>JmWD*EhTFj@X)qjS2nVxPbq$HGmE|d)MKZ{;`G%`5r5So;;`R#VPK=sstoPg? zxFWM66JLimb8Zmh9ZHD@Rk6qEHKQ>k=2JbE&U)x1aE9Uyklv5d;`CbDX&<_m+aGf4 zZj%+rbr{>iAC*Nbe||Ce-2Kd-dbPXzgIpCIP{|9JnMOOvkMn3joZeKo9Ho&kEMAXw zSx0EFT_TZ&&JiIs)K&rEWO;_=q`w0wKVFZ|;Eyx;swzfo{)(evxGP_)4FxlsszD!( ze*$bSmNhVeu>IMGsgWr>rBGDmcCCNYOXDoxj@>Y?Tki&=G|GiUS$ceJ?nHg;V`qA$ zJPy+qbG7=M`b_v2=xU@sHban^u`_v4>YM9B$6Ez9IMyVpeejwAKP&> z^-a|4z_U#KT%qBKdc3cNi8&CaU4owEGYK=po1JnJ^td!OIL2d=+*&UT&k|?3i&!F$ zn*yU9SArKHzN0S_^#r<@sMkd##Lh%Lh3X{fHR^k&AjfmInj)vjum^aJw;5wuA%Jvy zkjrxF>b0LzmDya0bP2FS@!KnB1EQQaQj*PYSR195OtER{{LgI<)HFNn5IL($c(>%O zDe*EsmD6IQg9&s)k|p7)Pd2vW933#6 zC%B(D=vyHJNK|Ux0LEO@4!ns9*AYCylmm)|tG3~I=hfyG90T%Z@5fm2%&>SH;# zQP29iyP0y*L52d$1Lr6xdbKy0ZfkjLyc>9VOa!me%=&sA<9FTaxUa?S71OTz5GtD| z=_xT4YAdFqo-kNmTvq49$~QYnkJ3K$r+$FfdegjNS&IAuEqk|vC(r3crEDO*f!-zz zNIzbICRUHR6_qj0D53`&=m|1Uxi8SW4fIaEbJPKcHdn080SH3hy>22bVl#rX5|+8@ zR^1Ec49+)n>vo{PWc}XOvA|*$c2>}Cp=PdvM(Ciza}2EJRaUq%Q-FDX~zh<<(l>PR}+1av~brNFT1VaA$*eu}sk$C!B;l z#OPi5g@Mj|3R;epPzZ-uIVuBwdFbhdz_BG==!u&xn9ah1r zxv9wUxFUF?V14?4q+@#U zefCYp*;i_=(I4(QPM-!=h7Hgo+gvWTif|uQ^Ac8qH`VsMiQ|+k*#D8vr|KbL7dL$4 zeQ>k4h(m}dDowAgUH+C@rs?$`oeq@3JOJ-k@8Vj}%Dqy)9Y!#ewf|!qzgyeT$}~O7 z$NN?~jH^iR)h*v>X`>cRz>4jq`S)?usLpMth3L=>2mO zy=kq>6RqPoFztIjwu-pbpsAkNhRH934SNJzd&wf}K*Sc}zv=F3xLHDZg1e+Q)9bUL zWmq#k-046R2bsqPc+AnJ#V_! zOxHc-3@{;V6=F8p3&+0Q6mqCU8#rFDHV=ipWSN^)fynvH)?Nw1Y-#EIj{(Ui&}jpe zX74&|YK7>qtrr37#Nhcxwo>T@S6EdRF&^v&ezineJFIf5JPMwQslraL5KRA2^Hiz( zNz(WrDw^3`j|$!vRE@-e#0+f)&K@)89j`Tu3DxE)xEU@ne#PwoE0{Ty)OYn7Oi;}e1{_~oUz>3)L$|sXR7vx8y)IDL#}K-2b5E7siD}1L&;7hEufFxM+aNz z(LVb)ceUO4hwLr&D6BwCOTBKRS_^zz7+?-e>lM41sp+h7+Ay2hU71Au%)OGM~)WGQ|&uDhyynDgtR3`8o>*49Qg=!l1{r0%b4WM)O+f z^&jN8ROTj`^6>7@=2RA*fGmYaI1bBJd4DQ|)BkqgyR)fYYkfBS5jVHiqwU%BTWdY0 z^~XUTWPMv9FmSeo4i%F2*tEdnV@W1c`!;&RTA#9X1cJzvNbO#Zbfq0_0Ynqq=@+|{t& ztNMnWiv%C8kpHi=Z-I~Ddc&R_NoIE@iA^F2L97UIPb8$8Mq=aIR$b~+MX^aX5^|Gl zh~7f0s!FA;I+`j$Xi5--Rg@?tMM+glTUAx+epf}&?|EnT#K_n0`&IwLlXIT;+|PN> zZRX6ZDsoeYgeh1wnRnthV)Vk-{z>>c3BQC-e*{;jX;nqWS?gz*XM4EPp|7c_1^5K% z1p2@qsmHGUm3$;z)J5vh1=*tH;Bw&{TyRs0hIB*iydFkkfvzYXj32CS93B!(X7O=S zP)Ec>V;s6g2>sC-lxarY<`WFSW!61a8GkO$+EVOMXb4ltw$+qIE8+ zDJp;AZ&3e6t~HlJwE~?n`>PFr$Hq#4D$x}Ix>N1Lt3!LeSTN)FO%?n$Pl0(64l<#Z zYr1hNdSWCTaO;3RfMLd%sV?g2B}Y|2GZ#iRLHh&-1w?`mf>hQH0>M89nQN6s^jK?9 z>T3B_^Iwfshn~|ul-C8GM9}wsunTm5$j@t@>|?9ctbiUFlV7*4*g?#>gL@_o)k@Oh z(-d?Elt9mS*W#qRT8*j-K;8BocST05^sWb5MxQxqs0Ta{Cy+3axk9Tw{-im`DzJhC zDXxGI4A5i>QnRuyudC+S+EN`wt!uub#j(0Z7ZToBUUj|V@N35#&^b{d;k}zbO*^rV z-5rDJOkKDp960yQuf+@BEdH=S^Ko;*`jz|keYv(6T&T$jsRx!%Sa0>~hK2SAEVrA~ zs-XIBT}p@FUHtliO)H&)wRlckI=k-jkKY~@Yf?Htt?pX-06B;M(fdrJpQa|3)J%-klqF{m9tqyEYvC*p(M`+&WWw=J*6H<{cZWx4m)s`kM#;uM8FL(_*GkGU1C)Ppqiixa)e5 z7HaYKm5YA;{q%KDDg{?OG&fqifV#XshZkJ&sHq>H_4_Y(4$a##$raBp3rdcEIc3*n zHFcUhs77%==y&`$qpauzr*#4v3TZ#}lj8Eh1*gzIS)ZO#!*0KtvDVSIQI(hKmj(^0 zQ2M>bhXnM)?5cnst5EOT!#?XN#l~tMW-`!;FSP)7{+zs{e9b((#G^J$@x|>&uN+=` z_XD+I)cmS%mY^KbB5T@9@++WRPzDX=%OW!6njqCQF4fNEH(nS-)tjX1y+ZBADQ#bA z?<^#2e{0wBy<4tci_#L`zx>?$J1f3e)|y&DRe<-;RG<2IW!0WSSNkvhYKrg84}YFl zefB}~;$4?ddtn`4{v|uu3j@SlZ-oxNs1=XjJ1{=7M@f* z)u`xKT3BUoDX1={MQ_1+Nwi+X?I-+V*V*3Ervu1$18hMxS~^vo zsIS*8P{U}oamq_aHoL3&QXBnegU;cshmP#m(sb3YRuqklRD+vWxYP1oOY|NcHKCt= z(&&$spyTlSjV^wZIQ^oOF8X>PTp~7H4+^|V)R783*^JGOS_F(-Wjgx-W_v~oEGU$*yr$O!>HFXG6>2(x zf%9&3%j-UBN7d@$jg?qzP#dXq9=$zJKNqd$U0rSQQlP@nvoS=N51LdNa&LC$uJ3ET z#ro=a?B|f^$sNs*>T#`;YiWLlG_|oIM(Bi`Tjy7?puSS)QM(O1mSrOKSe!XlO}xvX z=_I9u2a=?oWS#G=_3QJpX3mrqH#jME1cM6Y?JZ`f0o%pNm4Z^)*tw>!^pqj2Q(|&t z=af!%TT+KaTU4j$#}iX*os&C0ZcmCz>10bvOioEoNqNl1#;?Gs$o}OLo`d(7dOcdO zdK^PB$CPj3G@+<^5+ZgQaF#?B9xIy`bP*(+Hx3w;DZXrKs!7hAMK>dDMFlGnHr z@?nsV1!REpaj-XM!2_iBLAg0e;pu6K?Q?P=8)G!9{>Xn*EuQKZaTM!Js^lq;Pt8kD zhrg*twt2D?i=rdCB(ltjsa01+rk5TZGFDb(yVQW>Qx6 zq@1)dsSXOCmSoS&wF{H%wj2lCO_1609I073f-r~tt9hO~MQZ0&2Tn%7ji6<q!uVG(_zoC=Q?sxth)$rhTI<@|C~2+ z@j7tivc8Z-==}7Dy%k_{IP4kO4vQm;HGe~jEa3jz!p&FbzZdKc0Mb{k<5W3%?hQ_| zGP7Bz3zPDOTUKg3elY6+YTtlKdDe5%U&sQ4GYA|ALM=iG1_2GV&Qv@|@?~+)Nlgku zeaymmIJ5zp0#ssZIzr=GJlG{GMVKbATL20}_7I>MKo`|Asj;3ncgap7Yzx?_H$poH zsD!F7B`sgb%*wP!CPyVFkIBx>&d$Y%kvT3k$DZP_=Q|vevh5vxweCc6i+ut#D8ufs zCEFY}LCCaa*puzKN$lOBl0VgVB577Go(M06+T8PHtA*I1~ja>7$34E~X?*S(z=l-|CPVKDc!)x((bbKf5TDn?% zyTK8EK}`fC&?m4LT%Q1OeFDVw3Csi6ComtJn&|T8Z8d3-EE3?EUPNk6dU5}6y-*My z*Lhk7JNfzhKKWsdhD#}N`gU&(xBoJn-4m6@1;UwnW?>{^_|;9zE~P{l9(Jl1^)23q zu&@)_1A19FIQ3P%tOGa|Q!k4G?^sLL3Dqw|*AhMk-no{Hsso$3ha!9eJhqms3wT^D zSv+{xS~4qm0uauYu8QRoTiIcX8e%zyOb?j-fk)JxZ_=dT#uW%pS^oo|QB=?Cvu8(3;qI0OW}!8^?5jNCJ_y#%>e3_P z(GrUYXNBI1zkgO{lAWf;ZHS0!>jNONW{hcKRHsd$Q4rf~lXAp4K`cB`dJa=`8!vs` z4q)bFsZFaGiy+Kwh|D1p-lXKMtByNh4`wk_q$W|v;gu$c%Pc5VS+Y_r>GsSqj#NveTJc)W8jrxc39*f|?)5qP5NUdBBi3hx6z%l`oqZ}^I~(DBCR$Q$6YQ4$@Z$+tZND^vJqz%#pVZ>_D2&eC8~LRv75>MmnXLOjpmK99 z?o*4m1E&acZ4=Tm$5?9CAynWCt^&E-9I19ow#|_$;|IMOr%1t~#lpJ4Y~RhIjRFW_ zOHa3?LTPadxWIo&|jfnttH_r2F@#n#*@(}J~3KrQg8;`-F zyJb^m9V4|b;NcFz52eNVF4(EfIllwDMaN~7J5a~Hz#Hi})f;Kma{yPTp|DeasNrbz z8YW;q;4MXiER7SK(|}8J59cjmr-E=!6CNoh=hO(qY2q9WP@*GPpHw{4g2JVxO7A7S z6=D`LAsz^{2lTvCEl$aEcj^)}$h{T9x}-^wiCj)ynd+YNaj=^JM_PuRCOW$KsQqZ8 z5u>5cx44-$^j$|h)HcP%FD^k%0Wl;3}r8jm5zx>9KCla=SN3$M3g z2eL3`{ML$H&yo_n#vl}>LNTYRGo1&`biG?UbF-xdZXvu>;iy&`BRKC2-csc(Do<)* z?;fu0X}Eh9+)#iT|8Kxf>n!d*y_P$TK_NPK1-zM#dxD2*rM^D_(_Z5VlBc*coVA%C zT@$xPuqG3wB5_~`wt1p7LEP7YMdeG8;>bugCLhNnV9w5$?zCSNX%?0t(q2(!HL*09 z7VsSV_&jVlp(KJ(0vTm|WfXHxl47DygVW$lsx#z)Uz#AG39{hUQBxWHR=iP17WSeP z7t|MSK?pMv=~5gtkdV3|KLsyJFD4ekJstc*fYeUUo9K8+EqCI2_Z8p`b^eLBQaKy` zlJsi9lup_WDVks?A9QgU&6uG&9t=AT_S~H&f(8IxTs8%E8Z0><3p>TjId#S60AGss zpDYb!%McqAIXTp&&8N7x`hAJLD6?Th`r@`KYHZ9x(H7w1ZoWBS=m4L=;F0;e#1&n3KUYEud zbcoS(e>g;xHs>_N5vPmG*1;YH)Hl1;$PR#0MQ}eGV5f@GbE*fQ!K=^j^W^n!qDg>quTOX?f=7 zG8@F2@ugs?H!GYWb@EMxbQE0qS|T-;9iAbzF5q%+1U12(^C-86c`h8O(4;GLAxr8Y zr{@_iS)VT2DvoB0Y#lR{G9NwqjB zEXj5v>`O3T23`SP1qx_-8N}MZgRg@LaV&nO^rDy@$2QNDS`#TpCs`lCTkw#YmFplGjT;o{3?Mrz$MLp{Q$@^V3uZ~o=T^K~UBZ__(@EKHv7YZp z%^uhLoCR@SPP%%Bs<$hK*)Vgn@^X@p|3cV#_TOcT-;r{Cs}U<*t*zR!t*w=X&6ZO9 z=Yvx!ZDCT0Vk}tNvBk5ccrVH?<(2RryEGf`QdKjLLMi@VkLkFv-njiEF2gYoghZAk znAMvkGL)FAOwXI@_+Wf>#?_-Ti=uRnxZH7{U4uZl=Y>j8AZ=V^)m~jk)uV(9n~i1yTJW)oJ!kK$Eh}` zG~EAp;1Mckdq0+$%= zd5M(WzTOaRQ5OK$){t_(0Xz~sJREa1{7@y-8^TJLNWqcZPdJ8!p1Lr^hv@h>;7zq~ zw-BypBb1IV9)2xWNS(lAfY9f(4$j?n!;R#eZwGIqayFtw3JT%!U}Vgy^Vb!e79+^( zyb^Rd?r%TjG|57KrbOynz~%MOMVsluHvp#<7k3W>C#B;&5+2ZxuXM)v=Bs%Q#QX}Dkb*hZm+?OMhwo$ub!uZ zQ{^Js8b6tk(eeXvy}C?#wx%kUL++}I6U(Hy#yrkAC>`}D&ijB5c`%##b{?jU{EJ*8 zf9GM&<)29R4K4NzHw)haM}gD8Z9o}e7McQ&0nY*1BiQ~=rI9`M5=SEK5D5)koENzw z<(wz70DkyTwGj4VU=bU?TzVpIF|2e!Q}Lh)Wq;(ys=;E#3rt=ib^X`97Q=rt@rjXa z?g}ZTVE#z6Fcb+t3xon%o4>ymZNA=XGIxE=Qzo*Ry&+LXN9HTDyZ@C295bh!N4$+uVUT`XLj1bo&H$UVu{A zKLzjw%?hb4+qOzF{_BRtS|Av07+})|U_WG$8iMnNHJnAPmSV&J8%tk}b9mhSB>D=~ zb6-5-9Ec|;D`4l@DuZ3mSJvWGr>g)yC9Q_N9QYU2qx{ps7x$iNnaOtbasG%zt+uv` z+9<~PF7QZKvOJq>T%2cPExWv03XHyhC}r{lOEnL>v=mw%sJmL8ya{<%oxhdf z6h9BQ8Jy-j&JTdo=squr9T*_R_;Ps*47m7D@~5Ab;!Irr5$oC-pH8a(vDA%Ktd$x` z)q62?oRZk^R!XCUadxxt0zx$fyaCRigq_9%&S^nO!#wAius2dU>$Fbl+l9+%Y|+OP z3f>(4?D^R^x|8O>lZec;qzCcDkbfHfd$D$ljcud0q+k?>|Jgtwz&X_bElTyAY9E}< zTx`tnO&_DJ?>>eQ-9$(q!%kI5JszoRij7j)`1Oyb6(M4ND*I-=G`8SKs+3ZCmEtxk2(j zFJ=#7KP@u8+Tr*hpNHy{b6%%m%~)*AYF9YeM;HytYm{>utttQO0O+EM3TMC) z?8aE)LG;s z)B`L)dmt8g26!H@1LGI5I+u;@+iih;4{#Vb4O|2M20R!02#tVnAR2fA*vv*=Ha;D< z1v#M$6)AiUJYw-Z|CB?T4qD=)&Sy6fo^IM~1^5WO4faYFaK%_qa1!3=cFIwtMT)?A zC-`lm<6Xc>%W07x6Qe$~cqq#4aMNe|i&}gKILQi2e1x4azXW#C%|jx)0e#@ATKp?; zlI=s_Juvr@9U%MHz;~04LdGg#8ibPry-t>^}pl-@dR=u8(nni~qu92z(s&Ux5<<*?$9y zQ4>dj`fwiv6axE}`mpM2##UY@;Q`I1_R>>q`8DH;;5o~*zBUAjg~Q1Lq(i1&pEz@` z8$H|_oYAP7p|h}`1I_~%v^xgWX{{H*F9DZ{E?^q=0W>6nG3~HCuJd7}jcNnU6Z+B*G-U?t(^Me`~|&<7E$LIqL$w*kIAxC8rNz+G*7a1U0x;8Pd?JbIeJEvByYQE%A% zjI_KF$9(`LQ}H)Uiripe%$M}@5J0j(Y(4;LH*r?0Kr{auM z5-rXDI@}mqGVgoFCSLleOf2S}v7xtKBpcZFby5#jde7LZUG-`o^)4)pPVE8C8^hj8 z$4Q-nG_}DKMGH&5Z}jh&UhbpbRc#7KnmOr#96f}i!vS>Cf(}=3KZD^;3letqjp{c<<7lrY#u#y?f=%AhN&L}n?k855$wW!W3M<`ZqY@?f1E75UtXu5EIt@$ zdPO{T=7WQ#En@LGXQ9C~(jZR0$gYT{XZ#Lb#Pn>U($j5hNwA)~$okhYy&&$n#8%fa z9TiKjuvu=V46*1cyX$6pM!ay9J?(BP5HDS0``k@WDx0n&0)q#Bd*wQ_cp$*qKb-eH zOkaD8lizoK?QhC9h}S+~@qwliaUNr*0!?ehee>9d^-VLyA3tPa4NPI;)FRfufvKrj zTEuc2m>w5@FJh}2mSi85f7bacj6IX&lw63gij2dVfkH6lawQ8Nl*tr>~xmh?P{_GR-J>uiz^n@7T)DL zc8)o$rtig#7g(PJQ?9rQzjT#g$`Xq&vB++wD@xH7ip5>9F1y0obvLDX&AAFe9RpVR z*5y~(lJ3aL+-uC(11Y|Do&4f1Wa;%!X7(@{#IirwJ3UR$)V=!$9Nh7R13b*N&bZ0W z^)&So58Y%@y-YKd6MwowUHFq#^)lJ(p14K6-H|`E<&|43wzp}Vc>Ff|v^R=*{0{lX z;jl}0oMBIzUY;O2a@o>trk>*3GtP_KOpylh_jAss+fAjSSa^l)-C^n=?!4l>zr!@g zARfKW=6q?oDSrP4o4Lz0T|9o11?)E65>MQ6nyXAV4C0Zy%wvx!$!qJquTV&QQv)x5 z-D4B>n5Kx;_u1_|rUl}yzuAX-O~dOPnY_pO=U&sV265|D_T$$kyIA@Lv+jqQ&6~!` z_nW$j$EUGB_M1A1MQ=LW9xyF1_-&oB2a1YW1(%3_)`K&gKUbSXQT+ZL_Ul1YJ8}AK zX8zVxB$gDivTvbgN8V+l51Hb{qW9RML(q;n@3WxqOee*=b2fZ$>WZWpOE_#=+vM6j z#OzNoQx$97K266d9*8C^a z@FvrXDH?aR<5Dyfg~9dDy1bYz{0XXlsF+p#WC|CjEpp!e$<)XoE?eRZ{MmG*8ZF_x zx>9Baab6Xhv0A<)PT#{`ESG19bM`WiHFERNt$V-L6l4!se41cBZRtve)(3d8@AtjV zerx1>H}T{l_SJehTrB#I-B~Y(i95ez%{ItK#gfC!;FQb6gGbnEr#wJh_JdR0DC5V~ z&i&*}_)JFgFF59$ut{zq234JKrFMucctk~)8~Vcuwqc9BTr51v58)6TlW&pFEuZIio5he%MJ=&WBU&k1|##z%hYqqa7PmDU5C zw}ssgphu*9zHR|~7|;^H8v+@0!am1hr-L>^@qBjwwA|XhWxk)V1E>O8!5{CatzmBi zv<0vQ$hG%O&$2nR^{(DuJMR7@UI!Rs&(VBf;)0yV)t=2dBL_C7vnf1l5wPpMbYR=h z$OFAd#8+&Qtm#>~VF$ghC^!$!9ET%`nM3S3bX?~D&QM2|aaN93W8&#|VxOIrlN#$| zibhON=4N${j_9C9(TX)bCpT>JTuvIDZg?nbbcCiu`v{;jJyni9)FXZ5XU-Pl4qM&B8qg-(Eu&O*pl;d+ZOTg$TQRd z_JNM%C$r$Hmv=R=Yv<%O>=pvx9X?%%1lHk#9O$JNbz_4spl!PAe05-pF32B==N7ZL zi*i6fFF0`>=?(jnz*7c%z26uU_cZKuk?|kbo7e}xa#>!{Vtk38`uw>GqVfguGtz|{ zkkRl1d!|~=OWCtm+(dy zA^aSK-0X=yrG9Gu4}IdN9ykj|-B5Wrp9@aevo!5u>5cNyH63Bls_dRYnbqV32;nqv z6}YX#-=%)cGC*qks84X!%>>>*@MO>3kp0BmPg(j6xznTEjjG4g;Ad{g-o9?j{nWc7 zvoVs=hQw_-GyWmRi75GQ~#BI4?S6U=@s0DhYV2)Yg}dw?BtKG zog~w41kqC$y2$vCYx|}VtF+{4$4q^KhO)kh5PG4aAM5w0{Ca$nE!&2hwK(@b!Jd96>&CsyUNQBB(0vTOoQepb*GB5!4XMushFo2JP!(kr*@Nw`3 z*y$qUKaPXJ5#@dl?Yk*&Ga;jEX1TNKmOR5C-dV#2-I1fkwrknUJ93biyq1;T!5U}s zT2_5WZZDRuo%@s5K@ljdMN6iP|UL@{^_ z$nSbqeGgSvx}IIQhq!jGXN~U5!QzeeEcQO?tlkDTnt108Z1R2Nb?64I&c<$FU z?&3u->Bck9y9IXMUR&Tsx}dAa9{qX_oRsOX<&T5|SMX7=lhW&D{U96cNa?JXjV77a zCIx7e9$6{s5-B??7m3*5Xog7*nF#whJPOkXPJ%2Mpo<^Gw}W$KrY8V;-V2;$sZKv3 z1^&hWPl1y?)xh@sEpHWXJ3pAI4C`3}FUx>ZU=6Sqr~uXjPGB=YezpSJfJ$IHum|`W z*bf|VvbF~0oo@doF683>7Z>w!SH`zE^qHSPYa$Es+W=-7OkUx1u5cWX?z-Faox9SY zAbpcI(!LMRH1~5}1Ut%D?^v_Otu-HtZcq~v!5ErI+$=3=v5o6@hzia-^tzB-yTX}!EsR1 z3-0wnM}+4sT1Vk-xsDtc06K)8_D`*JFv{xq`e^{ zJ<7CCq_UmD1$xhKz%zB#X)y6$#hb8XAh>OeU6>9#@1k$P{xtk*(Q5gpa#GFB zfSaXdR<5>-w6yZvs?8b4w`)y&9wxltrK+4DyDfQ=B{2)z&58Epw@HeN3=`|+r8Myk z{lZT@n{f_nB+}&2FIc{p(!zH%

WzOgNFe+)D{co{6wLkF#KZ2bc}i-r87MHvcG+8Lg2Rz@>nh#wbMgc0D$NQy?$iRm2@li{Iq!!e(YAy0{@}F!*S7#I+o|<< zt+QHu9{7hq5kQ@eTIM6z=L4>a_E2m3V}>t&HuTbK!UAVAvocT=!*;WLU!}D;VmB-C zRpP{{yV*%!r6;~G-NsK@Q7{J$TLP>Hb_0ij)4(0TbDN*g00;wOf!@GCAQgB8mt8Ej0L6uML;RA9{39QrqZvOZ~>M(fDDbY0G)uYzz`q_$lT8I z>nY0$gfGzxf%-rzpfk`5cpexFOaKai*+3Cc0;~Zx169Bw;1uvD;JynQ34{TWKn&0g z=nFg#WCJe)(}5yj@h)Vv9F{G>Uf>vT1Py-{=1st`+fVS=%_{xzsr>ou7k_1Vr^-Ek z!XUKQvjA!6LbMsRAE6lbMZjV}FJHp40+c8@buSi0@YZK9D+^E(yi4GXE;0rPEIUA{ z$IO9>2Y&e=Fi?pPsI$*c-2ydmnc*-EE`Ru-ifrl)K_dj7z}^p3qI};5r3Kpq|LjPh z5}ddZa?*1LOgfmL#e=txT)`uur*tnWaT0Dv*wx!h4`qKTGT1XGD}xpqd{9{i8Sl)W z7}&e9N+8RsulN;o{2KL-$Y^CtCg&-z(`u1(8|<{d;~bj_fkq9^7r}1P@r|%I*KykE zP^P*6%djK7)6hU!lPwExH}74C|kr9y^r~++gMT>(rnIUMB#(O z%AdpY^Z@mkPzZOU^r`we?^?szhpm*jL@p;apnP+_suo{aihX%>e(Jei+Ky9kIj6_@#O*!o?RvC(Am^=B!9v?&maJf@ZIv;RhkwNs0^eI;HiY>R zf>LNp%MO;D603jcJov_oX$IsN^s2=;eof04lzp+zD$ZHdY#zn>e>g4`i z?T+xY>`}JlWO~(CaQx<{9TdT0lhmH4tWRMxLT>>+2OPh#DeaYjv_l9d!G8?4;5?3C z2wJGJ4l%=Zas36qlmTw42g=5w)Ha7DEUc|1^rYtA944)K^_ku}H(YtFac{U-;F1cH zhI)PO@cUTdO0R-X;6yu_N|%>|FlnSd29pvWjSc$$%a3qMOBh~iDj*axsF0czAMq;@ z(RSbqz(UtcmbUmKx0PRc((+2nmasgWw;k~KLPS2k#g${rnG~Lsm17U5S2p3OgRDM2T(BF#jl}r|;-9+KihAlb-ZI?K7gV;@k*M>VA-wMk!JF zDXpVX%3B`izQu4Ah40T}=9%0PqxxGNl|ZTV5Tr+*V5`?lmSS0P@yxAGZG(%4^M(yH||On4QJvBFMDo2TlZ*S1eB zU{ZH$4U=kv>lA%}0q;H2w>GrT4v}p%9+9}B9|8YVTgOgP_YY*1>m@g4iB=@jOt?@R zN-QE;3HR9u+8mTBDH_=5%#K#px%<9+)lXe*^@ogBT`yl{onn<3|3i?I-;Mx^E{w=~ zNjSu&#wy`ry=%_(vC2OGg4u=Dcz#$cSQo06DtxI>wcHV`Tgf7JoiAATtG50-@N8SP zh29XX7s*y<^4xj&J!jQw*|HVq``#sK%W;&L3~w)lZyEhDR2S7*0Pn5$H~dFi1u zco6(<0XeijJ@s9|`V%?0PrhOKeZ62kuae>ppN{%Xu>PeIw{2fFd`Gap{T_Myvi+9D zYXoZ%5s$7DV#X=Q@bFw^XWnvawtI$P-K4U_vMQUFzNUu%mWmpqE}$!D3UCu!lr@o!Kc$R-K1r``CpvrCGi7hV_Ik zKo#&UkPonz#wo+hrz=s2U&YDw+jSrGS5s40XS1=&Py;(TPD!X&^YJ-LWJF|CyNKv^ Mk)2sgx-#JZ0Gs6sJpcdz diff --git a/apps/gipy/pkg/gps_bg.wasm.d.ts b/apps/gipy/pkg/gps_bg.wasm.d.ts index df9a024fa..b4303ee30 100644 --- a/apps/gipy/pkg/gps_bg.wasm.d.ts +++ b/apps/gipy/pkg/gps_bg.wasm.d.ts @@ -12,8 +12,8 @@ export function gps_from_area(a: number, b: number, c: number, d: number): numbe export function __wbindgen_malloc(a: number): number; export function __wbindgen_realloc(a: number, b: number, c: number): number; export const __wbindgen_export_2: WebAssembly.Table; -export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab13c10d53cd1c5a(a: number, b: number, c: number): void; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__heb2f4d39a212d7d1(a: number, b: number, c: number): void; export function __wbindgen_add_to_stack_pointer(a: number): number; export function __wbindgen_free(a: number, b: number): void; export function __wbindgen_exn_store(a: number): void; -export function wasm_bindgen__convert__closures__invoke2_mut__h26ce002f44a5439b(a: number, b: number, c: number, d: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h362f82c7669db137(a: number, b: number, c: number, d: number): void; From fd5cc494c522dc110b6b2f4f64f6bd81490d5a57 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 5 Jul 2023 15:46:45 +0200 Subject: [PATCH 02/11] gipy: readme update --- apps/gipy/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index 44a8b9bcd..242282dbf 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -119,4 +119,7 @@ I had to go back uphill by quite a distance. Feel free to give me feedback : is it useful for you ? what other features would you like ? +If you want to raise issues the main repository is [https://github.com/wagnerf42/BangleApps](here) and +the rust code doing the actual map computations is located [https://github.com/wagnerf42/gps](here). + frederic.wagner@imag.fr From d165c822d3a3e485a1f31f47b06b47a2cbc24c7e Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Wed, 5 Jul 2023 15:59:15 +0200 Subject: [PATCH 03/11] gipy: forgot to bump metadata up --- apps/gipy/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json index 8b8c88780..7dd4123f6 100644 --- a/apps/gipy/metadata.json +++ b/apps/gipy/metadata.json @@ -2,7 +2,7 @@ "id": "gipy", "name": "Gipy", "shortName": "Gipy", - "version": "0.19", + "version": "0.20", "description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.", "allow_emulator":false, "icon": "gipy.png", From e40cc26240f5303cabd1c6cf1c1c3ec337b60982 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Fri, 7 Jul 2023 09:54:55 +0200 Subject: [PATCH 04/11] gipy: new settings + attempts at power saving --- apps/gipy/ChangeLog | 3 + apps/gipy/app.js | 2780 +++++++++++++++++++++-------------------- apps/gipy/settings.js | 71 +- 3 files changed, 1448 insertions(+), 1406 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 9e9654fd0..c2e9a21b4 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -92,3 +92,6 @@ * Large display for instant speed * Bugfix for negative coordinates * Disable menu while the map is not loaded + * Turn screen off while idling to save battery + * New setting : disable buzz on turns + * New setting : turn bluetooth off to save battery diff --git a/apps/gipy/app.js b/apps/gipy/app.js index d34cb75fe..866849efc 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -6,62 +6,63 @@ let zoomed = true; let status; let interests_colors = [ - 0xffff, // Waypoints, white - 0xf800, // Bakery, red - 0x001f, // DrinkingWater, blue - 0x07ff, // Toilets, cyan - 0x07e0, // Artwork, green + 0xffff, // Waypoints, white + 0xf800, // Bakery, red + 0x001f, // DrinkingWater, blue + 0x07ff, // Toilets, cyan + 0x07e0, // Artwork, green ]; let Y_OFFSET = 20; let s = require("Storage"); -var settings = Object.assign( - { - lost_distance: 50, - }, - s.readJSON("gipy.json", true) || {} +var settings = Object.assign({ + lost_distance: 50, + buzz_on_turns: false, + disable_bluetooth: true, + }, + s.readJSON("gipy.json", true) || {} ); let profile_start_times = []; let splashscreen = require("heatshrink").decompress( - atob( - "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" - ) + atob( + "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" + ) ); function start_profiling() { - profile_start_times.push(getTime()); + profile_start_times.push(getTime()); } function end_profiling(label) { - let end_time = getTime(); - let elapsed = end_time - profile_start_times.pop(); - console.log("profile:", label, "took", elapsed); + let end_time = getTime(); + let elapsed = end_time - profile_start_times.pop(); + console.log("profile:", label, "took", elapsed); } // return the index of the largest element of the array which is <= x function binary_search(array, x) { - let start = 0, - end = array.length; + let start = 0, + end = array.length; - while (end - start >= 0) { - let mid = Math.floor((start + end) / 2); - if (array[mid] == x) { - return mid; - } else if (array[mid] < x) { - if (array[mid + 1] > x) { - return mid; - } - start = mid + 1; - } else end = mid - 1; - } - if (array[start] > x) { - return null; - } else { - return start; - } + while (end - start >= 0) { + let mid = Math.floor((start + end) / 2); + if (array[mid] == x) { + return mid; + } else if (array[mid] < x) { + if (array[mid + 1] > x) { + return mid; + } + start = mid + 1; + } else end = mid - 1; + } + if (array[start] > x) { + return null; + } else { + return start; + } } // return a string containing estimated time of arrival. @@ -69,1446 +70,1465 @@ function binary_search(array, x) { // remaining distance in km // hour, minutes is current time function compute_eta(hour, minutes, approximate_speed, remaining_distance) { - if (isNaN(approximate_speed) || approximate_speed < 0.1) { - return ""; - } - let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes - let eta_in_minutes = Math.round(hour * 60 + minutes + time_needed); - let eta_minutes = eta_in_minutes % 60; - let eta_hour = ((eta_in_minutes - eta_minutes) / 60) % 24; - if (eta_minutes < 10) { - return eta_hour.toString() + ":0" + eta_minutes; - } else { - return eta_hour.toString() + ":" + eta_minutes; - } + if (isNaN(approximate_speed) || approximate_speed < 0.1) { + return ""; + } + let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes + let eta_in_minutes = Math.round(hour * 60 + minutes + time_needed); + let eta_minutes = eta_in_minutes % 60; + let eta_hour = ((eta_in_minutes - eta_minutes) / 60) % 24; + if (eta_minutes < 10) { + return eta_hour.toString() + ":0" + eta_minutes; + } else { + return eta_hour.toString() + ":" + eta_minutes; + } } class TilesOffsets { - constructor(buffer, offset) { - let type_size = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - this.entry_size = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0]; - offset += 2; - this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number); - offset += 2 * non_empty_tiles_number; - if (type_size == 24) { - this.non_empty_tiles_ends = Uint24Array( - buffer, - offset, - non_empty_tiles_number - ); - offset += 3 * non_empty_tiles_number; - } else if (type_size == 16) { - this.non_empty_tiles_ends = Uint16Array( - buffer, - offset, - non_empty_tiles_number - ); - offset += 2 * non_empty_tiles_number; - } else { - throw "unknown size"; + constructor(buffer, offset) { + let type_size = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + this.entry_size = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0]; + offset += 2; + this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number); + offset += 2 * non_empty_tiles_number; + if (type_size == 24) { + this.non_empty_tiles_ends = Uint24Array( + buffer, + offset, + non_empty_tiles_number + ); + offset += 3 * non_empty_tiles_number; + } else if (type_size == 16) { + this.non_empty_tiles_ends = Uint16Array( + buffer, + offset, + non_empty_tiles_number + ); + offset += 2 * non_empty_tiles_number; + } else { + throw "unknown size"; + } + return [this, offset]; } - return [this, offset]; - } - tile_start_offset(tile_index) { - if (tile_index <= this.non_empty_tiles[0]) { - return 0; - } else { - return this.tile_end_offset(tile_index - 1); + tile_start_offset(tile_index) { + if (tile_index <= this.non_empty_tiles[0]) { + return 0; + } else { + return this.tile_end_offset(tile_index - 1); + } } - } - tile_end_offset(tile_index) { - let me_or_before = binary_search(this.non_empty_tiles, tile_index); - if (me_or_before === null) { - return 0; + tile_end_offset(tile_index) { + let me_or_before = binary_search(this.non_empty_tiles, tile_index); + if (me_or_before === null) { + return 0; + } + if (me_or_before >= this.non_empty_tiles_ends.length) { + return ( + this.non_empty_tiles_ends[this.non_empty_tiles.length - 1] * + this.entry_size + ); + } else { + return this.non_empty_tiles_ends[me_or_before] * this.entry_size; + } } - if (me_or_before >= this.non_empty_tiles_ends.length) { - return ( - this.non_empty_tiles_ends[this.non_empty_tiles.length - 1] * - this.entry_size - ); - } else { - return this.non_empty_tiles_ends[me_or_before] * this.entry_size; + end_offset() { + return ( + this.non_empty_tiles_ends[this.non_empty_tiles_ends.length - 1] * + this.entry_size + ); } - } - end_offset() { - return ( - this.non_empty_tiles_ends[this.non_empty_tiles_ends.length - 1] * - this.entry_size - ); - } } class Map { - constructor(buffer, offset, filename) { - this.points_cache = []; // don't refetch points all the time - // header - let color_array = Uint8Array(buffer, offset, 3); - this.color = [ - color_array[0] / 255, - color_array[1] / 255, - color_array[2] / 255, - ]; - offset += 3; - this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile - offset += 2 * 4; - this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height - offset += 2 * 4; - this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates - offset += 2 * 8; - let side_array = Float64Array(buffer, offset, 1); // side of a tile - this.side = side_array[0]; - offset += 8; + constructor(buffer, offset, filename) { + this.points_cache = []; // don't refetch points all the time + // header + let color_array = Uint8Array(buffer, offset, 3); + this.color = [ + color_array[0] / 255, + color_array[1] / 255, + color_array[2] / 255, + ]; + offset += 3; + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile + offset += 2 * 4; + this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height + offset += 2 * 4; + this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates + offset += 2 * 8; + let side_array = Float64Array(buffer, offset, 1); // side of a tile + this.side = side_array[0]; + offset += 8; - // tiles offsets - let res = new TilesOffsets(buffer, offset); - this.tiles_offsets = res[0]; - offset = res[1]; + // tiles offsets + let res = new TilesOffsets(buffer, offset); + this.tiles_offsets = res[0]; + offset = res[1]; - // now, do binary ways - // since the file is so big we'll go line by line - let binary_lines = []; - for (let y = 0; y < this.grid_size[1]; y++) { - let first_tile_start = this.tiles_offsets.tile_start_offset( - y * this.grid_size[0] - ); - let last_tile_end = this.tiles_offsets.tile_start_offset( - (y + 1) * this.grid_size[0] - ); - let size = last_tile_end - first_tile_start; - let string = s.read(filename, offset + first_tile_start, size); - let array = Uint8Array(E.toArrayBuffer(string)); - binary_lines.push(array); - } - this.binary_lines = binary_lines; - offset += this.tiles_offsets.end_offset(); - - return [this, offset]; - - // now do streets data header - // let streets_header = E.toArrayBuffer(s.read(filename, offset, 8)); - // let streets_header_offset = 0; - // let full_streets_size = Uint32Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 4; - // let blocks_number = Uint16Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 2; - // let labels_string_size = Uint16Array( - // streets_header, - // streets_header_offset, - // 1 - // )[0]; - // streets_header_offset += 2; - // offset += streets_header_offset; - - // // continue with main streets labels - // main_streets_labels = s.read(filename, offset, labels_string_size); - // // this.main_streets_labels = main_streets_labels.split(/\r?\n/); - // this.main_streets_labels = main_streets_labels.split(/\n/); - // offset += labels_string_size; - - // // continue with blocks start offsets - // this.blocks_offsets = Uint32Array( - // E.toArrayBuffer(s.read(filename, offset, blocks_number * 4)) - // ); - // offset += blocks_number * 4; - - // // continue with compressed street blocks - // let encoded_blocks_size = - // full_streets_size - 4 - 2 - 2 - labels_string_size - blocks_number * 4; - // this.compressed_streets = Uint8Array( - // E.toArrayBuffer(s.read(filename, offset, encoded_blocks_size)) - // ); - // offset += encoded_blocks_size; - } - - display( - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - g.setColor(this.color[0], this.color[1], this.color[2]); - let local_x = displayed_x - this.start_coordinates[0]; - let local_y = displayed_y - this.start_coordinates[1]; - let tile_x = Math.floor(local_x / this.side); - let tile_y = Math.floor(local_y / this.side); - - let limit = 1; - if (!zoomed) { - limit = 2; - } - for (let y = tile_y - limit; y <= tile_y + limit; y++) { - if (y < 0 || y >= this.grid_size[1]) { - continue; - } - for (let x = tile_x - limit; x <= tile_x + limit; x++) { - if (x < 0 || x >= this.grid_size[0]) { - continue; - } - if ( - this.tile_is_on_screen( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ) - ) { -// let colors = [ -// [0, 0, 0], -// [0, 0, 1], -// [0, 1, 0], -// [0, 1, 1], -// [1, 0, 0], -// [1, 0, 1], -// [1, 1, 0], -// [1, 1, 0.5], -// [0.5, 0, 0.5], -// [0, 0.5, 0.5], -// ]; - if (this.color[0] == 1 && this.color[1] == 0 && this.color[2] == 0) { - this.display_thick_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction + // now, do binary ways + // since the file is so big we'll go line by line + let binary_lines = []; + for (let y = 0; y < this.grid_size[1]; y++) { + let first_tile_start = this.tiles_offsets.tile_start_offset( + y * this.grid_size[0] ); - } else { - this.display_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction + let last_tile_end = this.tiles_offsets.tile_start_offset( + (y + 1) * this.grid_size[0] ); - } + let size = last_tile_end - first_tile_start; + let string = s.read(filename, offset + first_tile_start, size); + let array = Uint8Array(E.toArrayBuffer(string)); + binary_lines.push(array); } - } + this.binary_lines = binary_lines; + offset += this.tiles_offsets.end_offset(); + + return [this, offset]; + + // now do streets data header + // let streets_header = E.toArrayBuffer(s.read(filename, offset, 8)); + // let streets_header_offset = 0; + // let full_streets_size = Uint32Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 4; + // let blocks_number = Uint16Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 2; + // let labels_string_size = Uint16Array( + // streets_header, + // streets_header_offset, + // 1 + // )[0]; + // streets_header_offset += 2; + // offset += streets_header_offset; + + // // continue with main streets labels + // main_streets_labels = s.read(filename, offset, labels_string_size); + // // this.main_streets_labels = main_streets_labels.split(/\r?\n/); + // this.main_streets_labels = main_streets_labels.split(/\n/); + // offset += labels_string_size; + + // // continue with blocks start offsets + // this.blocks_offsets = Uint32Array( + // E.toArrayBuffer(s.read(filename, offset, blocks_number * 4)) + // ); + // offset += blocks_number * 4; + + // // continue with compressed street blocks + // let encoded_blocks_size = + // full_streets_size - 4 - 2 - 2 - labels_string_size - blocks_number * 4; + // this.compressed_streets = Uint8Array( + // E.toArrayBuffer(s.read(filename, offset, encoded_blocks_size)) + // ); + // offset += encoded_blocks_size; } - } - tile_is_on_screen( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - let width = g.getWidth(); - let height = g.getHeight(); - let center_x = width / 2; - let center_y = height / 2 + Y_OFFSET; - let side = this.side; - let tile_center_x = (tile_x + 0.5) * side; - let tile_center_y = (tile_y + 0.5) * side; - let scaled_center_x = (tile_center_x - current_x) * scale_factor; - let scaled_center_y = (tile_center_y - current_y) * scale_factor; - let rotated_center_x = scaled_center_x * cos_direction - scaled_center_y * sin_direction; - let rotated_center_y = scaled_center_x * sin_direction + scaled_center_y * cos_direction; - let on_screen_center_x = center_x - rotated_center_x; - let on_screen_center_y = center_y + rotated_center_y; + display( + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + g.setColor(this.color[0], this.color[1], this.color[2]); + let local_x = displayed_x - this.start_coordinates[0]; + let local_y = displayed_y - this.start_coordinates[1]; + let tile_x = Math.floor(local_x / this.side); + let tile_y = Math.floor(local_y / this.side); - let scaled_side = side * scale_factor * Math.sqrt(1/2); - - if (on_screen_center_x + scaled_side <= 0) { - return false; + let limit = 1; + if (!zoomed) { + limit = 2; + } + for (let y = tile_y - limit; y <= tile_y + limit; y++) { + if (y < 0 || y >= this.grid_size[1]) { + continue; + } + for (let x = tile_x - limit; x <= tile_x + limit; x++) { + if (x < 0 || x >= this.grid_size[0]) { + continue; + } + if ( + this.tile_is_on_screen( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ) + ) { + // let colors = [ + // [0, 0, 0], + // [0, 0, 1], + // [0, 1, 0], + // [0, 1, 1], + // [1, 0, 0], + // [1, 0, 1], + // [1, 1, 0], + // [1, 1, 0.5], + // [0.5, 0, 0.5], + // [0, 0.5, 0.5], + // ]; + if (this.color[0] == 1 && this.color[1] == 0 && this.color[2] == 0) { + this.display_thick_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ); + } else { + this.display_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ); + } + } + } + } } - if (on_screen_center_x - scaled_side >= width) { - return false; + + tile_is_on_screen( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + let width = g.getWidth(); + let height = g.getHeight(); + let center_x = width / 2; + let center_y = height / 2 + Y_OFFSET; + let side = this.side; + let tile_center_x = (tile_x + 0.5) * side; + let tile_center_y = (tile_y + 0.5) * side; + let scaled_center_x = (tile_center_x - current_x) * scale_factor; + let scaled_center_y = (tile_center_y - current_y) * scale_factor; + let rotated_center_x = scaled_center_x * cos_direction - scaled_center_y * sin_direction; + let rotated_center_y = scaled_center_x * sin_direction + scaled_center_y * cos_direction; + let on_screen_center_x = center_x - rotated_center_x; + let on_screen_center_y = center_y + rotated_center_y; + + let scaled_side = side * scale_factor * Math.sqrt(1 / 2); + + if (on_screen_center_x + scaled_side <= 0) { + return false; + } + if (on_screen_center_x - scaled_side >= width) { + return false; + } + if (on_screen_center_y + scaled_side <= 0) { + return false; + } + if (on_screen_center_y - scaled_side >= height) { + return false; + } + return true; } - if (on_screen_center_y + scaled_side <= 0) { - return false; + + tile_points(tile_num, tile_x, tile_y, scaled_side) { + let line_start_offset = this.tiles_offsets.tile_start_offset( + tile_y * this.grid_size[0] + ); + let offset = + this.tiles_offsets.tile_start_offset(tile_num) - line_start_offset; + let upper_limit = + this.tiles_offsets.tile_end_offset(tile_num) - line_start_offset; + + let line = this.binary_lines[tile_y]; + // we need to copy both for correct results and for performances + // let's precompute also. + let cached_tile = new Float64Array(upper_limit - offset); + for (let i = offset; i < upper_limit; i += 2) { + let x = (tile_x + line.buffer[i] / 255) * scaled_side; + let y = (tile_y + line.buffer[i + 1] / 255) * scaled_side; + cached_tile[i - offset] = x; + cached_tile[i + 1 - offset] = y; + } + return cached_tile; } - if (on_screen_center_y - scaled_side >= height) { - return false; + + invalidate_caches() { + this.points_cache = []; } - return true; - } - tile_points(tile_num, tile_x, tile_y, scaled_side) { - let line_start_offset = this.tiles_offsets.tile_start_offset( - tile_y * this.grid_size[0] - ); - let offset = - this.tiles_offsets.tile_start_offset(tile_num) - line_start_offset; - let upper_limit = - this.tiles_offsets.tile_end_offset(tile_num) - line_start_offset; - - let line = this.binary_lines[tile_y]; - // we need to copy both for correct results and for performances - // let's precompute also. - let cached_tile = new Float64Array(upper_limit - offset); - for (let i = offset; i < upper_limit; i += 2) { - let x = (tile_x + line.buffer[i] / 255) * scaled_side; - let y = (tile_y + line.buffer[i + 1] / 255) * scaled_side; - cached_tile[i - offset] = x; - cached_tile[i + 1 - offset] = y; + fetch_points(tile_x, tile_y, scaled_side) { + let tile_num = tile_x + tile_y * this.grid_size[0]; + for (let i = 0; i < this.points_cache.length; i++) { + if (this.points_cache[i][0] == tile_num) { + return this.points_cache[i][1]; + } + } + if (this.points_cache.length > 40) { + this.points_cache.shift(); + } + let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); + this.points_cache.push([tile_num, points]); + return points; } - return cached_tile; - } - invalidate_caches() { - this.points_cache = []; - } + display_tile( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + "jit"; + let center_x = g.getWidth() / 2; + let center_y = g.getHeight() / 2 + Y_OFFSET; - fetch_points(tile_x, tile_y, scaled_side) { - let tile_num = tile_x + tile_y * this.grid_size[0]; - for (let i = 0; i < this.points_cache.length; i++) { - if (this.points_cache[i][0] == tile_num) { - return this.points_cache[i][1]; - } + let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + let scaled_current_x = current_x * scale_factor; + let scaled_current_y = current_y * scale_factor; + + for (let i = 0; i < points.length; i += 4) { + let scaled_x = points[i] - scaled_current_x; + let scaled_y = points[i + 1] - scaled_current_y; + let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let final_x = center_x - rotated_x; + let final_y = center_y + rotated_y; + scaled_x = points[i + 2] - scaled_current_x; + scaled_y = points[i + 3] - scaled_current_y; + rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let new_final_x = center_x - rotated_x; + let new_final_y = center_y + rotated_y; + g.drawLine(final_x, final_y, new_final_x, new_final_y); + } } - if (this.points_cache.length > 40) { - this.points_cache.shift(); + + display_thick_tile( + tile_x, + tile_y, + current_x, + current_y, + scale_factor, + cos_direction, + sin_direction + ) { + let center_x = g.getWidth() / 2; + let center_y = g.getHeight() / 2 + Y_OFFSET; + + let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + let scaled_current_x = current_x * scale_factor; + let scaled_current_y = current_y * scale_factor; + + for (let i = 0; i < points.length; i += 4) { + let scaled_x = points[i] - scaled_current_x; + let scaled_y = points[i + 1] - scaled_current_y; + let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let final_x = center_x - rotated_x; + let final_y = center_y + rotated_y; + scaled_x = points[i + 2] - scaled_current_x; + scaled_y = points[i + 3] - scaled_current_y; + rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let new_final_x = center_x - rotated_x; + let new_final_y = center_y + rotated_y; + + let xdiff = new_final_x - final_x; + let ydiff = new_final_y - final_y; + let d = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + let ox = (-ydiff / d) * 3; + let oy = (xdiff / d) * 3; + g.fillPoly([ + final_x + ox, + final_y + oy, + new_final_x + ox, + new_final_y + oy, + new_final_x - ox, + new_final_y - oy, + final_x - ox, + final_y - oy, + ]); + } } - let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); - this.points_cache.push([tile_num, points]); - return points; - } - - display_tile( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - "jit"; - let center_x = g.getWidth() / 2; - let center_y = g.getHeight() / 2 + Y_OFFSET; - - let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - let scaled_current_x = current_x * scale_factor; - let scaled_current_y = current_y * scale_factor; - - for (let i = 0; i < points.length; i += 4) { - let scaled_x = points[i] - scaled_current_x; - let scaled_y = points[i + 1] - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = center_x - rotated_x; - let final_y = center_y + rotated_y; - scaled_x = points[i + 2] - scaled_current_x; - scaled_y = points[i + 3] - scaled_current_y; - rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let new_final_x = center_x - rotated_x; - let new_final_y = center_y + rotated_y; - g.drawLine(final_x, final_y, new_final_x, new_final_y); - } - } - - display_thick_tile( - tile_x, - tile_y, - current_x, - current_y, - scale_factor, - cos_direction, - sin_direction - ) { - let center_x = g.getWidth() / 2; - let center_y = g.getHeight() / 2 + Y_OFFSET; - - let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - let scaled_current_x = current_x * scale_factor; - let scaled_current_y = current_y * scale_factor; - - for (let i = 0; i < points.length; i += 4) { - let scaled_x = points[i] - scaled_current_x; - let scaled_y = points[i + 1] - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = center_x - rotated_x; - let final_y = center_y + rotated_y; - scaled_x = points[i + 2] - scaled_current_x; - scaled_y = points[i + 3] - scaled_current_y; - rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let new_final_x = center_x - rotated_x; - let new_final_y = center_y + rotated_y; - - let xdiff = new_final_x - final_x; - let ydiff = new_final_y - final_y; - let d = Math.sqrt(xdiff * xdiff + ydiff * ydiff); - let ox = (-ydiff / d) * 3; - let oy = (xdiff / d) * 3; - g.fillPoly([ - final_x + ox, - final_y + oy, - new_final_x + ox, - new_final_y + oy, - new_final_x - ox, - new_final_y - oy, - final_x - ox, - final_y - oy, - ]); - } - } } class Interests { - constructor(buffer, offset) { - this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile - offset += 2 * 4; - this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height - offset += 2 * 4; - this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates - offset += 2 * 8; - let side_array = Float64Array(buffer, offset, 1); // side of a tile - this.side = side_array[0]; - offset += 8; + constructor(buffer, offset) { + this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile + offset += 2 * 4; + this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height + offset += 2 * 4; + this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates + offset += 2 * 8; + let side_array = Float64Array(buffer, offset, 1); // side of a tile + this.side = side_array[0]; + offset += 8; - let res = new TilesOffsets(buffer, offset); - offset = res[1]; - this.offsets = res[0]; - let end = this.offsets.end_offset(); - this.binary_interests = new Uint8Array(end); - let binary_interests = Uint8Array(buffer, offset, end); - for (let i = 0; i < end; i++) { - this.binary_interests[i] = binary_interests[i]; - } - offset += end; - this.points_cache = []; - return [this, offset]; - } - - display( - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - let local_x = displayed_x - this.start_coordinates[0]; - let local_y = displayed_y - this.start_coordinates[1]; - let tile_x = Math.floor(local_x / this.side); - let tile_y = Math.floor(local_y / this.side); - for (let y = tile_y - 1; y <= tile_y + 1; y++) { - if (y < 0 || y >= this.grid_size[1]) { - continue; - } - for (let x = tile_x - 1; x <= tile_x + 1; x++) { - if (x < 0 || x >= this.grid_size[0]) { - continue; + let res = new TilesOffsets(buffer, offset); + offset = res[1]; + this.offsets = res[0]; + let end = this.offsets.end_offset(); + this.binary_interests = new Uint8Array(end); + let binary_interests = Uint8Array(buffer, offset, end); + for (let i = 0; i < end; i++) { + this.binary_interests[i] = binary_interests[i]; } - this.display_tile( - x, - y, - local_x, - local_y, - scale_factor, - cos_direction, - sin_direction - ); - } + offset += end; + this.points_cache = []; + return [this, offset]; } - } - tile_points(tile_num, tile_x, tile_y, scaled_side) { - let offset = this.offsets.tile_start_offset(tile_num); - let upper_limit = this.offsets.tile_end_offset(tile_num); - - let tile_interests = []; - for (let i = offset; i < upper_limit; i += 3) { - let interest = this.binary_interests[i]; - let x = (tile_x + this.binary_interests[i + 1] / 255) * scaled_side; - let y = (tile_y + this.binary_interests[i + 2] / 255) * scaled_side; - if (interest >= interests_colors.length) { - throw "bad interest" + interest + "at" + tile_num + "offset" + i; - } - tile_interests.push(interest); - tile_interests.push(x); - tile_interests.push(y); + display( + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + let local_x = displayed_x - this.start_coordinates[0]; + let local_y = displayed_y - this.start_coordinates[1]; + let tile_x = Math.floor(local_x / this.side); + let tile_y = Math.floor(local_y / this.side); + for (let y = tile_y - 1; y <= tile_y + 1; y++) { + if (y < 0 || y >= this.grid_size[1]) { + continue; + } + for (let x = tile_x - 1; x <= tile_x + 1; x++) { + if (x < 0 || x >= this.grid_size[0]) { + continue; + } + this.display_tile( + x, + y, + local_x, + local_y, + scale_factor, + cos_direction, + sin_direction + ); + } + } } - return tile_interests; - } - fetch_points(tile_x, tile_y, scaled_side) { - //TODO: factorize with map ? - let tile_num = tile_x + tile_y * this.grid_size[0]; - for (let i = 0; i < this.points_cache.length; i++) { - if (this.points_cache[i][0] == tile_num) { - return this.points_cache[i][1]; - } + + tile_points(tile_num, tile_x, tile_y, scaled_side) { + let offset = this.offsets.tile_start_offset(tile_num); + let upper_limit = this.offsets.tile_end_offset(tile_num); + + let tile_interests = []; + for (let i = offset; i < upper_limit; i += 3) { + let interest = this.binary_interests[i]; + let x = (tile_x + this.binary_interests[i + 1] / 255) * scaled_side; + let y = (tile_y + this.binary_interests[i + 2] / 255) * scaled_side; + if (interest >= interests_colors.length) { + throw "bad interest" + interest + "at" + tile_num + "offset" + i; + } + tile_interests.push(interest); + tile_interests.push(x); + tile_interests.push(y); + } + return tile_interests; } - if (this.points_cache.length > 40) { - this.points_cache.shift(); + fetch_points(tile_x, tile_y, scaled_side) { + //TODO: factorize with map ? + let tile_num = tile_x + tile_y * this.grid_size[0]; + for (let i = 0; i < this.points_cache.length; i++) { + if (this.points_cache[i][0] == tile_num) { + return this.points_cache[i][1]; + } + } + if (this.points_cache.length > 40) { + this.points_cache.shift(); + } + let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); + this.points_cache.push([tile_num, points]); + return points; } - let points = this.tile_points(tile_num, tile_x, tile_y, scaled_side); - this.points_cache.push([tile_num, points]); - return points; - } - invalidate_caches() { - this.points_cache = []; - } - display_tile( - tile_x, - tile_y, - displayed_x, - displayed_y, - scale_factor, - cos_direction, - sin_direction - ) { - let width = g.getWidth(); - let half_width = width / 2; - let half_height = g.getHeight() / 2 + Y_OFFSET; - let interests = this.fetch_points(tile_x, tile_y, this.side * scale_factor); - - let scaled_current_x = displayed_x * scale_factor; - let scaled_current_y = displayed_y * scale_factor; - - for (let i = 0; i < interests.length; i += 3) { - let type = interests[i]; - let x = interests[i + 1]; - let y = interests[i + 2]; - - let scaled_x = x - scaled_current_x; - let scaled_y = y - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = half_width - rotated_x; - let final_y = half_height + rotated_y; - - let color = interests_colors[type]; - if (type == 0) { - g.setColor(0, 0, 0).fillCircle(final_x, final_y, 6); - } - g.setColor(color).fillCircle(final_x, final_y, 5); + invalidate_caches() { + this.points_cache = []; + } + display_tile( + tile_x, + tile_y, + displayed_x, + displayed_y, + scale_factor, + cos_direction, + sin_direction + ) { + let width = g.getWidth(); + let half_width = width / 2; + let half_height = g.getHeight() / 2 + Y_OFFSET; + let interests = this.fetch_points(tile_x, tile_y, this.side * scale_factor); + + let scaled_current_x = displayed_x * scale_factor; + let scaled_current_y = displayed_y * scale_factor; + + for (let i = 0; i < interests.length; i += 3) { + let type = interests[i]; + let x = interests[i + 1]; + let y = interests[i + 2]; + + let scaled_x = x - scaled_current_x; + let scaled_y = y - scaled_current_y; + let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; + let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; + let final_x = half_width - rotated_x; + let final_y = half_height + rotated_y; + + let color = interests_colors[type]; + if (type == 0) { + g.setColor(0, 0, 0).fillCircle(final_x, final_y, 6); + } + g.setColor(color).fillCircle(final_x, final_y, 5); + } } - } } class Status { - constructor(path, maps, interests) { - this.path = path; - this.maps = maps; - this.interests = interests; - let half_screen_width = g.getWidth() / 2; - let half_screen_height = g.getHeight() / 2; - let half_screen_diagonal = Math.sqrt( - half_screen_width * half_screen_width + - half_screen_height * half_screen_height - ); - this.scale_factor = half_screen_diagonal / maps[0].side; // multiply geo coordinates by this to get pixels coordinates - this.on_path = true; // are we on the path or lost ? - this.position = null; // where we are - this.adjusted_cos_direction = 1; // cos of where we look at - this.adjusted_sin_direction = 0; // sin of where we look at - this.current_segment = null; // which segment is closest - this.reaching = null; // which waypoint are we reaching ? - this.distance_to_next_point = null; // how far are we from next point ? - this.projected_point = null; - - if (this.path !== null) { - let r = [0]; - // let's do a reversed prefix computations on all distances: - // loop on all segments in reversed order - let previous_point = null; - for (let i = this.path.len - 1; i >= 0; i--) { - let point = this.path.point(i); - if (previous_point !== null) { - r.unshift(r[0] + point.distance(previous_point)); - } - previous_point = point; - } - this.remaining_distances = r; // how much distance remains at start of each segment - } - this.starting_time = null; // time we start - this.advanced_distance = 0.0; - this.gps_coordinates_counter = 0; // how many coordinates did we receive - this.old_points = []; // record previous points but only when enough distance between them - this.old_times = []; // the corresponding times - } - invalidate_caches() { - for (let i = 0; i < this.maps.length; i++) { - this.maps[i].invalidate_caches(); - } - if (this.interests !== null) { - this.interests.invalidate_caches(); - } - } - new_position_reached(position) { - // we try to figure out direction by looking at previous points - // instead of the gps course which is not very nice. - - let now = getTime(); - - if (this.old_points.length == 0) { - this.gps_coordinates_counter += 1; - this.old_points.push(position); - this.old_times.push(now); - return null; - } else { - let previous_point = this.old_points[this.old_points.length - 1]; - let distance_to_previous = previous_point.distance(position); - // gps signal is noisy but rarely above 5 meters - if (distance_to_previous < 5) { - return null; - } - } - this.gps_coordinates_counter += 1; - this.old_points.push(position); - this.old_times.push(now); - - let oldest_point = this.old_points[0]; - let distance_to_oldest = oldest_point.distance(position); - - // every 3 points we count the distance - if (this.gps_coordinates_counter % 3 == 0) { - if (distance_to_oldest < 150.0) { - // to avoid gps glitches - this.advanced_distance += distance_to_oldest; - } - } - - this.instant_speed = distance_to_oldest / (now - this.old_times[0]); - - if (this.old_points.length == 4) { - this.old_points.shift(); - this.old_times.shift(); - } - // let's just take angle of segment between newest point and a point a bit before - let previous_index = this.old_points.length - 3; - if (previous_index < 0) { - previous_index = 0; - } - let diff = position.minus(this.old_points[previous_index]); - let angle = Math.atan2(diff.lat, diff.lon); - return angle; - } - update_position(new_position, maybe_direction, timestamp) { - let direction = this.new_position_reached(new_position); - if (direction === null) { - if (maybe_direction === null) { - return; - } else { - direction = maybe_direction; - } - } - if (in_menu) { - return; - } - - this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); - this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0); - this.angle = direction; - let cos_direction = Math.cos(direction); - let sin_direction = Math.sin(direction); - this.position = new_position; - - // we will display position of where we'll be at in a few seconds - // and not where we currently are. - // this is because the display has more than 1sec duration. - this.displayed_position = new Point( - new_position.lon + cos_direction * this.instant_speed * 0.00001, - new_position.lat + sin_direction * this.instant_speed * 0.00001 - ); - - // abort if we are late - // if (timestamp !== null) { - // let elapsed = Date.now() - timestamp; - // if (elapsed > 1000) { - // console.log("we are late"); - // return; - // } - // console.log("we are not late"); - // } - - if (this.path !== null) { - // detect segment we are on now - let res = this.path.nearest_segment( - this.displayed_position, - Math.max(0, this.current_segment - 1), - Math.min(this.current_segment + 2, this.path.len - 1), - cos_direction, - sin_direction - ); - let orientation = res[0]; - let next_segment = res[1]; - - if (this.is_lost(next_segment)) { - // start_profiling(); - // it did not work, try anywhere - res = this.path.nearest_segment( - this.displayed_position, - 0, - this.path.len - 1, - cos_direction, - sin_direction + constructor(path, maps, interests) { + this.path = path; + this.maps = maps; + this.interests = interests; + let half_screen_width = g.getWidth() / 2; + let half_screen_height = g.getHeight() / 2; + let half_screen_diagonal = Math.sqrt( + half_screen_width * half_screen_width + + half_screen_height * half_screen_height ); - orientation = res[0]; - next_segment = res[1]; - // end_profiling("repositioning"); - } - // now check if we strayed away from path or back to it - let lost = this.is_lost(next_segment); - if (this.on_path == lost) { - // if status changes - if (lost) { - Bangle.buzz(); // we lost path - setTimeout(() => Bangle.buzz(), 500); - setTimeout(() => Bangle.buzz(), 1000); - setTimeout(() => Bangle.buzz(), 1500); + this.scale_factor = half_screen_diagonal / maps[0].side; // multiply geo coordinates by this to get pixels coordinates + this.on_path = true; // are we on the path or lost ? + this.position = null; // where we are + this.adjusted_cos_direction = 1; // cos of where we look at + this.adjusted_sin_direction = 0; // sin of where we look at + this.current_segment = null; // which segment is closest + this.reaching = null; // which waypoint are we reaching ? + this.distance_to_next_point = null; // how far are we from next point ? + this.projected_point = null; + + if (this.path !== null) { + let r = [0]; + // let's do a reversed prefix computations on all distances: + // loop on all segments in reversed order + let previous_point = null; + for (let i = this.path.len - 1; i >= 0; i--) { + let point = this.path.point(i); + if (previous_point !== null) { + r.unshift(r[0] + point.distance(previous_point)); + } + previous_point = point; + } + this.remaining_distances = r; // how much distance remains at start of each segment } - this.on_path = !lost; - } - - this.current_segment = next_segment; - - // check if we are nearing the next point on our path and alert the user - let next_point = this.current_segment + (1 - orientation); - this.distance_to_next_point = Math.ceil( - this.position.distance(this.path.point(next_point)) - ); - - // disable gps when far from next point and locked - // if (Bangle.isLocked() && !settings.keep_gps_alive) { - // let time_to_next_point = - // (this.distance_to_next_point * 3.6) / settings.max_speed; - // if (time_to_next_point > 60) { - // Bangle.setGPSPower(false, "gipy"); - // setTimeout(function () { - // Bangle.setGPSPower(true, "gipy"); - // }, time_to_next_point); - // } - // } - if (this.reaching != next_point && this.distance_to_next_point <= 100) { - this.reaching = next_point; - let reaching_waypoint = this.path.is_waypoint(next_point); - if (reaching_waypoint) { - Bangle.buzz(); - setTimeout(() => Bangle.buzz(), 500); - setTimeout(() => Bangle.buzz(), 1000); - setTimeout(() => Bangle.buzz(), 1500); - if (Bangle.isLocked()) { - Bangle.setLocked(false); - } + this.starting_time = null; // time we start + this.advanced_distance = 0.0; + this.gps_coordinates_counter = 0; // how many coordinates did we receive + this.old_points = []; // record previous points but only when enough distance between them + this.old_times = []; // the corresponding times + } + invalidate_caches() { + for (let i = 0; i < this.maps.length; i++) { + this.maps[i].invalidate_caches(); + } + if (this.interests !== null) { + this.interests.invalidate_caches(); } - } } + new_position_reached(position) { + // we try to figure out direction by looking at previous points + // instead of the gps course which is not very nice. - // abort most frames if locked - if (Bangle.isLocked() && this.gps_coordinates_counter % 5 != 0) { - return; - } + let now = getTime(); - // re-display - this.display(); - } - display_direction() { - //TODO: go towards point on path at 20 meter - if (this.current_segment === null) { - return; - } - let next_point = this.path.point(this.current_segment + (1 - go_backwards)); - - let distance_to_next_point = Math.ceil( - this.projected_point.distance(next_point) - ); - let towards; - if (distance_to_next_point < 20) { - towards = this.path.point(this.current_segment + 2 * (1 - go_backwards)); - } else { - towards = next_point; - } - let diff = towards.minus(this.projected_point); - direction = Math.atan2(diff.lat, diff.lon); - - let full_angle = direction - this.angle; - // let c = towards.coordinates(p, this.adjusted_cos_direction, this.adjusted_sin_direction, this.scale_factor); - // g.setColor(1,0,1).fillCircle(c[0], c[1], 5); - - let scale; - if (zoomed) { - scale = this.scale_factor; - } else { - scale = this.scale_factor / 2; - } - - c = this.projected_point.coordinates( - this.displayed_position, - this.adjusted_cos_direction, - this.adjusted_sin_direction, - scale - ); - - let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2); - let cos2 = Math.cos(full_angle + Math.PI / 2); - let cos3 = Math.cos(full_angle - 0.6 + Math.PI / 2); - let sin1 = Math.sin(-full_angle - 0.6 - Math.PI / 2); - let sin2 = Math.sin(-full_angle - Math.PI / 2); - let sin3 = Math.sin(-full_angle + 0.6 - Math.PI / 2); - g.setColor(0, 1, 0).fillPoly([ - c[0] + cos1 * 15, - c[1] + sin1 * 15, - c[0] + cos2 * 20, - c[1] + sin2 * 20, - c[0] + cos3 * 15, - c[1] + sin3 * 15, - c[0] + cos3 * 10, - c[1] + sin3 * 10, - c[0] + cos2 * 15, - c[1] + sin2 * 15, - c[0] + cos1 * 10, - c[1] + sin1 * 10, - ]); - } - remaining_distance() { - let remaining_in_correct_orientation = - this.remaining_distances[this.current_segment + 1] + - this.position.distance(this.path.point(this.current_segment + 1)); - - if (go_backwards) { - return this.remaining_distances[0] - remaining_in_correct_orientation; - } else { - return remaining_in_correct_orientation; - } - } - // check if we are lost (too far from segment we think we are on) - // if we are adjust scale so that path will still be displayed. - // we do the scale adjustment here to avoid recomputations later on. - is_lost(segment) { - let projection = this.displayed_position.closest_segment_point( - this.path.point(segment), - this.path.point(segment + 1) - ); - this.projected_point = projection; // save this info for display - let distance_to_projection = this.displayed_position.distance(projection); - if (distance_to_projection > settings.lost_distance) { - return true; - } else { - return false; - } - } - display() { - if (displaying || in_menu) { - return; // don't draw on drawings - } - displaying = true; - g.clear(); - let scale_factor = this.scale_factor; - if (!zoomed) { - scale_factor /= 2; - } - - // start_profiling(); - for (let i = 0; i < this.maps.length; i++) { - this.maps[i].display( - this.displayed_position.lon, - this.displayed_position.lat, - scale_factor, - this.adjusted_cos_direction, - this.adjusted_sin_direction - ); - } - // end_profiling("map"); - if (this.interests !== null) { - this.interests.display( - this.displayed_position.lon, - this.displayed_position.lat, - scale_factor, - this.adjusted_cos_direction, - this.adjusted_sin_direction - ); - } - if (this.position !== null) { - this.display_path(); - } - - this.display_direction(); - this.display_stats(); - Bangle.drawWidgets(); - displaying = false; - } - display_stats() { - let now = new Date(); - let minutes = now.getMinutes().toString(); - if (minutes.length < 2) { - minutes = "0" + minutes; - } - let hours = now.getHours().toString(); - - // display the clock - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString(hours + ":" + minutes, 0, 24); - - let approximate_speed; - // display speed (avg and instant) - if (this.old_times.length > 0) { - let point_time = this.old_times[this.old_times.length - 1]; - let done_in = point_time - this.starting_time; - approximate_speed = Math.round( - (this.advanced_distance * 3.6) / done_in - ); - let approximate_instant_speed = Math.round(this.instant_speed * 3.6); - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .drawString( - "" + - approximate_speed + - "km/h", - 0, - g.getHeight() - 15 - ); - - g.setFont("6x8:3") - .setFontAlign(1, -1, 0) - .drawString( - ""+approximate_instant_speed, - g.getWidth(), - g.getHeight() - 22 - ); - } - - if (this.path === null || this.position === null) { - return; - } - - let remaining_distance = this.remaining_distance(); - let rounded_distance = Math.round(remaining_distance / 100) / 10; - let total = Math.round(this.remaining_distances[0] / 100) / 10; - // now, distance to next point in meters - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString( - "" + this.distance_to_next_point + "m", - 0, - g.getHeight() - 49 - ); - - let forward_eta = compute_eta( - now.getHours(), - now.getMinutes(), - approximate_speed, - remaining_distance / 1000 - ); - - // now display ETA - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(g.theme.fg) - .drawString(forward_eta, 0, 42); - - // display distance on path - g.setFont("6x8:2").drawString( - "" + rounded_distance + "/" + total, - 0, - g.getHeight() - 32 - ); - - // display various indicators - if (this.distance_to_next_point <= 100) { - if (this.path.is_waypoint(this.reaching)) { - g.setColor(0.0, 1.0, 0.0) - .setFont("6x15") - .drawString("turn", g.getWidth() - 50, 30); - } - } - if (!this.on_path) { - g.setColor(1.0, 0.0, 0.0) - .setFont("6x15") - .drawString("lost", g.getWidth() - 55, 35); - } - } - display_path() { - // don't display all segments, only those neighbouring current segment - // this is most likely to be the correct display - // while lowering the cost a lot - // - // note that all code is inlined here to speed things up - let cos = this.adjusted_cos_direction; - let sin = this.adjusted_sin_direction; - let displayed_x = this.displayed_position.lon; - let displayed_y = this.displayed_position.lat; - let width = g.getWidth(); - let height = g.getHeight(); - let half_width = width / 2; - let half_height = height / 2 + Y_OFFSET; - let scale_factor = this.scale_factor; - if (!zoomed) { - scale_factor /= 2; - } - - if (this.path !== null) { - // compute coordinate for projection on path - let tx = (this.projected_point.lon - displayed_x) * scale_factor; - let ty = (this.projected_point.lat - displayed_y) * scale_factor; - let rotated_x = tx * cos - ty * sin; - let rotated_y = tx * sin + ty * cos; - let projected_x = half_width - Math.round(rotated_x); // x is inverted - let projected_y = half_height + Math.round(rotated_y); - - // display direction to next point if lost - if (!this.on_path) { - let next_point = this.path.point(this.current_segment + 1); - let previous_point = this.path.point(this.current_segment); - let nearest_point; - if ( - previous_point.fake_distance(this.position) < - next_point.fake_distance(this.position) - ) { - nearest_point = previous_point; + if (this.old_points.length == 0) { + this.gps_coordinates_counter += 1; + this.old_points.push(position); + this.old_times.push(now); + return null; } else { - nearest_point = next_point; + let previous_point = this.old_points[this.old_points.length - 1]; + let distance_to_previous = previous_point.distance(position); + // gps signal is noisy but rarely above 5 meters + if (distance_to_previous < 5) { + return null; + } } - let tx = (nearest_point.lon - displayed_x) * scale_factor; - let ty = (nearest_point.lat - displayed_y) * scale_factor; - let rotated_x = tx * cos - ty * sin; - let rotated_y = tx * sin + ty * cos; - let x = half_width - Math.round(rotated_x); // x is inverted - let y = half_height + Math.round(rotated_y); - g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y); - } + this.gps_coordinates_counter += 1; + this.old_points.push(position); + this.old_times.push(now); - // display current-segment's projection - g.setColor(0, 0, 0); - g.fillCircle(projected_x, projected_y, 4); + let oldest_point = this.old_points[0]; + let distance_to_oldest = oldest_point.distance(position); + + // every 3 points we count the distance + if (this.gps_coordinates_counter % 3 == 0) { + if (distance_to_oldest < 150.0) { + // to avoid gps glitches + this.advanced_distance += distance_to_oldest; + } + } + + this.instant_speed = distance_to_oldest / (now - this.old_times[0]); + + if (this.old_points.length == 4) { + this.old_points.shift(); + this.old_times.shift(); + } + // let's just take angle of segment between newest point and a point a bit before + let previous_index = this.old_points.length - 3; + if (previous_index < 0) { + previous_index = 0; + } + let diff = position.minus(this.old_points[previous_index]); + let angle = Math.atan2(diff.lat, diff.lon); + return angle; } + update_position(new_position, maybe_direction, timestamp) { + let direction = this.new_position_reached(new_position); + if (direction === null) { + if (maybe_direction === null) { + return; + } else { + direction = maybe_direction; + } + } + if (in_menu) { + return; + } - // now display ourselves - g.setColor(0, 0, 0); - g.fillCircle(half_width, half_height, 5); - } + this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); + this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0); + this.angle = direction; + let cos_direction = Math.cos(direction); + let sin_direction = Math.sin(direction); + this.position = new_position; + + // we will display position of where we'll be at in a few seconds + // and not where we currently are. + // this is because the display has more than 1sec duration. + this.displayed_position = new Point( + new_position.lon + cos_direction * this.instant_speed * 0.00001, + new_position.lat + sin_direction * this.instant_speed * 0.00001 + ); + + // abort if we are late + // if (timestamp !== null) { + // let elapsed = Date.now() - timestamp; + // if (elapsed > 1000) { + // console.log("we are late"); + // return; + // } + // console.log("we are not late"); + // } + + if (this.path !== null) { + // detect segment we are on now + let res = this.path.nearest_segment( + this.displayed_position, + Math.max(0, this.current_segment - 1), + Math.min(this.current_segment + 2, this.path.len - 1), + cos_direction, + sin_direction + ); + let orientation = res[0]; + let next_segment = res[1]; + + if (this.is_lost(next_segment)) { + // start_profiling(); + // it did not work, try anywhere + res = this.path.nearest_segment( + this.displayed_position, + 0, + this.path.len - 1, + cos_direction, + sin_direction + ); + orientation = res[0]; + next_segment = res[1]; + // end_profiling("repositioning"); + } + // now check if we strayed away from path or back to it + let lost = this.is_lost(next_segment); + if (this.on_path == lost) { + // if status changes + if (lost) { + Bangle.buzz(); // we lost path + setTimeout(() => Bangle.buzz(), 500); + setTimeout(() => Bangle.buzz(), 1000); + setTimeout(() => Bangle.buzz(), 1500); + } + this.on_path = !lost; + } + + this.current_segment = next_segment; + + // check if we are nearing the next point on our path and alert the user + let next_point = this.current_segment + (1 - orientation); + this.distance_to_next_point = Math.ceil( + this.position.distance(this.path.point(next_point)) + ); + + // disable gps when far from next point and locked + // if (Bangle.isLocked() && !settings.keep_gps_alive) { + // let time_to_next_point = + // (this.distance_to_next_point * 3.6) / settings.max_speed; + // if (time_to_next_point > 60) { + // Bangle.setGPSPower(false, "gipy"); + // setTimeout(function () { + // Bangle.setGPSPower(true, "gipy"); + // }, time_to_next_point); + // } + // } + if (this.reaching != next_point && this.distance_to_next_point <= 100) { + this.reaching = next_point; + let reaching_waypoint = this.path.is_waypoint(next_point); + if (reaching_waypoint) { + if (settings.buzz_on_turns) { + Bangle.buzz(); + setTimeout(() => Bangle.buzz(), 500); + setTimeout(() => Bangle.buzz(), 1000); + setTimeout(() => Bangle.buzz(), 1500); + } + if (!Bangle.isLCDOn()) { + Bangle.setLCDPower(true); + Bangle.setLocked(false); + } + } + } + } + + // abort most frames if locked + if (Bangle.isLocked() && this.gps_coordinates_counter % 5 != 0) { + return; + } + + // re-display + this.display(); + } + display_direction() { + //TODO: go towards point on path at 20 meter + if (this.current_segment === null) { + return; + } + let next_point = this.path.point(this.current_segment + (1 - go_backwards)); + + let distance_to_next_point = Math.ceil( + this.projected_point.distance(next_point) + ); + let towards; + if (distance_to_next_point < 20) { + towards = this.path.point(this.current_segment + 2 * (1 - go_backwards)); + } else { + towards = next_point; + } + let diff = towards.minus(this.projected_point); + direction = Math.atan2(diff.lat, diff.lon); + + let full_angle = direction - this.angle; + // let c = towards.coordinates(p, this.adjusted_cos_direction, this.adjusted_sin_direction, this.scale_factor); + // g.setColor(1,0,1).fillCircle(c[0], c[1], 5); + + let scale; + if (zoomed) { + scale = this.scale_factor; + } else { + scale = this.scale_factor / 2; + } + + c = this.projected_point.coordinates( + this.displayed_position, + this.adjusted_cos_direction, + this.adjusted_sin_direction, + scale + ); + + let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2); + let cos2 = Math.cos(full_angle + Math.PI / 2); + let cos3 = Math.cos(full_angle - 0.6 + Math.PI / 2); + let sin1 = Math.sin(-full_angle - 0.6 - Math.PI / 2); + let sin2 = Math.sin(-full_angle - Math.PI / 2); + let sin3 = Math.sin(-full_angle + 0.6 - Math.PI / 2); + g.setColor(0, 1, 0).fillPoly([ + c[0] + cos1 * 15, + c[1] + sin1 * 15, + c[0] + cos2 * 20, + c[1] + sin2 * 20, + c[0] + cos3 * 15, + c[1] + sin3 * 15, + c[0] + cos3 * 10, + c[1] + sin3 * 10, + c[0] + cos2 * 15, + c[1] + sin2 * 15, + c[0] + cos1 * 10, + c[1] + sin1 * 10, + ]); + } + remaining_distance() { + let remaining_in_correct_orientation = + this.remaining_distances[this.current_segment + 1] + + this.position.distance(this.path.point(this.current_segment + 1)); + + if (go_backwards) { + return this.remaining_distances[0] - remaining_in_correct_orientation; + } else { + return remaining_in_correct_orientation; + } + } + // check if we are lost (too far from segment we think we are on) + // if we are adjust scale so that path will still be displayed. + // we do the scale adjustment here to avoid recomputations later on. + is_lost(segment) { + let projection = this.displayed_position.closest_segment_point( + this.path.point(segment), + this.path.point(segment + 1) + ); + this.projected_point = projection; // save this info for display + let distance_to_projection = this.displayed_position.distance(projection); + if (distance_to_projection > settings.lost_distance) { + return true; + } else { + return false; + } + } + display() { + if (displaying || in_menu) { + return; // don't draw on drawings + } + displaying = true; + g.clear(); + let scale_factor = this.scale_factor; + if (!zoomed) { + scale_factor /= 2; + } + + // start_profiling(); + for (let i = 0; i < this.maps.length; i++) { + this.maps[i].display( + this.displayed_position.lon, + this.displayed_position.lat, + scale_factor, + this.adjusted_cos_direction, + this.adjusted_sin_direction + ); + } + // end_profiling("map"); + if (this.interests !== null) { + this.interests.display( + this.displayed_position.lon, + this.displayed_position.lat, + scale_factor, + this.adjusted_cos_direction, + this.adjusted_sin_direction + ); + } + if (this.position !== null) { + this.display_path(); + } + + this.display_direction(); + this.display_stats(); + Bangle.drawWidgets(); + displaying = false; + } + display_stats() { + let now = new Date(); + let minutes = now.getMinutes().toString(); + if (minutes.length < 2) { + minutes = "0" + minutes; + } + let hours = now.getHours().toString(); + + // display the clock + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(hours + ":" + minutes, 0, 24); + + let approximate_speed; + // display speed (avg and instant) + if (this.old_times.length > 0) { + let point_time = this.old_times[this.old_times.length - 1]; + let done_in = point_time - this.starting_time; + approximate_speed = Math.round( + (this.advanced_distance * 3.6) / done_in + ); + let approximate_instant_speed = Math.round(this.instant_speed * 3.6); + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .drawString( + "" + + approximate_speed + + "km/h", + 0, + g.getHeight() - 15 + ); + + g.setFont("6x8:3") + .setFontAlign(1, -1, 0) + .drawString( + "" + approximate_instant_speed, + g.getWidth(), + g.getHeight() - 22 + ); + } + + if (this.path === null || this.position === null) { + return; + } + + let remaining_distance = this.remaining_distance(); + let rounded_distance = Math.round(remaining_distance / 100) / 10; + let total = Math.round(this.remaining_distances[0] / 100) / 10; + // now, distance to next point in meters + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString( + "" + this.distance_to_next_point + "m", + 0, + g.getHeight() - 49 + ); + + let forward_eta = compute_eta( + now.getHours(), + now.getMinutes(), + approximate_speed, + remaining_distance / 1000 + ); + + // now display ETA + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(forward_eta, 0, 42); + + // display distance on path + g.setFont("6x8:2").drawString( + "" + rounded_distance + "/" + total, + 0, + g.getHeight() - 32 + ); + + // display various indicators + if (this.distance_to_next_point <= 100) { + if (this.path.is_waypoint(this.reaching)) { + g.setColor(0.0, 1.0, 0.0) + .setFont("6x15") + .drawString("turn", g.getWidth() - 50, 30); + } + } + if (!this.on_path) { + g.setColor(1.0, 0.0, 0.0) + .setFont("6x15") + .drawString("lost", g.getWidth() - 55, 35); + } + } + display_path() { + // don't display all segments, only those neighbouring current segment + // this is most likely to be the correct display + // while lowering the cost a lot + // + // note that all code is inlined here to speed things up + let cos = this.adjusted_cos_direction; + let sin = this.adjusted_sin_direction; + let displayed_x = this.displayed_position.lon; + let displayed_y = this.displayed_position.lat; + let width = g.getWidth(); + let height = g.getHeight(); + let half_width = width / 2; + let half_height = height / 2 + Y_OFFSET; + let scale_factor = this.scale_factor; + if (!zoomed) { + scale_factor /= 2; + } + + if (this.path !== null) { + // compute coordinate for projection on path + let tx = (this.projected_point.lon - displayed_x) * scale_factor; + let ty = (this.projected_point.lat - displayed_y) * scale_factor; + let rotated_x = tx * cos - ty * sin; + let rotated_y = tx * sin + ty * cos; + let projected_x = half_width - Math.round(rotated_x); // x is inverted + let projected_y = half_height + Math.round(rotated_y); + + // display direction to next point if lost + if (!this.on_path) { + let next_point = this.path.point(this.current_segment + 1); + let previous_point = this.path.point(this.current_segment); + let nearest_point; + if ( + previous_point.fake_distance(this.position) < + next_point.fake_distance(this.position) + ) { + nearest_point = previous_point; + } else { + nearest_point = next_point; + } + let tx = (nearest_point.lon - displayed_x) * scale_factor; + let ty = (nearest_point.lat - displayed_y) * scale_factor; + let rotated_x = tx * cos - ty * sin; + let rotated_y = tx * sin + ty * cos; + let x = half_width - Math.round(rotated_x); // x is inverted + let y = half_height + Math.round(rotated_y); + g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y); + } + + // display current-segment's projection + g.setColor(0, 0, 0); + g.fillCircle(projected_x, projected_y, 4); + } + + // now display ourselves + g.setColor(0, 0, 0); + g.fillCircle(half_width, half_height, 5); + } } function load_gps(filename) { - // let's display splash screen while loading file - g.clear(); - g.drawImage(splashscreen, 0, 0); - g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) - .setColor(0xf800) - .drawString(filename, 0, g.getHeight() - 30); - g.flip(); + // let's display splash screen while loading file + g.clear(); + g.drawImage(splashscreen, 0, 0); + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(0xf800) + .drawString(filename, 0, g.getHeight() - 30); + g.flip(); - let buffer = s.readArrayBuffer(filename); - let file_size = buffer.length; - let offset = 0; + let buffer = s.readArrayBuffer(filename); + let file_size = buffer.length; + let offset = 0; - let path = null; - let maps = []; - let interests = null; - while (offset < file_size) { - let block_type = Uint8Array(buffer, offset, 1)[0]; - offset += 1; - if (block_type == 0) { - // it's a map - console.log("loading map"); - let res = new Map(buffer, offset, filename); - let map = res[0]; - offset = res[1]; - maps.push(map); - } else if (block_type == 2) { - console.log("loading path"); - let res = new Path(buffer, offset); - path = res[0]; - offset = res[1]; - } else if (block_type == 3) { - console.log("loading interests"); - let res = new Interests(buffer, offset); - interests = res[0]; - offset = res[1]; - } else { - console.log("todo : block type", block_type); + let path = null; + let maps = []; + let interests = null; + while (offset < file_size) { + let block_type = Uint8Array(buffer, offset, 1)[0]; + offset += 1; + if (block_type == 0) { + // it's a map + console.log("loading map"); + let res = new Map(buffer, offset, filename); + let map = res[0]; + offset = res[1]; + maps.push(map); + } else if (block_type == 2) { + console.log("loading path"); + let res = new Path(buffer, offset); + path = res[0]; + offset = res[1]; + } else if (block_type == 3) { + console.log("loading interests"); + let res = new Interests(buffer, offset); + interests = res[0]; + offset = res[1]; + } else { + console.log("todo : block type", block_type); + } } - } - // checksum file size - if (offset != file_size) { - console.log("invalid file size", file_size, "expected", offset); - let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; - E.showAlert(msg).then(function () { - E.showAlert(); - start_gipy(path, maps, interests); - }); - } else { - start_gipy(path, maps, interests); - } + // checksum file size + if (offset != file_size) { + console.log("invalid file size", file_size, "expected", offset); + let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; + E.showAlert(msg).then(function() { + E.showAlert(); + start_gipy(path, maps, interests); + }); + } else { + start_gipy(path, maps, interests); + } } class Path { - constructor(buffer, offset) { - // let p = Uint16Array(buffer, offset, 1); - // console.log(p); - let points_number = Uint16Array(buffer, offset, 1)[0]; - offset += 2; + constructor(buffer, offset) { + // let p = Uint16Array(buffer, offset, 1); + // console.log(p); + let points_number = Uint16Array(buffer, offset, 1)[0]; + offset += 2; - // path points - this.points = Float64Array(buffer, offset, points_number * 2); - offset += 8 * points_number * 2; + // path points + this.points = Float64Array(buffer, offset, points_number * 2); + offset += 8 * points_number * 2; - // path waypoints - let waypoints_len = Math.ceil(points_number / 8.0); - this.waypoints = Uint8Array(buffer, offset, waypoints_len); - offset += waypoints_len; + // path waypoints + let waypoints_len = Math.ceil(points_number / 8.0); + this.waypoints = Uint8Array(buffer, offset, waypoints_len); + offset += waypoints_len; - return [this, offset]; - } - - is_waypoint(point_index) { - let i = Math.floor(point_index / 8); - let subindex = point_index % 8; - let r = this.waypoints[i] & (1 << subindex); - return r != 0; - } - - // return point at given index - point(index) { - let lon = this.points[2 * index]; - let lat = this.points[2 * index + 1]; - return new Point(lon, lat); - } - - // return index of segment which is nearest from point. - // we need a direction because we need there is an ambiguity - // for overlapping segments which are taken once to go and once to come back. - // (in the other direction). - nearest_segment(point, start, end, cos_direction, sin_direction) { - // we are going to compute two min distances, one for each direction. - let indices = [0, 0]; - let mins = [Number.MAX_VALUE, Number.MAX_VALUE]; - - let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]); - for (let i = start + 1; i < end + 1; i++) { - let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]); - - let closest_point = point.closest_segment_point(p1, p2); - let distance = point.length_squared(closest_point); - - let dot = - cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat); - let orientation = +(dot < 0); // index 0 is good orientation - if (distance <= mins[orientation]) { - mins[orientation] = distance; - indices[orientation] = i - 1; - } - - p1 = p2; + return [this, offset]; } - // by default correct orientation (0) wins - // but if other one is really closer, return other one - if (mins[1] < mins[0] / 100.0) { - return [1, indices[1]]; - } else { - return [0, indices[0]]; + is_waypoint(point_index) { + let i = Math.floor(point_index / 8); + let subindex = point_index % 8; + let r = this.waypoints[i] & (1 << subindex); + return r != 0; + } + + // return point at given index + point(index) { + let lon = this.points[2 * index]; + let lat = this.points[2 * index + 1]; + return new Point(lon, lat); + } + + // return index of segment which is nearest from point. + // we need a direction because we need there is an ambiguity + // for overlapping segments which are taken once to go and once to come back. + // (in the other direction). + nearest_segment(point, start, end, cos_direction, sin_direction) { + // we are going to compute two min distances, one for each direction. + let indices = [0, 0]; + let mins = [Number.MAX_VALUE, Number.MAX_VALUE]; + + let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]); + for (let i = start + 1; i < end + 1; i++) { + let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]); + + let closest_point = point.closest_segment_point(p1, p2); + let distance = point.length_squared(closest_point); + + let dot = + cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat); + let orientation = +(dot < 0); // index 0 is good orientation + if (distance <= mins[orientation]) { + mins[orientation] = distance; + indices[orientation] = i - 1; + } + + p1 = p2; + } + + // by default correct orientation (0) wins + // but if other one is really closer, return other one + if (mins[1] < mins[0] / 100.0) { + return [1, indices[1]]; + } else { + return [0, indices[0]]; + } + } + get len() { + return this.points.length / 2; } - } - get len() { - return this.points.length / 2; - } } class Point { - constructor(lon, lat) { - this.lon = lon; - this.lat = lat; - } - coordinates(current_position, cos_direction, sin_direction, scale_factor) { - let translated = this.minus(current_position).times(scale_factor); - let rotated_x = - translated.lon * cos_direction - translated.lat * sin_direction; - let rotated_y = - translated.lon * sin_direction + translated.lat * cos_direction; - return [ - g.getWidth() / 2 - Math.round(rotated_x), // x is inverted - g.getHeight() / 2 + Math.round(rotated_y) + Y_OFFSET, - ]; - } - minus(other_point) { - let xdiff = this.lon - other_point.lon; - let ydiff = this.lat - other_point.lat; - return new Point(xdiff, ydiff); - } - plus(other_point) { - return new Point(this.lon + other_point.lon, this.lat + other_point.lat); - } - length_squared(other_point) { - let londiff = this.lon - other_point.lon; - let latdiff = this.lat - other_point.lat; - return londiff * londiff + latdiff * latdiff; - } - times(scalar) { - return new Point(this.lon * scalar, this.lat * scalar); - } - dot(other_point) { - return this.lon * other_point.lon + this.lat * other_point.lat; - } - distance(other_point) { - //see https://www.movable-type.co.uk/scripts/latlong.html - const R = 6371e3; // metres - const phi1 = (this.lat * Math.PI) / 180; - const phi2 = (other_point.lat * Math.PI) / 180; - const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180; - const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180; - - const a = - Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) + - Math.cos(phi1) * - Math.cos(phi2) * - Math.sin(deltalambda / 2) * - Math.sin(deltalambda / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return R * c; // in meters - } - fake_distance(other_point) { - return Math.sqrt(this.length_squared(other_point)); - } - // return closest point from 'this' on [v,w] segment. - // since this function is critical we inline all code here. - closest_segment_point(v, w) { - // from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment - // Return minimum distance between line segment vw and point p - let segment_londiff = w.lon - v.lon; - let segment_latdiff = w.lat - v.lat; - let l2 = - segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt - if (l2 == 0.0) { - return v; // v == w case + constructor(lon, lat) { + this.lon = lon; + this.lat = lat; } - // Consider the line extending the segment, parameterized as v + t (w - v). - // We find projection of point p onto the line. - // It falls where t = [(p-v) . (w-v)] / |w-v|^2 - // We clamp t from [0,1] to handle points outside the segment vw. - - // let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below - let start_londiff = this.lon - v.lon; - let start_latdiff = this.lat - v.lat; - let t = - (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2; - if (t < 0) { - t = 0; - } else { - if (t > 1) { - t = 1; - } + coordinates(current_position, cos_direction, sin_direction, scale_factor) { + let translated = this.minus(current_position).times(scale_factor); + let rotated_x = + translated.lon * cos_direction - translated.lat * sin_direction; + let rotated_y = + translated.lon * sin_direction + translated.lat * cos_direction; + return [ + g.getWidth() / 2 - Math.round(rotated_x), // x is inverted + g.getHeight() / 2 + Math.round(rotated_y) + Y_OFFSET, + ]; + } + minus(other_point) { + let xdiff = this.lon - other_point.lon; + let ydiff = this.lat - other_point.lat; + return new Point(xdiff, ydiff); + } + plus(other_point) { + return new Point(this.lon + other_point.lon, this.lat + other_point.lat); + } + length_squared(other_point) { + let londiff = this.lon - other_point.lon; + let latdiff = this.lat - other_point.lat; + return londiff * londiff + latdiff * latdiff; + } + times(scalar) { + return new Point(this.lon * scalar, this.lat * scalar); + } + dot(other_point) { + return this.lon * other_point.lon + this.lat * other_point.lat; + } + distance(other_point) { + //see https://www.movable-type.co.uk/scripts/latlong.html + const R = 6371e3; // metres + const phi1 = (this.lat * Math.PI) / 180; + const phi2 = (other_point.lat * Math.PI) / 180; + const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180; + const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180; + + const a = + Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) + + Math.cos(phi1) * + Math.cos(phi2) * + Math.sin(deltalambda / 2) * + Math.sin(deltalambda / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return R * c; // in meters + } + fake_distance(other_point) { + return Math.sqrt(this.length_squared(other_point)); + } + // return closest point from 'this' on [v,w] segment. + // since this function is critical we inline all code here. + closest_segment_point(v, w) { + // from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment + // Return minimum distance between line segment vw and point p + let segment_londiff = w.lon - v.lon; + let segment_latdiff = w.lat - v.lat; + let l2 = + segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt + if (l2 == 0.0) { + return v; // v == w case + } + // Consider the line extending the segment, parameterized as v + t (w - v). + // We find projection of point p onto the line. + // It falls where t = [(p-v) . (w-v)] / |w-v|^2 + // We clamp t from [0,1] to handle points outside the segment vw. + + // let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below + let start_londiff = this.lon - v.lon; + let start_latdiff = this.lat - v.lat; + let t = + (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2; + if (t < 0) { + t = 0; + } else { + if (t > 1) { + t = 1; + } + } + let lon = v.lon + segment_londiff * t; + let lat = v.lat + segment_latdiff * t; + return new Point(lon, lat); } - let lon = v.lon + segment_londiff * t; - let lat = v.lat + segment_latdiff * t; - return new Point(lon, lat); - } } let fake_gps_point = 0; + function simulate_gps(status) { - if (status.path === null) { - let map = status.maps[0]; - let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); - let p2 = new Point( - map.start_coordinates[0] + map.side * map.grid_size[0], - map.start_coordinates[1] + map.side * map.grid_size[1] - ); - let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); - if (fake_gps_point < 1) { - fake_gps_point += 0.01; - } - status.update_position(pos, null, null); - } else { - if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { - return; - } - let point_index = Math.floor(fake_gps_point); - if (point_index >= status.path.len / 2 - 1) { - return; - } - let p1 = status.path.point(2 * point_index); // use these to approximately follow path - let p2 = status.path.point(2 * (point_index + 1)); - //let p1 = status.path.point(point_index); // use these to strictly follow path - //let p2 = status.path.point(point_index + 1); - - let alpha = fake_gps_point - point_index; - let pos = p1.times(1 - alpha).plus(p2.times(alpha)); - - if (go_backwards) { - fake_gps_point -= 0.05; // advance simulation + if (status.path === null) { + let map = status.maps[0]; + let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); + let p2 = new Point( + map.start_coordinates[0] + map.side * map.grid_size[0], + map.start_coordinates[1] + map.side * map.grid_size[1] + ); + let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); + if (fake_gps_point < 1) { + fake_gps_point += 0.01; + } + status.update_position(pos, null, null); } else { - fake_gps_point += 0.05; // advance simulation + if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { + return; + } + let point_index = Math.floor(fake_gps_point); + if (point_index >= status.path.len / 2 - 1) { + return; + } + let p1 = status.path.point(2 * point_index); // use these to approximately follow path + let p2 = status.path.point(2 * (point_index + 1)); + //let p1 = status.path.point(point_index); // use these to strictly follow path + //let p2 = status.path.point(point_index + 1); + + let alpha = fake_gps_point - point_index; + let pos = p1.times(1 - alpha).plus(p2.times(alpha)); + + if (go_backwards) { + fake_gps_point -= 0.05; // advance simulation + } else { + fake_gps_point += 0.05; // advance simulation + } + status.update_position(pos, null, null); } - status.update_position(pos, null, null); - } } function drawMenu() { - const menu = { - "": { title: "choose trace" }, - }; - var files = s.list(".gps"); - for (var i = 0; i < files.length; ++i) { - menu[files[i]] = start.bind(null, files[i]); - } - menu["Exit"] = function () { - load(); - }; - E.showMenu(menu); + const menu = { + "": { + title: "choose trace" + }, + }; + var files = s.list(".gps"); + for (var i = 0; i < files.length; ++i) { + menu[files[i]] = start.bind(null, files[i]); + } + menu["Exit"] = function() { + load(); + }; + E.showMenu(menu); } function start(fn) { - E.showMenu(); - console.log("loading", fn); + E.showMenu(); + console.log("loading", fn); - load_gps(fn); + load_gps(fn); } function start_gipy(path, maps, interests) { - console.log("starting"); - status = new Status(path, maps, interests); + console.log("starting"); - setWatch( - function () { - if (in_menu) { - return; - } - in_menu = true; - const menu = { - "": { title: "choose action" }, - "Go Backward": { - value: go_backwards, - format: (v) => (v ? "On" : "Off"), - onchange: (v) => { - go_backwards = v; - }, - }, - Zoom: { - value: zoomed, - format: (v) => (v ? "In" : "Out"), - onchange: (v) => { - status.invalidate_caches(); - zoomed = v; - }, - }, - "back to map": function () { - in_menu = false; - E.showMenu(); - g.clear(); - g.flip(); - if (status !== null) { - status.display(); - } - }, - }; - E.showMenu(menu); - }, - BTN1, - { repeat: true } - ); - - - if (status.path !== null) { - let start = status.path.point(0); - status.displayed_position = start; - } else { - let first_map = maps[0]; - status.displayed_position = new Point( - first_map.start_coordinates[0] + - (first_map.side * first_map.grid_size[0]) / 2, - first_map.start_coordinates[1] + - (first_map.side * first_map.grid_size[1]) / 2); - } - status.display(); - - Bangle.on("stroke", (o) => { - if (in_menu) { - return; - } - // we move display according to stroke - let first_x = o.xy[0]; - let first_y = o.xy[1]; - let last_x = o.xy[o.xy.length - 2]; - let last_y = o.xy[o.xy.length - 1]; - let xdiff = last_x - first_x; - let ydiff = last_y - first_y; - - let c = status.adjusted_cos_direction; - let s = status.adjusted_sin_direction; - let rotated_x = xdiff * c - ydiff * s; - let rotated_y = xdiff * s + ydiff * c; - status.displayed_position.lon += 1.3 * rotated_x / status.scale_factor; - status.displayed_position.lat -= 1.3 * rotated_y / status.scale_factor; - status.display(); - }); - - if (simulated) { - status.starting_time = getTime(); - // let's keep the screen on in simulations - Bangle.setLCDTimeout(0); - Bangle.setLCDPower(1); - setInterval(simulate_gps, 500, status); - } else { - Bangle.setLocked(false); - - let frame = 0; - let set_coordinates = function (data) { - frame += 1; - // 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere - let valid_coordinates = - !isNaN(data.lat) && - !isNaN(data.lon) && - (data.lat != 0.0 || data.lon != 0.0); - if (valid_coordinates) { - if (status.starting_time === null) { - status.starting_time = getTime(); - Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen - } - status.update_position(new Point(data.lon, data.lat), null, data.time); - } - let gps_status_color; - if (frame % 2 == 0 || valid_coordinates) { - gps_status_color = g.theme.bg; - } else { - gps_status_color = g.theme.fg; - } - if (!in_menu) { - g.setColor(gps_status_color) - .setFont("6x8:2") - .drawString("gps", g.getWidth() - 40, 30); - } - }; - - Bangle.setGPSPower(true, "gipy"); - Bangle.on("GPS", set_coordinates); - Bangle.on("lock", function (on) { - if (!on) { - Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking - } + Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 20000, + lcdPowerTimeout: 30000, + hrmSportMode: 2, }); - } + if (!simulated && settings.disable_bluetooth) { + NRF.sleep(); // disable bluetooth completely + } + + status = new Status(path, maps, interests); + + setWatch( + function() { + if (in_menu) { + return; + } + in_menu = true; + const menu = { + "": { + title: "choose action" + }, + "Go Backward": { + value: go_backwards, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => { + go_backwards = v; + }, + }, + Zoom: { + value: zoomed, + format: (v) => (v ? "In" : "Out"), + onchange: (v) => { + status.invalidate_caches(); + zoomed = v; + }, + }, + "back to map": function() { + in_menu = false; + E.showMenu(); + g.clear(); + g.flip(); + if (status !== null) { + status.display(); + } + }, + }; + E.showMenu(menu); + }, + BTN1, { + repeat: true + } + ); + + + if (status.path !== null) { + let start = status.path.point(0); + status.displayed_position = start; + } else { + let first_map = maps[0]; + status.displayed_position = new Point( + first_map.start_coordinates[0] + + (first_map.side * first_map.grid_size[0]) / 2, + first_map.start_coordinates[1] + + (first_map.side * first_map.grid_size[1]) / 2); + } + status.display(); + + Bangle.on("stroke", (o) => { + if (in_menu) { + return; + } + // we move display according to stroke + let first_x = o.xy[0]; + let first_y = o.xy[1]; + let last_x = o.xy[o.xy.length - 2]; + let last_y = o.xy[o.xy.length - 1]; + let xdiff = last_x - first_x; + let ydiff = last_y - first_y; + + let c = status.adjusted_cos_direction; + let s = status.adjusted_sin_direction; + let rotated_x = xdiff * c - ydiff * s; + let rotated_y = xdiff * s + ydiff * c; + status.displayed_position.lon += 1.3 * rotated_x / status.scale_factor; + status.displayed_position.lat -= 1.3 * rotated_y / status.scale_factor; + status.display(); + }); + + if (simulated) { + status.starting_time = getTime(); + // let's keep the screen on in simulations + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(1); + setInterval(simulate_gps, 500, status); + } else { + Bangle.setLocked(false); + + let frame = 0; + let set_coordinates = function(data) { + frame += 1; + // 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere + let valid_coordinates = !isNaN(data.lat) && + !isNaN(data.lon) && + (data.lat != 0.0 || data.lon != 0.0); + if (valid_coordinates) { + if (status.starting_time === null) { + status.starting_time = getTime(); + Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen + } + status.update_position(new Point(data.lon, data.lat), null, data.time); + } + let gps_status_color; + if (frame % 2 == 0 || valid_coordinates) { + gps_status_color = g.theme.bg; + } else { + gps_status_color = g.theme.fg; + } + if (!in_menu) { + g.setColor(gps_status_color) + .setFont("6x8:2") + .drawString("gps", g.getWidth() - 40, 30); + } + }; + + Bangle.setGPSPower(true, "gipy"); + Bangle.on("GPS", set_coordinates); + Bangle.on("lock", function(on) { + if (!on) { + Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking + } + }); + } } let files = s.list(".gps"); if (files.length <= 1) { - if (files.length == 0) { - load(); - } else { - start(files[0]); - } + if (files.length == 0) { + load(); + } else { + start(files[0]); + } } else { - drawMenu(); -} + drawMenu(); +} \ No newline at end of file diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js index 1f6ae0853..ecbcb267e 100644 --- a/apps/gipy/settings.js +++ b/apps/gipy/settings.js @@ -1,29 +1,48 @@ -(function (back) { - var FILE = "gipy.json"; - // Load settings - var settings = Object.assign( - { - lost_distance: 50, - }, - require("Storage").readJSON(FILE, true) || {} - ); +(function(back) { + var FILE = "gipy.json"; + // Load settings + var settings = Object.assign({ + lost_distance: 50, + buzz_on_turns: false, + disable_bluetooth: true, + }, + require("Storage").readJSON(FILE, true) || {} + ); - function writeSettings() { - require("Storage").writeJSON(FILE, settings); - } + function writeSettings() { + require("Storage").writeJSON(FILE, settings); + } - // Show the menu - E.showMenu({ - "": { title: "Gipy" }, - "< Back": () => back(), - "lost distance": { - value: 50 | settings.lost_distance, // 0| converts undefined to 0 - min: 10, - max: 500, - onchange: (v) => { - settings.max_speed = v; - writeSettings(); - }, - }, - }); + // Show the menu + E.showMenu({ + "": { + title: "Gipy" + }, + "< Back": () => back(), + "buzz on turns": { + value: !!settings.buzz_on_turns, // !! converts undefined to false + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => { + settings.buzz_on_turns = v; + writeSettings(); + } + }, + "disable bluetooth": { + value: !!settings.disable_bluetooth, // !! converts undefined to false + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => { + settings.disable_bluetooth = v; + writeSettings(); + } + }, + "lost distance": { + value: 50 | settings.lost_distance, // 0| converts undefined to 0 + min: 10, + max: 500, + onchange: (v) => { + settings.max_speed = v; + writeSettings(); + }, + }, + }); }); From 9ca06bbfca16aa315da6784bd7e6045fd362ee39 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Sat, 8 Jul 2023 11:46:38 +0200 Subject: [PATCH 05/11] gipy : lcd power saving + settings --- apps/gipy/ChangeLog | 3 ++- apps/gipy/README.md | 15 ++++++++++----- apps/gipy/TODO | 3 --- apps/gipy/app.js | 24 +++++++++++++++--------- apps/gipy/settings.js | 9 +++++++++ 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index c2e9a21b4..e8055100a 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -92,6 +92,7 @@ * Large display for instant speed * Bugfix for negative coordinates * Disable menu while the map is not loaded - * Turn screen off while idling to save battery + * Turn screen off while idling to save battery (with setting) * New setting : disable buzz on turns * New setting : turn bluetooth off to save battery + * Color change for lost direction (now purple) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index 242282dbf..d75cd5d6f 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -18,8 +18,8 @@ It provides the following features : - display the path with current position from gps - display a local map around you, downloaded from openstreetmap - detects and buzzes if you leave the path -- buzzes before sharp turns -- buzzes before waypoints +- (optional) buzzes before sharp turns +- (optional) buzzes before waypoints (for example when you need to turn in https://mapstogpx.com/) - display instant / average speed - display distance to next point @@ -51,8 +51,8 @@ Your path will be displayed in svg. ### Starting Gipy At start you will have a menu for selecting your trace (if more than one). -Choose the one you want and you will reach the splash screen where you'll wait for the gps signal. -Once you have a signal you will reach the main screen: +Choose the one you want and you will reach the splash screen where you'll wait for the map. +Once the map is loaded you will reach the main screen: ![Screenshot](legend.png) @@ -83,7 +83,7 @@ On your screen you can see: ### Lost If you stray away from path we will rescale the display to continue displaying nearby segments and -display the direction to follow as a black segment. +display the direction to follow as a purple segment. Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you are. On path it just needed to scan a few points ahead and behind. @@ -100,6 +100,10 @@ If you click the button you'll reach a menu where you can currently zoom out to Few settings for now (feel free to suggest me more) : - lost distance : at which distance from path are you considered to be lost ? +- buzz on turns : should the watch buzz when reaching a waypoint ? +- disable bluetooth : turn bluetooth off completely to try to save some power. +- power lcd off : turn lcd off after 30 seconds to save power. the watch will wake up when reaching waypoints +and when you touch the screen. ### Caveats @@ -121,5 +125,6 @@ Feel free to give me feedback : is it useful for you ? what other features would If you want to raise issues the main repository is [https://github.com/wagnerf42/BangleApps](here) and the rust code doing the actual map computations is located [https://github.com/wagnerf42/gps](here). +You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/) frederic.wagner@imag.fr diff --git a/apps/gipy/TODO b/apps/gipy/TODO index c94211c1d..0c25f7d0b 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -1,8 +1,5 @@ -+ disable backlight during day ? + put back foot only ways -+ disable bluetooth -+ disable lcd completely + try fiddling with jit + put back street names + put back shortest paths but with points cache this time and jit diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 866849efc..a39062aa9 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -20,6 +20,7 @@ var settings = Object.assign({ lost_distance: 50, buzz_on_turns: false, disable_bluetooth: true, + power_lcd_off: true, }, s.readJSON("gipy.json", true) || {} ); @@ -815,7 +816,7 @@ class Status { } // abort most frames if locked - if (Bangle.isLocked() && this.gps_coordinates_counter % 5 != 0) { + if (!Bangle.isLocked() && this.gps_coordinates_counter % 5 != 0) { return; } @@ -1087,7 +1088,7 @@ class Status { let rotated_y = tx * sin + ty * cos; let x = half_width - Math.round(rotated_x); // x is inverted let y = half_height + Math.round(rotated_y); - g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y); + g.setColor(1, 0, 1).drawLine(half_width, half_height, x, y); } // display current-segment's projection @@ -1386,12 +1387,17 @@ function start(fn) { function start_gipy(path, maps, interests) { console.log("starting"); - Bangle.setOptions({ - lockTimeout: 10000, - backlightTimeout: 20000, - lcdPowerTimeout: 30000, - hrmSportMode: 2, - }); + if (settings.power_lcd_off) { + Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 20000, + lcdPowerTimeout: 30000, + hrmSportMode: 2, + wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. + wakeOnFaceUp: false, + wakeOnTouch: true, + }); + } if (!simulated && settings.disable_bluetooth) { NRF.sleep(); // disable bluetooth completely } @@ -1531,4 +1537,4 @@ if (files.length <= 1) { } } else { drawMenu(); -} \ No newline at end of file +} diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js index ecbcb267e..37b299815 100644 --- a/apps/gipy/settings.js +++ b/apps/gipy/settings.js @@ -5,6 +5,7 @@ lost_distance: 50, buzz_on_turns: false, disable_bluetooth: true, + power_lcd_off: true, }, require("Storage").readJSON(FILE, true) || {} ); @@ -44,5 +45,13 @@ writeSettings(); }, }, + "power lcd off": { + value: !!settings.power_lcd_off, // !! converts undefined to false + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => { + settings.power_lcd_off = v; + writeSettings(); + } + }, }); }); From ed543b871634a593f21743a12b3a13be99d4b70a Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Sat, 8 Jul 2023 11:58:18 +0200 Subject: [PATCH 06/11] gipy: checkboxes in settings --- apps/gipy/settings.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js index 37b299815..9283f8ab9 100644 --- a/apps/gipy/settings.js +++ b/apps/gipy/settings.js @@ -20,17 +20,15 @@ title: "Gipy" }, "< Back": () => back(), - "buzz on turns": { - value: !!settings.buzz_on_turns, // !! converts undefined to false - format: (v) => (v ? "Yes" : "No"), + /*LANG*/"buzz on turns": { + value: settings.buzz_on_turns == true, onchange: (v) => { settings.buzz_on_turns = v; writeSettings(); } }, - "disable bluetooth": { - value: !!settings.disable_bluetooth, // !! converts undefined to false - format: (v) => (v ? "Yes" : "No"), + /*LANG*/"disable bluetooth": { + value: settings.disable_bluetooth == true, onchange: (v) => { settings.disable_bluetooth = v; writeSettings(); @@ -45,9 +43,8 @@ writeSettings(); }, }, - "power lcd off": { - value: !!settings.power_lcd_off, // !! converts undefined to false - format: (v) => (v ? "Yes" : "No"), + /*LANG*/"power lcd off": { + value: settings.power_lcd_off == true, onchange: (v) => { settings.power_lcd_off = v; writeSettings(); From 9d54ac09ec81abd2c2605d3986ed85c320a2b17d Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 11 Jul 2023 11:18:30 +0200 Subject: [PATCH 07/11] gipy: fiddling with powersaving --- apps/gipy/README.md | 6 ++- apps/gipy/TODO | 38 +++++++++++++++++++ apps/gipy/app.js | 86 +++++++++++++++++++++++++++++-------------- apps/gipy/settings.js | 18 +++++++-- 4 files changed, 116 insertions(+), 32 deletions(-) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index d75cd5d6f..fceff20c8 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -93,7 +93,7 @@ The distance to next point displayed corresponds to the length of the black segm ### Menu If you click the button you'll reach a menu where you can currently zoom out to see more of the map -(with a slower refresh rate) and reverse the path direction. +(with a slower refresh rate), reverse the path direction and disable power saving (keeping backlight on). ### Settings @@ -102,7 +102,8 @@ Few settings for now (feel free to suggest me more) : - lost distance : at which distance from path are you considered to be lost ? - buzz on turns : should the watch buzz when reaching a waypoint ? - disable bluetooth : turn bluetooth off completely to try to save some power. -- power lcd off : turn lcd off after 30 seconds to save power. the watch will wake up when reaching waypoints +- brightness : how bright should screen be ? (by default 0.5, again saving power) +- power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points and when you touch the screen. ### Caveats @@ -111,6 +112,7 @@ It is good to use but you should know : - the gps might take a long time to start initially (see the assisted gps update app). - gps signal is noisy : there is therefore a small delay for instant speed. sometimes you may jump somewhere else. +- if you adventure in gorges the gps signal will become garbage. - your gpx trace has been decimated and approximated : the **REAL PATH** might be **A FEW METERS AWAY** - sometimes the watch will tell you that you are lost but you are in fact on the path. It usually figures again the real gps position after a few minutes. It usually happens when the signal is acquired very fast. diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 0c25f7d0b..93f241e44 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -1,3 +1,41 @@ +*** thoughts on lcd power *** + +so, i tried experimenting with turning the lcd off in order to save power. + +the good news: this saves a lot. i did a 3h ride which usually depletes the battery and I still had +around two more hours to go. + +now the bad news: + +- i had to de-activate the twist detection : you cannot raise your watch to the eyes to turn it on. +that's because with twist detection on all road bumps turn the watch on constantly. +- i tried manual detection like : + +Bangle.on('accel', function(xyz) { + + if (xyz.diff > 0.4 && xyz.mag > 1 && xyz.z < -1.4) { + Bangle.setLCDPower(true); + Bangle.setLocked(false); + } + +}); + +this works nicely when you sit on a chair with a simulated gps signal but does not work so nicely when on the bike. +sometimes it is ok, sometimes you can try 10 times with no success. + +- instead i use screen touch to turn it on. that's a bother since you need two hands but well it could be worth it. +the problem is in the delay: between 1 and 5 seconds before the screen comes back on. + + +my conclusion is that: + +* we should not turn screen off unless we agree to have an unresponsive ui +* we should maybe autowake near segments ends and when lost +* we should play with backlight instead + + +************************** + + put back foot only ways + try fiddling with jit diff --git a/apps/gipy/app.js b/apps/gipy/app.js index a39062aa9..718a2c6f5 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -3,6 +3,7 @@ let displaying = false; let in_menu = false; let go_backwards = false; let zoomed = true; +let powersaving = true; let status; let interests_colors = [ @@ -18,9 +19,10 @@ let s = require("Storage"); var settings = Object.assign({ lost_distance: 50, + brightness: 0.5, buzz_on_turns: false, disable_bluetooth: true, - power_lcd_off: true, + power_lcd_off: false, }, s.readJSON("gipy.json", true) || {} ); @@ -606,6 +608,8 @@ class Interests { class Status { constructor(path, maps, interests) { this.path = path; + this.active = false; // should we have screen on + this.last_activity = getTime(); this.maps = maps; this.interests = interests; let half_screen_width = g.getWidth() / 2; @@ -644,6 +648,32 @@ class Status { this.old_points = []; // record previous points but only when enough distance between them this.old_times = []; // the corresponding times } + activate() { + this.last_activity = getTime(); + if (this.active) { + return; + } else { + this.active = true; + Bangle.setLCDBrightness(settings.brightness); + Bangle.setLocked(false); + if (settings.power_lcd_off) { + Bangle.setLCDPower(true); + } + } + } + check_activity() { + if (!this.active || !powersaving) { + return; + } + if (getTime() - this.last_activity > 30) { + this.active = false; + Bangle.setLCDBrightness(0); + Bangle.setLocked(true); + if (settings.power_lcd_off) { + Bangle.setLCDPower(false); + } + } + } invalidate_caches() { for (let i = 0; i < this.maps.length; i++) { this.maps[i].invalidate_caches(); @@ -713,6 +743,7 @@ class Status { if (in_menu) { return; } + this.check_activity(); // if we don't move or are in menu we should stay on this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0); @@ -768,6 +799,7 @@ class Status { // now check if we strayed away from path or back to it let lost = this.is_lost(next_segment); if (this.on_path == lost) { + this.activate(); // if status changes if (lost) { Bangle.buzz(); // we lost path @@ -798,6 +830,7 @@ class Status { // } // } if (this.reaching != next_point && this.distance_to_next_point <= 100) { + this.activate(); this.reaching = next_point; let reaching_waypoint = this.path.is_waypoint(next_point); if (reaching_waypoint) { @@ -807,16 +840,12 @@ class Status { setTimeout(() => Bangle.buzz(), 1000); setTimeout(() => Bangle.buzz(), 1500); } - if (!Bangle.isLCDOn()) { - Bangle.setLCDPower(true); - Bangle.setLocked(false); - } } } } - // abort most frames if locked - if (!Bangle.isLocked() && this.gps_coordinates_counter % 5 != 0) { + // abort most frames if inactive + if (!this.active && this.gps_coordinates_counter % 5 != 0) { return; } @@ -1353,9 +1382,9 @@ function simulate_gps(status) { let pos = p1.times(1 - alpha).plus(p2.times(alpha)); if (go_backwards) { - fake_gps_point -= 0.05; // advance simulation + fake_gps_point -= 0.01; // advance simulation } else { - fake_gps_point += 0.05; // advance simulation + fake_gps_point += 0.01; // advance simulation } status.update_position(pos, null, null); } @@ -1387,17 +1416,18 @@ function start(fn) { function start_gipy(path, maps, interests) { console.log("starting"); - if (settings.power_lcd_off) { - Bangle.setOptions({ - lockTimeout: 10000, - backlightTimeout: 20000, - lcdPowerTimeout: 30000, - hrmSportMode: 2, - wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. - wakeOnFaceUp: false, - wakeOnTouch: true, - }); - } + // we handle manually the backlight + Bangle.setOptions({ + lockTimeout: 0, + backlightTimeout: 0, + lcdPowerTimeout: 0, + hrmSportMode: 2, + wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. + wakeOnFaceUp: false, + wakeOnTouch: false, + powerSave: false, + }); + Bangle.setPollInterval(4000); // disable accelerometer as much as we can if (!simulated && settings.disable_bluetooth) { NRF.sleep(); // disable bluetooth completely } @@ -1406,6 +1436,7 @@ function start_gipy(path, maps, interests) { setWatch( function() { + status.activate(); if (in_menu) { return; } @@ -1429,6 +1460,12 @@ function start_gipy(path, maps, interests) { zoomed = v; }, }, + /*LANG*/"powersaving": { + value: powersaving, + onchange: (v) => { + powersaving = v; + } + }, "back to map": function() { in_menu = false; E.showMenu(); @@ -1461,6 +1498,7 @@ function start_gipy(path, maps, interests) { status.display(); Bangle.on("stroke", (o) => { + status.activate(); if (in_menu) { return; } @@ -1488,7 +1526,7 @@ function start_gipy(path, maps, interests) { Bangle.setLCDPower(1); setInterval(simulate_gps, 500, status); } else { - Bangle.setLocked(false); + status.activate(); let frame = 0; let set_coordinates = function(data) { @@ -1519,15 +1557,9 @@ function start_gipy(path, maps, interests) { Bangle.setGPSPower(true, "gipy"); Bangle.on("GPS", set_coordinates); - Bangle.on("lock", function(on) { - if (!on) { - Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking - } - }); } } - let files = s.list(".gps"); if (files.length <= 1) { if (files.length == 0) { diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js index 9283f8ab9..395b1ac93 100644 --- a/apps/gipy/settings.js +++ b/apps/gipy/settings.js @@ -5,7 +5,8 @@ lost_distance: 50, buzz_on_turns: false, disable_bluetooth: true, - power_lcd_off: true, + brightness: 0.5, + power_lcd_off: false, }, require("Storage").readJSON(FILE, true) || {} ); @@ -35,14 +36,25 @@ } }, "lost distance": { - value: 50 | settings.lost_distance, // 0| converts undefined to 0 + value: settings.lost_distance, min: 10, max: 500, onchange: (v) => { - settings.max_speed = v; + settings.lost_distance = v; writeSettings(); }, }, + "brightness": { + value: settings.brightness, + min: 0, + max: 1, + step: 0.1, + onchange: (v) => { + settings.brightness = v; + writeSettings(); + }, + }, + /*LANG*/"power lcd off": { value: settings.power_lcd_off == true, onchange: (v) => { From 549cd0aadd505cfb84ce31304cb11c07b4e1f0d7 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Fri, 14 Jul 2023 08:32:18 +0200 Subject: [PATCH 08/11] gipy: misc --- apps/gipy/TODO | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 93f241e44..294f18952 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -36,6 +36,10 @@ my conclusion is that: ************************** ++ when you walk the direction still has a tendency to shift ++ don't do the new powersave if you walk ? ++ can we turn lcd off without locking ? ++ function Graphics.transformVertices + put back foot only ways + try fiddling with jit From 465539561ff50a230150d51d2e86d4d83060e7bb Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Tue, 18 Jul 2023 08:59:37 +0200 Subject: [PATCH 09/11] gipy: improvements to sleep algorithm --- apps/gipy/README.md | 4 +-- apps/gipy/app.js | 78 ++++++++++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/apps/gipy/README.md b/apps/gipy/README.md index fceff20c8..03ca97753 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -103,8 +103,8 @@ Few settings for now (feel free to suggest me more) : - buzz on turns : should the watch buzz when reaching a waypoint ? - disable bluetooth : turn bluetooth off completely to try to save some power. - brightness : how bright should screen be ? (by default 0.5, again saving power) -- power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points -and when you touch the screen. +- power lcd off (disabled by default): turn lcd off when inactive to save power. the watch will wake up when reaching points, +when you touch the screen and when speed is below 13km/h. ### Caveats diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 718a2c6f5..85319922a 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -608,6 +608,7 @@ class Interests { class Status { constructor(path, maps, interests) { this.path = path; + this.default_options = true; // do we still have default options ? this.active = false; // should we have screen on this.last_activity = getTime(); this.maps = maps; @@ -668,7 +669,6 @@ class Status { if (getTime() - this.last_activity > 30) { this.active = false; Bangle.setLCDBrightness(0); - Bangle.setLocked(true); if (settings.power_lcd_off) { Bangle.setLCDPower(false); } @@ -698,6 +698,10 @@ class Status { let distance_to_previous = previous_point.distance(position); // gps signal is noisy but rarely above 5 meters if (distance_to_previous < 5) { + // update instant speed and return + let oldest_point = this.old_points[0]; + let distance_to_oldest = oldest_point.distance(position); + this.instant_speed = distance_to_oldest / (now - this.old_times[0]); return null; } } @@ -731,18 +735,47 @@ class Status { let angle = Math.atan2(diff.lat, diff.lon); return angle; } - update_position(new_position, maybe_direction, timestamp) { + update_position(new_position) { let direction = this.new_position_reached(new_position); if (direction === null) { - if (maybe_direction === null) { - return; - } else { - direction = maybe_direction; + if (this.old_points.length > 1) { + this.display(); // re-display because speed has changed } + return; } if (in_menu) { return; } + if (this.instant_speed * 3.6 < 13) { + this.activate(); // if we go too slow turn on, we might be looking for the direction to follow + if (!this.default_options) { + this.default_options = true; + + Bangle.setOptions({ + lockTimeout: 10000, + backlightTimeout: 10000, + wakeOnTwist: true, + powerSave: true, + }); + } + } else { + if (this.default_options) { + this.default_options = false; + + Bangle.setOptions({ + lockTimeout: 0, + backlightTimeout: 0, + lcdPowerTimeout: 0, + hrmSportMode: 2, + wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. + wakeOnFaceUp: false, + wakeOnTouch: true, + powerSave: false, + }); + Bangle.setPollInterval(4000); // disable accelerometer as much as we can + } + + } this.check_activity(); // if we don't move or are in menu we should stay on this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0); @@ -760,16 +793,6 @@ class Status { new_position.lat + sin_direction * this.instant_speed * 0.00001 ); - // abort if we are late - // if (timestamp !== null) { - // let elapsed = Date.now() - timestamp; - // if (elapsed > 1000) { - // console.log("we are late"); - // return; - // } - // console.log("we are not late"); - // } - if (this.path !== null) { // detect segment we are on now let res = this.path.nearest_segment( @@ -1364,7 +1387,7 @@ function simulate_gps(status) { if (fake_gps_point < 1) { fake_gps_point += 0.01; } - status.update_position(pos, null, null); + status.update_position(pos); } else { if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { return; @@ -1386,7 +1409,7 @@ function simulate_gps(status) { } else { fake_gps_point += 0.01; // advance simulation } - status.update_position(pos, null, null); + status.update_position(pos); } } @@ -1416,18 +1439,6 @@ function start(fn) { function start_gipy(path, maps, interests) { console.log("starting"); - // we handle manually the backlight - Bangle.setOptions({ - lockTimeout: 0, - backlightTimeout: 0, - lcdPowerTimeout: 0, - hrmSportMode: 2, - wakeOnTwist: false, // if true watch will never sleep due to speed and road bumps. tried several tresholds. - wakeOnFaceUp: false, - wakeOnTouch: false, - powerSave: false, - }); - Bangle.setPollInterval(4000); // disable accelerometer as much as we can if (!simulated && settings.disable_bluetooth) { NRF.sleep(); // disable bluetooth completely } @@ -1460,7 +1471,8 @@ function start_gipy(path, maps, interests) { zoomed = v; }, }, - /*LANG*/"powersaving": { + /*LANG*/ + "powersaving": { value: powersaving, onchange: (v) => { powersaving = v; @@ -1540,7 +1552,7 @@ function start_gipy(path, maps, interests) { status.starting_time = getTime(); Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen } - status.update_position(new Point(data.lon, data.lat), null, data.time); + status.update_position(new Point(data.lon, data.lat)); } let gps_status_color; if (frame % 2 == 0 || valid_coordinates) { @@ -1569,4 +1581,4 @@ if (files.length <= 1) { } } else { drawMenu(); -} +} \ No newline at end of file From 0d8df8aaebce2b34201e964a52e2d756c1107d1a Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Sat, 22 Jul 2023 11:54:52 +0200 Subject: [PATCH 10/11] gipy: removing jit segfaults with the jit so i removed it. perfs are still ok due to the use of transformVertices instead i would have like to have both though :-( my guess is the segfaults are not really jit related but related to the size of the code in memory. --- apps/gipy/ChangeLog | 2 + apps/gipy/TODO | 3 - apps/gipy/app.js | 163 +++++++++++++++++++++----------------------- 3 files changed, 81 insertions(+), 87 deletions(-) diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index e8055100a..870ad0fdb 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -95,4 +95,6 @@ * Turn screen off while idling to save battery (with setting) * New setting : disable buzz on turns * New setting : turn bluetooth off to save battery + * New setting : power screen off between points to save battery * Color change for lost direction (now purple) + * Adaptive screen powersaving diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 294f18952..b2a3c7ae1 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -37,9 +37,6 @@ my conclusion is that: ************************** + when you walk the direction still has a tendency to shift -+ don't do the new powersave if you walk ? -+ can we turn lcd off without locking ? -+ function Graphics.transformVertices + put back foot only ways + try fiddling with jit diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 85319922a..9e0dbce24 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -27,23 +27,17 @@ var settings = Object.assign({ s.readJSON("gipy.json", true) || {} ); -let profile_start_times = []; +// let profile_start_times = []; -let splashscreen = require("heatshrink").decompress( - atob( - "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" - ) -); +// function start_profiling() { +// profile_start_times.push(getTime()); +// } -function start_profiling() { - profile_start_times.push(getTime()); -} - -function end_profiling(label) { - let end_time = getTime(); - let elapsed = end_time - profile_start_times.pop(); - console.log("profile:", label, "took", elapsed); -} +// function end_profiling(label) { +// let end_time = getTime(); +// let elapsed = end_time - profile_start_times.pop(); +// console.log("profile:", label, "took", elapsed); +// } // return the index of the largest element of the array which is <= x function binary_search(array, x) { @@ -399,28 +393,20 @@ class Map { cos_direction, sin_direction ) { - "jit"; + // "jit"; let center_x = g.getWidth() / 2; let center_y = g.getHeight() / 2 + Y_OFFSET; let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); let scaled_current_x = current_x * scale_factor; let scaled_current_y = current_y * scale_factor; + let recentered_points = g.transformVertices(points, [1, 0, 0, 1, -scaled_current_x, -scaled_current_y]); + let c = cos_direction; + let s = sin_direction; + let screen_points = g.transformVertices(recentered_points, [-c, s, s, c, center_x, center_y]); - for (let i = 0; i < points.length; i += 4) { - let scaled_x = points[i] - scaled_current_x; - let scaled_y = points[i + 1] - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = center_x - rotated_x; - let final_y = center_y + rotated_y; - scaled_x = points[i + 2] - scaled_current_x; - scaled_y = points[i + 3] - scaled_current_y; - rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let new_final_x = center_x - rotated_x; - let new_final_y = center_y + rotated_y; - g.drawLine(final_x, final_y, new_final_x, new_final_y); + for (let i = 0; i < screen_points.length; i += 4) { + g.drawLine(screen_points[i], screen_points[i + 1], screen_points[i + 2], screen_points[i + 3]); } } @@ -433,26 +419,23 @@ class Map { cos_direction, sin_direction ) { + // "jit"; let center_x = g.getWidth() / 2; let center_y = g.getHeight() / 2 + Y_OFFSET; let points = this.fetch_points(tile_x, tile_y, this.side * scale_factor); let scaled_current_x = current_x * scale_factor; let scaled_current_y = current_y * scale_factor; + let recentered_points = g.transformVertices(points, [1, 0, 0, 1, -scaled_current_x, -scaled_current_y]); + let c = cos_direction; + let s = sin_direction; + let screen_points = g.transformVertices(recentered_points, [-c, s, s, c, center_x, center_y]); - for (let i = 0; i < points.length; i += 4) { - let scaled_x = points[i] - scaled_current_x; - let scaled_y = points[i + 1] - scaled_current_y; - let rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - let rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let final_x = center_x - rotated_x; - let final_y = center_y + rotated_y; - scaled_x = points[i + 2] - scaled_current_x; - scaled_y = points[i + 3] - scaled_current_y; - rotated_x = scaled_x * cos_direction - scaled_y * sin_direction; - rotated_y = scaled_x * sin_direction + scaled_y * cos_direction; - let new_final_x = center_x - rotated_x; - let new_final_y = center_y + rotated_y; + for (let i = 0; i < screen_points.length; i += 4) { + let final_x = screen_points[i]; + let final_y = screen_points[i + 1]; + let new_final_x = screen_points[i + 2]; + let new_final_y = screen_points[i + 3]; let xdiff = new_final_x - final_x; let ydiff = new_final_y - final_y; @@ -1156,7 +1139,15 @@ class Status { function load_gps(filename) { // let's display splash screen while loading file + + let splashscreen = require("heatshrink").decompress( + atob( + "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" + ) + ); + g.clear(); + g.drawImage(splashscreen, 0, 0); g.setFont("6x8:2") .setFontAlign(-1, -1, 0) @@ -1314,9 +1305,9 @@ class Point { times(scalar) { return new Point(this.lon * scalar, this.lat * scalar); } - dot(other_point) { - return this.lon * other_point.lon + this.lat * other_point.lat; - } + // dot(other_point) { + // return this.lon * other_point.lon + this.lat * other_point.lat; + // } distance(other_point) { //see https://www.movable-type.co.uk/scripts/latlong.html const R = 6371e3; // metres @@ -1375,43 +1366,6 @@ class Point { let fake_gps_point = 0; -function simulate_gps(status) { - if (status.path === null) { - let map = status.maps[0]; - let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); - let p2 = new Point( - map.start_coordinates[0] + map.side * map.grid_size[0], - map.start_coordinates[1] + map.side * map.grid_size[1] - ); - let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); - if (fake_gps_point < 1) { - fake_gps_point += 0.01; - } - status.update_position(pos); - } else { - if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { - return; - } - let point_index = Math.floor(fake_gps_point); - if (point_index >= status.path.len / 2 - 1) { - return; - } - let p1 = status.path.point(2 * point_index); // use these to approximately follow path - let p2 = status.path.point(2 * (point_index + 1)); - //let p1 = status.path.point(point_index); // use these to strictly follow path - //let p2 = status.path.point(point_index + 1); - - let alpha = fake_gps_point - point_index; - let pos = p1.times(1 - alpha).plus(p2.times(alpha)); - - if (go_backwards) { - fake_gps_point -= 0.01; // advance simulation - } else { - fake_gps_point += 0.01; // advance simulation - } - status.update_position(pos); - } -} function drawMenu() { const menu = { @@ -1536,6 +1490,47 @@ function start_gipy(path, maps, interests) { // let's keep the screen on in simulations Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); + Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen + + + function simulate_gps(status) { + if (status.path === null) { + let map = status.maps[0]; + let p1 = new Point(map.start_coordinates[0], map.start_coordinates[1]); + let p2 = new Point( + map.start_coordinates[0] + map.side * map.grid_size[0], + map.start_coordinates[1] + map.side * map.grid_size[1] + ); + let pos = p1.times(1 - fake_gps_point).plus(p2.times(fake_gps_point)); + if (fake_gps_point < 1) { + fake_gps_point += 0.05; + } + status.update_position(pos); + } else { + if (fake_gps_point > status.path.len - 1 || fake_gps_point < 0) { + return; + } + let point_index = Math.floor(fake_gps_point); + if (point_index >= status.path.len / 2 - 1) { + return; + } + let p1 = status.path.point(2 * point_index); // use these to approximately follow path + let p2 = status.path.point(2 * (point_index + 1)); + //let p1 = status.path.point(point_index); // use these to strictly follow path + //let p2 = status.path.point(point_index + 1); + + let alpha = fake_gps_point - point_index; + let pos = p1.times(1 - alpha).plus(p2.times(alpha)); + + if (go_backwards) { + fake_gps_point -= 0.05; // advance simulation + } else { + fake_gps_point += 0.05; // advance simulation + } + status.update_position(pos); + } + } + setInterval(simulate_gps, 500, status); } else { status.activate(); @@ -1550,7 +1545,7 @@ function start_gipy(path, maps, interests) { if (valid_coordinates) { if (status.starting_time === null) { status.starting_time = getTime(); - Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen + Bangle.loadWidgets(); // load them even in simulation to eat mem } status.update_position(new Point(data.lon, data.lat)); } From 88a4f98ce8f13d79eb800a8eacbdb8b4a9a3bf77 Mon Sep 17 00:00:00 2001 From: frederic wagner Date: Mon, 24 Jul 2023 09:41:42 +0200 Subject: [PATCH 11/11] gipy: trying to fix the segfaults --- apps/gipy/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gipy/app.js b/apps/gipy/app.js index 9e0dbce24..071ef8283 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -755,7 +755,7 @@ class Status { wakeOnTouch: true, powerSave: false, }); - Bangle.setPollInterval(4000); // disable accelerometer as much as we can + Bangle.setPollInterval(2000); // disable accelerometer as much as we can (a value of 4000 seem to cause hard reboot crashes (segfaults ?) so keep 2000) } }