Ticket #24238: ticket_24238.patch

File ticket_24238.patch, 154.7 KB (added by StephaneP, 12 months ago)
  • new file .vscode/launch.json

    diff --git a/.vscode/launch.json b/.vscode/launch.json
    new file mode 100644
    index 0000000000..58807032f3
    - +  
     1{
     2    // Use IntelliSense to learn about possible attributes.
     3    // Hover to view descriptions of existing attributes.
     4    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
     5    "version": "0.2.0",
     6    "configurations": [
     7        {
     8            "type": "java",
     9            "name": "Current File",
     10            "request": "launch",
     11            "mainClass": "${file}"
     12        },
     13        {
     14            "type": "java",
     15            "name": "ProjectionCLI",
     16            "request": "launch",
     17            "mainClass": "org.openstreetmap.josm.data.projection.ProjectionCLI",
     18            "projectName": "josm"
     19        },
     20        {
     21            "type": "java",
     22            "name": "ProjectionRefTest",
     23            "request": "launch",
     24            "mainClass": "org.openstreetmap.josm.data.projection.ProjectionRefTest",
     25            "projectName": "josm"
     26        },
     27        {
     28            "type": "java",
     29            "name": "ProjectionRegressionTest",
     30            "request": "launch",
     31            "mainClass": "org.openstreetmap.josm.data.projection.ProjectionRegressionTest",
     32            "projectName": "josm"
     33        },
     34        {
     35            "type": "java",
     36            "name": "MainApplication",
     37            "request": "launch",
     38            "mainClass": "org.openstreetmap.josm.gui.MainApplication",
     39            "projectName": "josm",
     40            "vmArgs": "-Djosm.dir.name=JOSM-dev --add-exports=java.base/sun.security.action=ALL-UNNAMED --add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED --add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED",
     41        }
     42    ]
     43}
     44 No newline at end of file
  • new file nodist/data/exif-position-error.jpg

    diff --git a/nodist/data/exif-position-error.jpg b/nodist/data/exif-position-error.jpg
    new file mode 100644
    index 0000000000000000000000000000000000000000..db8edf752efa8b0274e7f89a20534da20e122219
    GIT binary patch
    literal 47012
    zcmcG#2UL?y6EGTz3IZZXSBg~Wy*CRT=^X^5mk^NNK@sUSH0dI}1O!5FQl+C*3B7j+
    zf)M%*0)FrJ-tV6Oo_o$+&a*o+J2N{oJG<G<6E1#U%mVJqNy$n9P*6|+GRQx`#hm7M
    zNjEEV06;+jzytsQFaX#n_W-C!hzj|nq2T_3DUfg@%Kg9aBnmzN4M{^m{sB<P0Jr|Y
    z0KhTI!y6a{73Ik_4^kTn=O0-TBy<;r>kmwTgsD*<{6|lq6#)H84+>up-3^TM8UVn(
    zFY!WF;T0zzx3rS7s?sZI4o)rsZf*`XIb~_21hPomDLdF*f&ZhHkyKUU<fM9TV`KvS
    zAKie<NL+ey)yi;jv2k#+ad5G5aj@}o{1xEh;NlVD;1S~HpyCt|;^q_L;Q|0S{E}`w
    zM)UGZ{R{gCQUAyLAckwW2LOly0PY}FqH}O@aYmutL;@`=3jHtqDe5ZHNIo=Z6y6{D
    zC0rkc{}*nHM!C|1f{F%+M*TZ(FBk=I_sSczVeo(GlVJ3}^hNNkzx<ov+kfGG@SXq6
    z1{mWXFy=K(1pt8W{^dUfdtKUqiGg$n9US$?He@E?V1uJ??8Lzbga6XWK3)5cfq56}
    z6Uwztq_Lk+Z)ThD>nF4u_yNYJTQ@L{;%CB}adD_V6JN_BGb{5-C(30yW?sbvncM(C
    z)<5)Tq5fyAEVO^X=zn!S$+~q-$3@ECzJ`&CvTkB>sq-c#m+(zYkTzu9{fADht9e1P
    z0J5<ELBCo7$d!b{u}%)aM0Q`A`QVZdS&%T1C$c3)K6n3Dru{D(GY6Lt2Qoqb-CpSf
    z93xxof54_l_-a(-N<~6f_#X;#og?wP00JVw*QZhdz)Lj^ZVo;UP7Y2U4h{}p?sTO~
    zQNSkvvJaVwmld*5C0x_LT+zGz19Q6CR?jq{Z(C0=`L*w?N`nEcm`vX`ulQdllU=Sf
    zKy?tG#AW*leAQmU|9=-VDsw9vGb&jLDp4vPeh!Ie99$e!IR6^;6YS|F<pNnQ+owGQ
    zfBe0KFH6C7nT@^jd%yaM{|0|rbA=-p!Rt%@dflH-FWZ~)^>W6M#rC@WxBf5BB&7c^
    ze7%x;op?3QY|>Tx9ckBF<a0my3cqRZ{So?4b%WouH$6A@mtSYWwHbidM=gKmBWg74
    z*(EP>P5mh{H{-@#jrXbN%C7I%ICk$94!_1Fy8jywyT*|@|MpU0wc%C&ALYnO<~o0F
    z?7WG`4Sv%SUJKmVdm|5cJtcVwzlr~E`)=?X`=YM<Z|u9l|4;T+f4tJ`d_CZeeK+Ip
    zzfAZOum6*M@N2%e*Zv~&<4^HYy@|(FeBhUfhb&Roe0i&sfAphY^Z&cmzRr&u``%vj
    z-847$-Q>{;Quk%wjs8yg^*{4Id#xug;A*^o`*XR8T$T{3KMerus`*>Lxz3-zgzI^^
    znXg#kzdipGxUu6V-!A?9d2Q>Zz?bWMe|w2smIujy?7l*->YLd7lXf%yjr_mo@1MMv
    z@Y|blZpOa$<NI|U{LTMs{3gHP*Z56--NgIf`v0B(uRs3x{&HjAePzi%`@oI;|DKP(
    zdjA#Y%QMks-%VcpCno=$UpMpJo5J?=lHdP&-Y?;QmN!JLf8xxKBwju@_WYCQf5G3c
    z<Crq+^7OJ_`FbC@SqC@sans(!{bql=;TI4e|08eu=bVY$RIlUp=33uPfAzI#|L&J~
    zdL<7)VS0OM$G_u$NxLkFHq)nnfa_{?@M>KH9$yhYl3m5&-~Qjs*Ufsq!7cNS{wqIk
    z`h!TW^aNR6waV9VLiWGD9O+c!dOx_f_hvu1;X5r3dwR)-opQA=-K?vqYdtr9ycRqD
    zGrq(%-wpo_eq-Ow=fL-Cc>zwPKl;B!Z2ytJ(SOsvxwijO{_=`&L!Z5t=YY!pk-vXE
    zuG^U0f9bo4<3HnjspHKxKLYCbhad2IND`?Y5QMn;T)G+Ol6F~MU-K8aUgehr=~X=O
    zuet%BuKOh(UG?ASxv~G>IKb#?yr-jA^Ulk|hg?TIg1np@f&jo3HuC->wDXCgJaP!r
    z%jX3*4?ib27e7BQFTViq^}zpWE;IHrMChu50$0?_MSPh+=*R>6C3M+~s)($R&}D)k
    zsrQVL^&h&{CH?AjdP(;{a$L$@CE(u^`#mu7ta&4wggoE>k>$RW#ri`>^4}{%o&o>R
    zFHHyF{H50duI_oL>X&rfzw{Qs)$QYoj`x?|4Y;~}Aa57<1_2NMoN}p<@FalzkACEb
    z!o5=f#ibueJ1;GzyphF4-Vpv2He`L-a~UsWizkX~^{#1`bb>!H8um{B;PDLda+QI^
    zy^+uTKXd?q_PV{isb2CEBOg)#+EWwcq1g#Zx}vi+w*ml9*d>+Kq=f|e1-Jmu)LzKS
    zB3Y#s)zqj|)#VMPWVNYYD5<JSzI>)GtEBj!vo(?l=>~E#Rspw>SBfiNZX-4Sz2o8<
    z{O68~YyQtWE-DIg#{SraYm55#MkB5JN9Vupm=`}UCIJ4I_wfH)K}D8pA`(DD7L+Rj
    zD(WSIhKfXy;2-Rg5AE`~?zw^fuw7HH`u-p4i?0AY41gQp6B^2604g2|8Xn3;8_H*-
    z7q_l#xHcYuyk?@`x_#&J=7vlUq-s=jv|G2(kq)DHUtU4c@owQi=6rVhfr`-`YI_2%
    zchMObG|x+$2~~f<X}OIZ{4nnl5tERT(a|$JVPxXr<>MC+6nY^cDJ3l<E2pNep{b><
    zqibSnW^Q3=W$ozX?BeR??(yFLLqK3qa0oaiHZDHlV`9?h%&hF3+`RmPvT|reWmR=e
    zZA)ugd&jrVuI|C1;gQj?pX0E(`Gv)$<(1X7_1#~4`@atk5l6?!yWo{O$ok5ofB1vE
    zV56X-qobkUx%3AG)%DU@JoH<SId9`XQ@LYg|A3n79R|Vk=#0{4Od4)gIH9q_kGn*)
    zJacrrmoEMB=>MHVe*d35`s>hNe=cAEY&7H+f`^6&5C@#mz#D_nJB#>VgK#45)}GC!
    z<#7oeeDj#vrE7=8wKB`md&>Mk)xTw<KtDB8`{<<`8bcoKuHi_v<%x|mFUV%<I6Zzo
    zDce792LxR`PIDv-gSBt9x7L54v&Abz{j%`Tjr{hVFDps=N;rt;58eIC+9CxW&Vd!$
    zB#v5%o_$BDdAE@U+$SsfX$0O<WQL2~<vOUC@a??--1gW*7^~%$z~*!et+Z-o(cGiH
    z^z+SCp$gET1PTzwnaz^eftD~c4MoykbXEa^;<@i(ex-Q;=hAxrZj8QOSD?upDSQFo
    z&VBGAIYBW&5TeymTn82)8`s04asv3(Jm<&W>X&6s+3hS5sfKU#IT)jn0%~3t?meL;
    zpL%NsI0^Y}HKLX?)B9_QLbfDH!0x^ku}c7dg=<i$302tABYzAuDTjw|6*<2jkHxH~
    z!|wA$XgphWSwYXG^55FSP9Lm&@<W5hmqsY=mFc-OR+0Y8zDMD<pCw~uw9o>V90;&2
    z?ochv2okPD+Bw?Qw>N;q-G&PHK1{d1>vzET`g<kY=^IWDr9O5VO%kxjurox~rg$Dt
    z(QmBRS&37Cbn@xFz)2GkY<|(wl{8K+%X9KwmSfTWbfQeVENchL85kD|=~zU%;2@2q
    zJGXm6&s}%fnveK-S<wb;K*rZ)rybT^L(Rs89Vd=b{*!i@8@7=vr5khfrG)U^b8Bcm
    zw5mMCE;j>Fluqa)?Dm-w58?W1w?E(c^QtaU>YQvvAN|_G34N|_;j@C|fa<r>Y#F`}
    zS;*X~>83y#jI?R`L!Id=Zm#nY6-K$1wZ4%e+*TQgh!%ZmrCJc<O4?>?rWF~;-?ea_
    z2rTO?SOV6U+Fh|QoXq+$Nkl8pu&XX&QW$H@bpe3z8WeM5zAMhwOM<t~m9h$|*}Hr&
    zv>vWGgu2J2<!)#oj0s<l@OEaXmO)jaxkC1XTGLEIdLYoEV{K!D?SQ>w4!IG8XU%BX
    z`S$xhOE%5zV<PAzKX*E`Iae>^A>DHmOj-xMjFOKcFXmIR)EWX$oT&y)YO3oiYXe4k
    zAMiGVr)j~Mi8{qUHnVC1E%%7U<O#G}c?=U4cA15H1*ho9?DGw>8At_{#^oRkgsM!c
    zDj?6n`XmQXiPOoWrhD-HSUZ<t^*TpMTd1y&|FOgmaxs10rLvI<`6*>Lr!IoLC6H5}
    ztR1~pkMx0TL~z!tJ|3HVTDv@q`mKdtt7JL&C^%loIon@>wf7vf{;CQ#C~&unw}<wF
    zNrJ34Xm}E==g_iSl4fjVsE}yHIEOHglj-BXtG@PaS~h26Pi`G{0;wzppX@;RMw#rb
    zP8I5khX-}{M_iEW;zURfTIx4^WTx{SPv|}^3hMR>;`~6&XedmJi={s^@71IbOY@?z
    z83WKpd^$8WH>UGc($~fG;~lA@oD+cyz~Z+#oepu>GwG9V!z3XEO6dp@z`GcGCTaA!
    zCc}gytwJjg3D8pM%Yr!cy|}LJhHulP=W$yLE0(84UI(O(^2-+hJ`cXgjF+f-pLr_F
    zs@eR#svq6POY;YGdpP|rE}+ELi}N@1{`}C5o(>=HhmT5ts8SAQNkp6Ru=&>H;bX~e
    zp_J{?oVEmkp(Qs~wO-DmMf~UYP0e50gmL3P6`x)SBimE3kjBW7xZht)7u8waUDHos
    z^67Dav;p&2^FR{ko-BnTlSrA*%U??O*Os?Vdp^jhQz=s!rq-84J#td3=98saGf*g4
    zEnR-vsSf{CiRIY(dI8H%7<YchP&)9ChEi$x7f^2N_&hMyx{)$Kbfw3|1f2W*uxsa=
    zp~qgT@mD62%3J#5qQ9ikB2E`}30GUEfrA%-=pKgd#HHiP_-@izycDl)C6nmrnUr%4
    z_|8^h&Z}?mT*Zd+7>zH(tRnhGgz=E9fkzBVHEFQ*<!SlgM?Tv>U;Wg}0v9rWvc<%<
    zhf{DEqz8DZ(ZXUmev;)w$d>xP;5wHmfQAYt539utcVo=toz^wq8&vDAA{d&*NJsXA
    zMO?ihvOtRhx6iW9d08@H#FXPn==ax-Q}C>@0TC9S8==AI;5*v!j3GQ~@?oMcl!rHW
    zHl5{9rj#VS$yjf1DC1A*(EsFZwye~Wt;p6_9bD`o?`XCy&iFyDg;jN$3yFxZXmbVu
    zH~g}a9O`Y?Dm)>Ao12qPF|HMfXOxUWD!Q9znWyZzx%1(v+?r#X&YhW|%7fX1(6bj?
    z2~pO3yOHH|Ep)MrZ$ijO!lL>UBombqX`so^UP`C(_9v7~5yzbHOV!msV~EZv1$!Q*
    zVK6jtJ);KIsF{}Qc0_`{$QAb%OHlbtw8Z4fGv_P17<8+(EAD7`xH;1fX}Q>kf$cQP
    zTwR@u3!ar!k-a%OIq;k))RLE9KM-V&{BW?)c1UDEwu`2xKV?O^%lg6WgiOiZt+qF)
    z1S17Dod{kRSb8goMau<^&$HFCDK)eTG|x4&qK&rxg4}$)lHxi8yy@529+yTisxf`%
    zZqt+pT0Arjk=4IF@?2z^+P>MuS}lB1w1jsgbw1-^A#;PCyar_9w2Q5l+j(sWq?0#Q
    zA_^L+TJTLj01tJmVxZ{62_B8bF_5Bk;LdI7@e$b~sBcLz#8bSz0Jv8A4#-^q<lZ=G
    z2pNt!9tDzX^ykhUYVRvBD7D~pk+dw84!g{{mgvJr6PIO)v>1QErE{Bdo}My)>)UWp
    z&0jkv7r<~-q-ZJ-n??x2-?|l;D$_`)@6kusN)t;RpUnb(w|g=~Hd#z~v&+PvH!svi
    z+}Ab3?6t7}*^a9cQ&pn&-Y{VK)yq5!b*qiSkS0mjjAKP&`h!HTB^Z~l!!UxT{rK+M
    zULV>^zWg^Dv3zKpodfX2h`70=)TZ&Nuvc;Db_X<fmhSv?*)oJIYBV%F@%2AF<jvm9
    zWlt7q((rrzi|?5cSYRXS*^#Hy8L+Q{gv)e~U@m}57{nkz@%v!d$c<gXt3u~jSDFb#
    zCo&nUM(BN~=<&(^e0@w?)9XQ}DQWHD+<+maAy)IcnluNOEPF;{E)+KH@Q?ybmak7}
    z-{}jayyV)v$5b@K^2DfcetAY_D4UV*4Bwn!ewT3Vxlql>2ZFj91zI01FU7O+7>V5o
    zs2B4ivXu`_WgsVSEIAo+HrkOA+dHt5>=d(jvm^l!>t~n|r(7@jl0#;X54+C~e#-k^
    zD21{XIjwVrcDM#^d?)ISb(WfLyg1g=zxM|aF}AJ|PZq6m^>V=ihYv03gDRS_PKDci
    z_>HUgs23)MvTp^nwLl)R?H1&zlmw?N@w-yEtEs#y^krz;s<A^K>e>|)HL%!XlzNb^
    zzOG2{8LZjF6bEx1`i0=yn_sFp2<IK=d&8Cp(b;g-BEs+3hWC`q)(6Q=D0F(tdxEk%
    z;A0U)v~-EDYAbRY-VhOZZWQVn-jfDpuhZ{K5;i6rOleEOitgsBOj+bk5MXD79ZZ{`
    zVZV-C#->;J&Eg8x{UztjD=W*lwoy2iAWc{>=FNMCnsl&jbs#_X;?r(r2`KwpE{rA0
    z*RtytbaWI%6(BGupY`GX2OEOhRD}L-3nxWnN(X{AzEw04u1EE;73OI-=k@^2Ozn!Z
    z_6$JMTSP;WAd*<$qOdNHgGs@=)z65xNA%XXQi^jZxYTNGt+KK+E3}2DJj=8^W@?x8
    z4P9d1Y{pg0K=!8|Akb73Iy~aKmH5Xim=htql(_oq7(=WG`H%F!vTHvO?vHQ19<*KL
    zs*8`(ZwD<3OLR{Sgz5IkHjWfI>zy(3O33gEpNOz%5etP6t#uEW=7}UG676RGl9=|C
    z&b4W){JJpKZb+QypdjF&5+|ds;Gi^_%ghsZR1=pv9bfmVOe&V4N3DMpSk+o_SfwOh
    ze?GN!%CfD#l_*PP=LPyrN^(ocr($Alny4u{USLiq<^Crr_t#GsOVF_&t&m6B8?IH2
    zsxy|*4lH@(_h%l_{D7vTZP-z_)Y>$^3nOpcAvWanG57@P>Y^CgNP7g&X+jSX>-h4y
    zn!+Ze`3=eKj-{@zROv%WM<N@fm@4@eKhAT@<5j@<xayJhVs|&l0t3utEFxMgmnHnP
    z!DhiBof?grF~+x5?=QFte%*-P=MU$_an%@Pa|$PJ^~l=OZxI>K{goL`gD!x@cUA`O
    z7JBe)v?;YbOoYf)E-NR4@QK3op;y^j!x`@A7vAi%bk(~e1Mw=Fdyxj;R`}c50~)!0
    zY{#5vNykt9MpeiHR;n@5NsT;tg^yKWA~--U=~Xi?erKc7cK6lKsb^e|c<Y09YY&&X
    zxawc;Z5h_Ui}_M**vMO~<&+)tVWH(N9@{=9Z6xl45FI&Ihc3y35q<08$~}<;^}*sf
    z7P?Ajn6?0NXPI5u`?e6W2T@bPzm0W&lKc101iWO31FXn2ZI};_8*1v^11Y_{<DybK
    zi?-E_+)&e)=bK<F>V`O{qVn35EspED*{z}8#BG&>1FYSx4>}pjoCKgXe(Hf%ug7&d
    z)h#9rN#~b=j}lrHSqqjFMQThwr%Lt_t!rhYgU?;2K23*sL8j<EQWyf<`sunecf=3U
    zT)Q_1{Y@wK^|dr|)!?}E<n7sm4fW4wPE2GSSu~Yp%4F=4M(0P$Y0&xEj4S*WFu0*b
    zjJ6IPWuV^zlr6Pe;b}4C@rm;?(86#V14CoM*2H4yNB=$!RrEJdyiCazTPRBzL7b~^
    z$|Q~89M`0i@mND(yV_yHL<>YfN=XxHRWT-=o4&uz7LrifSXrp3=j<P&FPypDvlQ8y
    z#t|cv@Vr{bsdKp=T4q%gUa$G2>cIrJb*{%0jgs$5PH%!QwaAVm$tKTG3Gi41HX4x>
    z5P2_<wZ4&lcvEv(Pd*jU(2&_VR=KL!W|_1{hizo)M`S0o&MOL}%{P1~H~o9P1?Nd6
    zWSdct*ORV~l)p@cBh@)j%fs-8sM?Tb#V<Y|d`YX~4Fh8MKAHWlYuImZJ-s1lYst(}
    zr3ZNy_s_MY>`7hU&SM+D`S}42sAhx&UaLLJ!QS)I*iBw>^bMf9Z=sh&WyJARu{qr0
    z-Ijza$G1En>|S;jj_t8?M@O611;8$Co=zPaBNab7daD*Zedo-Mb?#1hCzDoACYg1(
    z4a?$$L*(|G{_buUl~ftlhBaMQ+G4L!X5QmU6UAh^#OmRYGTCW}w*d^s<)b&1uqj5Q
    z0KWiq5Gk)|ZIuAG?!R?=q0J$pwV*wewv1K1gJD|sE4wDiMux=h`Ku3m@ipnh3c1z>
    zG!>0woBO2n<yC{PhxSD<jH^qBw;q3~36Ss6S@aeg*V71IsNyIx6^OvA9kV(Oh_GPM
    zARDfshJ<JbDHJO1Bt803?%BRUs?Xs1&|qa;3e(s~)Q(Ip;(TMHpkhL)peil!m`EFO
    z=j*XVv}6$?U|xrUAC#n-W-7tKp$8s%X*`)}S{m>=v=)EjAfI=0`Yv3&b$rLBLLawd
    zOh|t@9zHECI-2yE)HK9<onO+yC76a<BvWkWV<1EB?`{6JK!M>3<`$J$GRN|A1yOw9
    zF7iIH$0ClFr<m-_D$vb6$gia!2I2lWkGPX*9Us+qC&Ww48O@WoJ+>-p;DLd(mw5v-
    z%TBFiw3?|(r?i+FPI~?AhJluuyZd_d@7Q@og;nga#cka6@p&UgQGnQ<nNF|iYy<#Y
    zaS>8`4s*=QyQlUn23YYNvAT|jRX-rC*(Kh56-6G}21F4Li58cWtl5#%yM5;4z3pib
    z_{rC7z}?<GU(Z@o70cZT>P8d3-$^6yZ@xEXdH1#3=egTreRVF1#Cf&#+Nij3Nmlq6
    z_xZ?l7ENJ<_t1k%6OyFRx3rU&Xvt3Zi1p_S4f;GwH9>W{Oecq~7GuiUn^xJGBO5^P
    zT)P^}*ImM*-}y7;YT|oT6It3k>5_gv<Yln3V;S#Nnd%0t^c=aSevM2y=eL5)2W_#~
    z6gkyR%s_SP$L_<r_eyI`BStXCgFidFII{1ca-zQo6xeroq}O8Z#^}hC!6-aEYH*Mi
    zzFmKka+E`E%yp`6lN_r$VzAw_<MI<84xCe{PvUJ%&2+PCiDEs7vpP80xv!sfXs<xm
    z!xQ<)dRDu;u#%D^;W>nab2h3`>(1uJ?2*}r-%pCiUxSz591!zGf;`cmX=<qArt~Jf
    z(Jva81SsKW-g=n4Z%y%7vuAZ)(0qkaBPCtLkj1HNQJ=N3mh!RMdqxv$m1Vy$6AZe}
    zvlY-$TGf~Wuk-*}?uQG2g;x2l*>djgi?8tZIg;NBo+?A9oJG1bjj3}AWFzb)D{1ZI
    z+pHQQU5lx2miVRpzR@c!NgtFTQq6Wl=Lf>Pl;$n~_tzc0xGS5Q;47^c0Ian^S>9uW
    zTSjTfVdoKTWzzOK$#3_fg(+GEALTedbei3rha%ef$?$2kJH09Pg5h@u!H8FuSr-6z
    z^LcRI0<&xQzRd4x-}kGF<L?U}?iHxz#JH*~y9^b>C=(0>s%zxewr{odJWfpeb)ptA
    zFFgdJbA7nvy~QF+Ytb35eDqfDC1J}Oul^0~5%D>SFTrL*B|OQI_qEr^DnGMJ9DZQm
    z*Z0&?kC6lo{?ywotQDeJ*d!XD?l{ETIXw)_z*zdZ<HCCOJnKON#DdrjLbzE%03GY1
    z)Y3O_IvgyBPoMJ;o3?>ZDYb#J5d~vv{89kcZPvwF_?ps_%;|NQ%{=UfHV>E}1ZMe;
    zvrHJ$=Fke26YD%nuD{C$oxpwMHZy-OIO9~Hsd{k7OKq-D)X;1hlD&3(0XWcy#5NLC
    zxxVq5uZr(XhH0}bR~pjGCzAX0_&bb5^jb0wfWt+Is4MgurG*qZ!?g{4ta8Aze80=S
    z?>cs;eZ$x+VH8jcNH{E>)LY9O(_>4JR=EIRxCG9!hlAaWVSDXSZoUfe?F#mHBHl(O
    ze0`3ZyFPVuCz|DH-(|yD!^ci(`G^vj#mG9w<YlnAV9>brG|t-4x$RW<OiQ5`ZUy4E
    zt<&*Xpsd7gnnbmc6L$4R5KG<j>_U0EonxtwtVI>_*?JYOTJc!kibR#ky<t{rg6_8R
    zDQe-eQUV@`M`ggd@B+<TQl1jt9ck>WL4-!bN>++;YfCW$4qr@c-T;1T3B1EMCU0!U
    zB)I|+ZLy0F8`2+fN)=lN%Gyy}0F<hR=qve-;Qg7oSPwm|qFxq!Z(Ypp3?Y00;)t8n
    z`^J0(I!JXbLWI5?fii+<2c42VL43!8gD&A{GwXax&g5;@ik~V+9h%*VhEvMhDZA;y
    zU->8m#jY9KE0j3!0$cdXd>fYJCg|hiN4xfbbz&c+lkR&TJ@5(l)gRbT{BEe;qz$UT
    zxjXHoUr7CoF11$+JOAwhJ|(nx_HOalR=<t<q|Hs<CUPsb(aLGeAHJnDRPI`MGdjIl
    ze1qMftwjxVoQ=I%+!uzmjXLUiH4tFM;K{y=6n9J*^;Sd7#?h<&Gy0Z+w#rBrU2Nh*
    zFber-V%~ZT#=OGUWz1W3a+t6J^w1QP2;k?zb8>{Idd#LH!r=nY3c{RPS##|-dAFcL
    zilx_Qht<np3hFEaIKJ}(+VJ03|5`=ZV@8{ztLe$SZEwtFX$*^LnAA_Q+h$09Rxt#j
    zNs!x>j`$s#ou*y#8omi1;LYTZvkj*UD}P4!i^Z3z_Aygo2kJX@s5hD_aDsX2#W&b!
    zN{l<Jl}U-LJ;>D?Gof3|x%A^gAn^5PCEjPoMIkj}egj70)~Ohd0<vt{g<B)MRhbsJ
    z2QM==lM0y`nWuga4LI`8(<c{>%R77Xx7`kSbN-6VJ!k@Q#2IR(V;mui)4wS1{xXtj
    z@ssB4+?$2(^-4u+LK@Q<-gen^1v;(CK!KIInvfAw9Wz#uI##doT=3yiB)IPLM0H4Z
    zqDp}NbDysE4qLG7MDmm?2)1Q7^rbOZQyW%#5S)wAS6R}tV-r7CRnm({SXkLgIAJ(N
    z*^LN?FD$Tk?BKY$wrLTQIj49yH^kSt@+H!p=#OW?pvNBac5~U_LGd!FF(u-J1DCi=
    zZH?fgq4T@sC(mD<_so<_LFVlOryn;ojWtq>M*5%9LApqRPgpBnHVo(Ii5%V*n0X$z
    zwY3tij=7@w06>_~-iz>Kr|XNwuYXfMs`;u6Y{i=mQ#jOR?eEqmmM4^~0g36e7YuoW
    zAoT-t1sn5YVXUW)7V`>*?W%Pf4_+RUiB6Vv*}IgmN81w)eWAyQAH3bDgZ<vQVI~2S
    z>6aF8*R!pPbv8%@n4>ekc1~G$zmIU=@yJ#4f#`9&OX-^R21R8!2m>sj`s5?D^k}a%
    zJlt2zB2hQOB2KtY$cbZgWRGvBGM}N`y3fvgXT#A^Ej>2*)OVIrR3Tg5(aOp$dqd6I
    zjxa6JtBS0){Sp5$+m|uTFH%2ze(!gwf|!=k*vMUVyLc40KWS<Vgq}*f?qbd5wu+L8
    zWB*(y>!^l_g*#MQM!^v8+cbSh;K$bd4^>pp!gS62xhoe<!M&4{n|m4q!)Bqd(Kum;
    z6I~6<E}&_$u3?V_-8xC4C!4&lru2tr7?zt}hV!0=xa+PS`vx>2R!;LnEPNi$9TlIX
    z%z_DZVOH$W4dBP<0DZRTu(?#4$PMxLlu*{DR$N?5F$ytKcy8pWz?YxaRWRHL5Pj5(
    zd}}h*acaGOk#8@IT#Vwk9|^geTV;5UVTT^p;NCCtYovY^otH@%xR_L%;=X3UH6~BH
    zvoYaI$@TRDU^%E#7m=SYIo_Vv-)A}S6mF2QK#7gWY$JPN&9AAZV?8v9P+(=lceLmI
    z9e#TM8~MKfD}!c>;_2FF7*&B@@Ss5yZa{b~JTlVDZ~>#J{-Lu)p<AUqFw`mj96f|C
    z2=;SkLlfe26!GiVG;ABz7e{LFq-1mOhrbr56!PmzO@B_EWxj%#vRu_%=8-4e%*kD1
    zwwB}kK-v;W1EuL48K#qUdfd)XWnKCsMC5!z*eP$JwJ)v{b3R=iw59>R6;Ux4nwPXF
    zTh#rfB{(lIu{fKv{usEKB8^w=FVoghFkPQcEW&JbFMKXlSC1W6*J=Y~U%WmRpYRZ3
    zi+qou@g-D>s-d^9$lClsJ;E8kK1TkL-xDzJaaF<vplndqQNLdP9K^VKY~^a(eNd$$
    zRT#0c^~4={V)tH|YPx*^n9XjgI9s{^d{`Vg7eO4z8jnyB!4&#dKEmR*<L3Ey^}p0q
    z2)O#aaK0VMA{lU^FG>|1{>h1QICt~TiO%Gy(U-G~#>Z|=^R7W&;+Y7;d6(4LDW(g6
    za{Y{z9kHLtt+^B22MPpQ%#JuI?0Id+BXeN|*~=MDm>Mr}zgx{O=_-iHvgw^n7Mh(N
    zhF$==g8VaY(NE|?G>$8VG!U-hFTI3MWV}Rn35y}QFLsCIcRw`7uH7rY7wu^|v|3b`
    zD%m;?wNQLRV~#3mKqH8bra$vujM2?iq6WMmi)dXa>=V+|j8q!2-C|o%-KcS4?Zy3d
    z+8h^1LPJ_aLt5)RY}F#CpA?sLu0`tT!?LB3-|u9nk^|jLs!|_mqWONOE%n~^;nu(|
    zvu>?Rl#Gp?YNsCHp1=nX-@TMlgN9N~yHn5^#AR>tRPnoMIUdGVku6Usb0yU4HJ&GB
    z0>j-q-A~a-V8C$23bNo6#x-I>GiIP|N%F3+XdGE7_#kQ|XM3TfOZ)WjEC)FgpDzIA
    zhntBD5VYo|M@TQqV0&%nWW%RwpEh)*{p5d&=ES(ft4Vo1JE>ldGU!9cv28R8(|>vx
    zNuY&Sewuyq(zn@0+%z5FMyxk@O0zqlvdbSPVltafFyW@wjyxaM^DZ~fvERqNSKs(B
    zSyjHUlmx6G^{e~I^HxUeuwNa~MzB(y*cg>Jv3fbfYB5$~TGM=|8WFs5sf4P7b;ytL
    z-#d<oI>glOL`Ng@Ze^3}LOQXv5B)Iv?*^b|#fb9}b&T@xhs{0N$_?f4DXqTAszNGI
    zQHQg+6>Zl^lOAF`$Iz$q0`R5p0ubf}oY$9E8nSD$OvTcz94=<y^LY?5qxU^Vp<-y+
    zJ$60xWJAGV4b$Y2<4;(--BS3@ukr9ZYV>_6YCc1#nFNQR`7dj(8$-*E&npT#E&u~f
    z*%yG48p@A`gug}asO>I8AgeJy*QOuwE971PfL5DhL}T~{B-?Wz*fib+Bg5=0uq`1>
    zWBNd*3|E|KOauqyxvPpJ-1@^%iMPbx%V7`Ux1&}Ep2H15o@93kmx2O1j~uky?fBj~
    z0!3J(w%p$LJ{;Fu*)`l8cOlD)X}GKHSsCNe8b{Z}R?PoPf!MP~uTZIHUaBE5&`Xef
    zI(?oQYXc+&6lTXW)Rr_B>9K-h39>|_+KDAHw~TQ>_9HRg`lb1Nc}xZE&s|9m5n~?y
    zeE}du?$WJzdJ_n+IOf`M<b>0rAyl$JPl?uZzG@F!^Hwpm68Y6w*oTUdu-nQ-*`5w7
    z42jAy_1@t^P}r~7i4ol%&`@JCY)NY{l^3|Wg3zAjiF^RlLujd0CnLWZ_Xlll)~C`p
    zFZ`^m8q0C9%BYThxVd(#Z(-)Ksn^RNPn+s3JNHiHkG7p4YO1v(6@?2%j|Q!3-#;H9
    zpU!68ByuQ9u-;h0rPfUKQJ!|H%L;p+bolt5wEpUj0YOdO6l<;i^aX$>GPP%M>nPp#
    zyt4`aK}Q<Jn3EF8ddcnbImsVm&4-n?thD8&WsjGm#tB2j@szBH(n0yHBuT;_!VLP0
    zwLkbu?<PbbzuLpa#&K+OIRsAu$B0Yiy;i?yzwpIYrK@PKR~b0)57liPW-G4L6NV<p
    zIz2Yi2yS}Z_a(+lHaz2=TS1(Y>j|M1qo8zxoi{X{%j%mm$ZBM9X>ZB<e9~D&Zfv0=
    zNW`#ceFh%5itk3gW%9iu99L7wHrc*dTRb7(N>2na=eZ1a=kZKP-voD}@6eEFzSe7Q
    zr1jE<JD=@@{4T*oxVCjJ#ZzJwLp<MnK~M{_{l?tc3ZZKeEB-|fAxG54Y2PV}=qpMC
    z(1z~!R<Wd#49R&8`qLjaHOLAJI@%F}coZj`YKH4=PV(7LlTQbxhIh$BB`Oa=Iny1b
    zoWHWLXgYpyh6na%GTeQ^xMJh}oBpg}35-e9G79@iniY2|OCCQ_;6djGC6xuGpt3BJ
    z9>BaQ%BMl~1MiLk{SL4(VRXWSg4;!Y(N=mvU$I%PvfSAYBS1@srm;HQM7fmbb?r-R
    zV7%zF3xMuJ8i$azx!slEA73Mtbw(v8aAE8WT=KNS1}=Ejw+NV-Qo;hXXkR}t*v6t{
    zR8{D<a;PYq(zdRTE1H>E7qScXVGKOH0Kl4`%7F2(^BKX?oP|a&ZB0;Q6Mk8s$Uc_-
    zrWC9>G^wM92qUhNw93>NdRGzj+*WE4sA_~`9qz)9a*{pWcB}X>aU>hp!I{PaeG@j-
    z?gT7k)Jj>()XOeV@>TB@^bv5&ZHqZl10K7STIK$1_GHV=Up<yM(0wK3*CqI@M6{lB
    zytfMk{;d2Aj7H<NAjZ6lb?_dcF;|FVKuL-?nX`r9L})IweqT#6+iKHdqG9{ojpeeJ
    zN+#3xRqiF|LDU6+4Yw-63^D1#+rj1#Yo;`vE7+Aw_t0QaPPlMdE8?JZLNW8iP9m)f
    zc&H6mu=`4Za#py-!X`%Og}ckUoWdaK8!}Xj;FlO&3Rzap2o6~CQckysH9)WU5)Y5p
    zu;N>icAcmEiQBG8iR~nMdt<>#>zFP+1HxKSRLk0e!=~V3xiCyuVdBdWUp^flA^A|F
    zvA^XhIf>SOB+gRYmm^*ww|qtgwX%wdR~76#T~)mk^758dcDW#t2l=<?wjpu9r;*p%
    zm%n_8eA=ejYvJZGvyrzDhtf^e@a<CfVqqn77P|`fZ8eFZ&8hti+ECxc=4`#TmWLl$
    z=%-?P?#|YW&pnKMcLBJ2&hTO$&B}8gM_zt}osG??B1JY|!6MVweD(Xp_;!R87%f|0
    zuD5+bK2;-0hkX3tl*lO3t)Suy*gt<X!c5LVt2S2OS)jTryN^7{<p(ow1{A&xHh2Tv
    zMt;%<mUehOsT~n{F{^~}6G(O|Eu5%kE*;NmnmUA;w1^>fs{)5uZYU$*a640h{5*AD
    zFK7;h%sD!9Vky+fup?m<f8Z?o(=(ig;?&~tpOp}7$)!-Kp-+|RT2|Y#;hP}vH1S}V
    z^)r*a39*)<b`NVcJD#GNWaWHKJN*lQrwXgfI4f(zN#bY%S0F<5$XQGg5eQ$9DX*v*
    zbp|QD`T?CtX64@`vyHSaZ)Z=ul{y0Dca@rDPqcn9J*h`DO)N|!5gh8Z>$)%@pt~Vg
    zsWZ{K(I_ljn8Vc_zk&Z1b)ut(Fvy4`^)qaxFQFH#gWEe}l+dEx?PAUNeAKgwx5s6q
    zsJ_Z&=!Kub=GL!M#h9-?g5P;kpYbZFp0w9$#C*f7u7-DpO|Ol8pMp*4_MnM;muAK=
    zu6;+L5sjDQdcXX>^mo`wn{Pl90XxXNdV~%7;Y+0Q=v;bRAI?VhNbH+ekDG=;8Gd>V
    zFO)}%K37VH%yb1PJWZI$Ek6848Yd?J(;zUOq|R_k=hn5#m1kx5<S&#dq+jr*^MR!V
    zl*1j{HCuLchZ>tj8$bTqNQcJ@PYQrU)(;tTrX{m!ZFz>{Idfa$BMg-I80o_Mif2>q
    zwA%q*WGKzmrE;C`sP3JLn&bm(LnWDYegelfwN!$sBTydMW395;QZv=jMmo|BM?k+M
    zW?2Mh6IxiQ=d!BW`}DJMRvdcsjOF+X@+|!_43C^3*d?t03}fEtWMQ|pr66bPT4AQ^
    z!2szpO3ayZeXi>>@9xH+r=P?(cP<qVqOGV%*B7^p@9&0@B7gBwO_VTL|B&Af`x8r}
    z4?g`grc)4Z$_0RN&cS^&JZ&&nK}vH5rXFj2Xs6}PQp-F(<sO~2La81Kdz93XX;WoD
    z5y1S_3|bvOO>eo)0-4^{b6>C|*!d9CRUfbt-b^hkM@U=IbpKQpKK5>93W5k|!Ferb
    z_NBsY&ar@ecii1Bk0_3Q(pi9;4A5IzkCTASyz0=KVQg=U7n*8lC1k~H)tz5hXDHc2
    zALCIqJg%ZL2AOJ<4qG*>Bo3^9262mgF_QCM3i@TR!istf0#-d{op*~D#mvH!a%o)U
    z#oZFHz1<RlqV^VT9zoe|2(@Ul3#m+_K={H<+M4d4KcY)rPBkg&C)=KBkLt3bO`{kt
    z_LAyg_GGQxg=J`%VU%~F`bQr`4mwPuD7P7}4UhjgZeuX(U3b{5T|GN<^>DJ;X_?d4
    zp^cl`-h%Cp>{l&%@*xjc=)cdP=d6A9zPoncD2UI}>WNu2r+wag=I7+PRelI7|8_4r
    zcl$%EzHX~y88D8fFF089-ktBhXicwu$7g1jMshOD=GGc{5C@OZ6DU{^5&2@tIo8U8
    zdvvAnjIbB)pRnZkigtY0IBNH4h&6+jc|Eck3^CZWW7et6B$#nIojIZyC1yHMRZUIT
    zpxhHX96IH9bw-DUpYA=&v<#{&pP9%Dd6L7_F`+QHzgt1JJ@+<%<i~U554aRHF@BDv
    zc-ik_n@A1rSDRFoMtDCTe8?(BPiM0UG4M=~FxJV&6KiuZsacHbK635z<cZ5rTa1A1
    zXcxoMSrDIfUUFFT6}9~8P~xQEtloN=XEqhr9S{X#4tT(07i+G&Qd~iK%kBHxN=GAJ
    z)$s3RAm$dU^M=dE$*Ch(b|+gf+$+ht-EvI%=w29J#w4AXaUOz=zgx7DBA%XDgQGOr
    zmapzZ_Tl*c=J+?&ZA|y`+<`O}gk(mzUF#uZAtC@|-ZG{!0^!knxcs2!E)SZ<-a;(?
    zvh=Slu-cMQjz9%J3cnZgZoo`?t!<qV{IE4<wbr0vVaj-;O>0MV=9pm66!H@+sExH+
    z{M$|SNn1!4^;%RfoR8Z22>U&!dj#`g{*#uJbvlyqex4~=gE4Ox58d9qqlThESWm*X
    z^hZ6n2L_Gt3hHjI=lGuNcUHgaybukQ_Z6!%x^<Qt012^--l8CX5DXopN42AWVj5Sp
    zXzC8^vu%9j#TGwjQnO1O>;wWTSL5rgRvzma7~@FcdD~<vYf3bM(Q5)Q$1v#~^6_8+
    zN@W2i0*bk1&?7Sv0XaDwW=8(ik`+(B+DtO&$bze#uwiMmE4*MvCqd@>>4SMg9JgH6
    zkU@}v{_}Ad3G0>-cz#q^IET|@t9(lTIh?vYL*{NGn)l9UFFH84Jlr4{IPXggTo;jb
    z&(oit&{24#Jic};!Johf&H`*b-kyh6C`8xF5{#<0fH6NssV@sod|<LyPJqUgD|j&}
    zYQYXcpwR&nA?T6Kj<YATSH!|MUp)5T`dH_Ln`Dr*7&8lJBQX&{5SRWm!u3VN9evZG
    zt!c~R)4Fk96XZLJa0vGRx;+#BfDc`uVZ_GC3xgpq?|e7ZlNO-&r+3US`2z3UR@mof
    zOlyaYHdTD1wM{_=zj#24%?Ei@HK=pjV*}k^?l&tImZ?H?A+C&dHHvbn5a+(`SSL?r
    zcV;u@elb}|ijxIg3aeV;ASX}Iz3=PsFp6?_NOu+Ylue{=JSmF`nyu#j>>?sACrK-v
    zgdR%{vVmY<ps2wUE#*=npGZf-oVw4nd_P3&^7N<1^#*vrEre982My0n{hXtEu~}(9
    zbA91y^gS*sXW_%)$)q%&2-yQy#V1wdqzWgUV5v3rY$H$mOA2TQX^8iqt$_vOK;lo5
    zw>JAshe}@*y!z&09Mk=TNHy|t;>e*{4~S07GG;EfWdJ6Akg_ZL!-vET>)Qq3LB)|-
    zL%p^WmT+j^>>l|J+Z46rgn62YAIDQ1YNqhs&y5?5a|MDRk3D|^vnSt@G`F^ReLR{}
    z8Rxd=<0foJ#XnR2+&IU;og=4dO(RDjUs~T{M2sU3GQbv(;spMn2eaP-==~8TT6)0M
    zY-q?FCx3huk>!$~ls?X)<z~@CyS0hamuE3P_kTWv&m7nxTz4P5gewM<44w*N*G5F<
    zOie+{m3Y8iMt0eXmC?ze@5RVezxBFCf!i1$ss^J9>;bHWhaTs5hG)MN-nn%;D0i-B
    z>0*fMg94-^&q!{AE&4oxHwCh0*45PwSAbMu=gb(haj1zVmPLR){>}pQMDypR+ppC%
    zM#TtEh2Y<UXW^RJyL=!blKI-|Ixee7G;(nT&=}P;R(?n91;9ww`8FG5>8opZC^HjJ
    ztF&Fhe1A=M&vFE}&It5mLw$H(9+SuJ&QDUF3joTRYF$xG>mwtJVcSjG=7{apNPd>y
    z4Gis{M>#J~Nq4nW!54rwmrA+aeir$njFfm;I@V?IVuTBu{acrB@oP!jK3f9EnSP4D
    zeC?Ns`DwGh)G!f#<d}VXPi8^D{LTtmO!T^fJMCs=)eeXr`+*>*DaS9iOeQp{u5<BP
    zfS}O;3s49;V(l0@B6ly@KtZP@v45rk`MTLBdtyqTdT?iR%bZAeq<!?`7#URVgsMzt
    zE||VkGif*8iZ(?;Oy*Fc8ijNsUV*rMR5xsJTxSjEkqeo3M<gNK(&GDk{L-gV9WOb8
    zP%C*Iy>+z$E<#IPsVMNrbKikCZ@TQ`82LtOvPq}=I@Ca3u})Q5PLaZRxE8t_K)yQb
    z#AOHmfC{W|;PZwg@p~JC!1x0#k9+)Gz`U8YgA!+Z?Cu$hY11Llpyjm6dERpn9t8n~
    z!hF-6_4|loosxP|AVthPmaJ1isNUQ6j@hexFAw>?1F4rW?U2W^_TtrK>xWjG!!HP;
    zz0=7^cFc6~lHWAkbL(c!Wizy(JxGv&sQ|US!Gg<Y6?CBa-$(4jd;Ei^DDY&hp#^w}
    z-vt0FVm~hoaaE~G;{sb5ki9E1_m_Pn88QH}v-fmIo#?9hInmZFF>L_*@^ir6YQ9kX
    zzC7*Vl=Tlp4#`f8`-wQrBmzTKh59U>y-c4k<%dn{Lr+%@CE<@o$MvGF8cgNsJR4H6
    z;B)Q4HlAADU%n5{SR%C5M?83sVnx_Lt#wfb@j~<z{m5^-Z-ZOgxRf4MZDmY0`_g4a
    zwE`bC+SnCfX`4srkn|7^_KCI>%JYc$b{LXb8L)v@nqj<E!Jo$J8J!>T&r#!r28sP}
    zy|cr@-@yj?;p9!qe_N-G1%he=)&2&N!!P{!T<aCI#{38hyRX1JHLedlB!4t!ZxHS^
    zc-~Z)_817!Usroand);mm=VIP+{d2!K}N-xbf)u>0~cUG?yxDqx6}^#HBdM#N!n!2
    zvw^MOLMvT!3@`IUh;Nqs{X&n$l9~&ei7Ru`Ns1Wp_z|5M@^^rbDS?Van@!vYj<b}f
    z_&o>D5p<v2fNe9-?WPL=W?)cOQ>1$G%eWkw65FEpa--4OZN0~kg8V8S7wX&bQaZVu
    zogwL;<9#b#0^!N6U<A#9F#<TJ^gfMP+>#P@p!wzkU~N5AqY(|JlL~>&P4iHghC3i$
    zdwriJTZioM)G~PbCa%t{6>(`!K@2;Oa!-IAzJwwYRSAb4@tO1F*1yv>)JHzFZ}t6N
    znvjdeBijZaE{89K%kybJ#__{A3p)GQWJGyyRg9F9*oR9IW*(lagaxhYW3B(iThUEx
    z1<Iv<IZd4y&qzg7L`dXS(Z9pmyqr?QdI5Nwx^L9idt&X94oztHy=!$W+VRc~_2<vE
    zU)kyYA`ddG`34Qv#i-@q0UJ+%vgBvedpOV;%cqa0u!&Gs*y4^3Ke?%#DmZ&2eXVXB
    zoa1N;`@I1)0}rT=+2G}P<4<U{ZVl}xu#Huz2t!8J>jR|Zo%&<r)47<0dsa-|?Jk|4
    zoT;h*Smvvxh$Wl;VH&|j-mA=A8p8%>h-qZN{w(P>)`cST0v=ypDB6){w{0NO`;3vm
    z1{DgSRpy&SOQ(yc!WD!W3P1|v`o+f_<K_BNrbY%5xVhqBkIZOh@wNSA2YSUcl?7>N
    zS9n(_=vA1Gf-V5UrrVmWn0%F7<IQ3wZ1;q|pufSa+&6T8Kd7|BiN9sHCDz5`H@%GM
    z|3<%wBuQd6qZysxG5NzC_vBYx_&|DZ15a%=#p;lG+Nw19WFmYfEZNwXtf^tiGb&ya
    zJamgJGSS9dN4cIp?%xv0dcUZz)9t7xg#vvvr<&kI=gTuM(g~H)LU$gDCm3#=_N(bs
    zH14uN&N94h=-*;#R8&`2KRw{rJambV|8)dRheAg=Zuv=Lm!1W`nXAg!(>JQ<V^{y-
    zbWl^<Fu0h6{7;dX*5f31Vh`t~hQ9cwLP?8489&jd3TXP#%k}p<CMD?7#s~RMb8&(U
    zbqX={ru>VJ+dEhfm#sG33de1de<1G1ymy^R^x<cpx&XA$Srn`te@G8ZR!Z|0TJJd#
    zZ<A+nfU#!xZA!*0X*4m~<$bfZ7s@1E44bFBbsAw9Xh1bm;fpup7TV^j>7n_-wZuwS
    zD${Q1Id7`)$@Asu{mQSVZb^FzX}&y<wrhim8r>g@dX<xkFhHGI1-%V8bE}xo_D4<+
    z8&3*#?gD2L<eeDT3?BTb7o0nL-ap+fsw7icr4IzsLHQ1klVQQ<%@irg)8=o=);SFP
    zr_SRDwoRY<JNe!_W&EUruu$KHj3PHG=K>{)V7r1=rqDB*Rx+u;XaY7GrAnE>SFINK
    z6P2T1ZWG6)d@iX9>Sa>osyk<6izCJ|^ss`Stb%>ndRITCT9<0ceNQP_yBl`GKAXO|
    z2alZ9_>faxpwkXPWY9cwgsiL7xx2DjNB0p1MBSr#K5b)P7vMnRUgjq&t|Yr#r4=x9
    z!q7bWW;I%AS>ia9siCi)!JJlxbMa@j%)x2P%UdkP7}(;Vf+O1Vofi|IYK+k9D16WA
    z3fHw*ojt~Cb21-?VA^d{=$IzoLnS4W)emN~c!g3S2Oz9w81f>Y)qkNm;#4Pp!aOCl
    zzAc;&aze1$((6S7@4#HE;?6&Dp_;Irs8IHPv=Jy~D6~+gsVWe+X)@n==R<(dnwvrB
    zqzUcb>JR;_FB8uvgfj)H)kmaKtJiymi?`toFh(j>yDiD2cjGhtY#Z%0i|0#Fn?G##
    zW|pc2Z?yME&LKoZi(#uU;cxbKIeliH_A8$Toj<xxtZTX)z|7O_Y4@HFmQN{RO%)ZT
    zo43d(*>&-F1Q)0$O#7Iui?QwAX&a#Q_7h;?>4B)~0r83^dK^OMns6tD!z-%_A5!83
    zCnzWC*<eSt<^8Bi7HK<+an8zZ^+mNzdsI~g)L~`i&U>k0tepNjK|Si_R_SlbeNZ_|
    zfcUgjDWP_;e33Vv;oaW?cD+k`(k&MN0udSdAMB7@ZK`^mMtYVk^=tR_Ik;bv25e=a
    zCmAb$4w$D^?x|W3o+x9Zz+dn(M(v-?2kTT3q6=Vi>92IIn!uj*V=GLn{4!VA_{F)s
    zofw+$@Ul$AE!@)`#NI0uK`<w6{K9*3j9;W<{$Y@g2A5)c0_mQZi}RhF$tNi8>r#_p
    zsV<@WhKc=$7C0`;KQe2l?LVNtI^C*I<crc0=Q64#&pPeru(y7EuiVNNdr{Dcf=ako
    zOB4ADGhkh2iF91(=mKCK9i3PSW#pRstjYmV(8%UFuz%wd&e;w-4<|V!Qou*|!+=CY
    zJ6oaVCKJ4w$R75!sEVdm54&Zdps6WH5^rfTEyoG&ct3a0aKazt_v%~bg#P4&_S7@{
    zNd>V_EboTdQUq}wxg}T_KB5+s8nRRyKOdXb*_QpyGDH9|pD!8>-!mAj-A=X}@Q!@&
    zAkeAwzUPjb(k{2fw?iT1uY>&IgAHjVjs%)XTT`2FFf-w!kwkeguQGBKby_Q09Nyti
    z@weC|{O6_~HgEG-7F9g?jSx}DMnv=|46{3(V6ZQQTkxsqO&bKrN6|zO@Nus0RFpGT
    zv)EF<MU9Kl_lv_W!_0xHRWuHWsGji-9<Rpr>kh!=w@(EX#btI~{kEMZ%qoW#8e}S-
    zKsHaiTIJp8%GbfowCm;dijV6mhBh7KNle^LgJFWGxhBOMu0*xs!F0O>d_9XWJ)ZN2
    zgC9c`XkOM<fuB%wJ!WJ~V}9sd{i_4f8JrUQ7<Lwwp-KX?c6B(2uvcJa*rLj2jW6gH
    zB~u)bFd%ysm$8^tfqa7|{XL{aWP|O~wAWzw-e)2%nTZ+efO;E}ujoQ6B2q;JG5UOQ
    z)_d;HiWO2g6D@R<sEH{o;xRxiTymh6#T8-ol{?3`eGX?FS2Il0yC~&O+2JB-EcGGX
    zJqmU5ZrhLv^$}aJ7N5)P61JG$yC+WZyeEQZ%geNq4+?p`&*svD#qao<%LBy`vLf=C
    zlN;ey_IBB;<&DXr?Xf-NeIw9AdrGF)Sq6&H-w1&39a$V?8(;S_X-chs&Lx^C4P3CB
    zIB9KU9%ZR8yo-3Cg(%G*|2dE&$(C)FyH-ofp)XCK)=H88CejH{lTc?EX8b~=E=6jc
    z8oq<?mU#e1WZ<N|o2LvE$FAqjwc?yW-apnJAR^_4h^2<u@tJ${VfKl|i)p4nz1TYc
    ziG<+SVg@P-PS5>5KtZSbgHpVl;CxSZSINSPGeeW4k*y1W4J8(A0^!?Si`&d9E}u5N
    z(AxkXJ1_<3eq7z7TM1_0nX4X6lO?oZ;4UQS`9gJfZ-)e-cW@xjocesP<NRLZ(Bsm&
    zL{<<lmW+9t(+Ou~z-artb5t#FuPPpu%2Gi&NouM~1YX`u-1yXt))4XyH=uo<j(%ho
    z^237?+by$grqcWT^T8WS3jR2lFTEf`nO4=bQhb>=R4N``@HV}{ye0n*m7{^(?(*0I
    zW$PLH+<qDt^47+70nmnTjpojiOMJ_me46xsQTLWXaW&ne?+_qpAV3K21BBpigNH!}
    zcMCAM4DJ>nkl+>|cz^)G-Q67mLvYvNZb5P<dEV#ztKNI-+&Ul6e3)w6t5^5#-qO9+
    zZ{MeuUdLIYUVck4)p31a&4r=-d6GnF#Q2$w$&wK3*@}5>y#-r6Pieq#<>|q10C9_t
    z1NX<v-LH7XbTpdk+^~5r0&A4m0jI#TOu-#Bs^~|ticmW1B3bqT50T!Go*6LBi?Y+U
    ze*8nUO?;4D2`9s{4(a*6Nyc=kZPBgh_Nm8R0$;#d&b|1HvahT=VM$zVp5Sv(uYD1{
    zIW+24nfo<bs-_IeRPh~TPX*-2mgQm5Q`|N0JU2vpS2Qp&P$VB&<8asg$&R0xx9{ZK
    zL5`L8hr^1A9AdO%!({H%Q*sqV6ci`ic;bLH9+lj?Gt9_uXwYfmc7k@J%~iT&>tV}j
    zI!u%sYq;|SYtxOCDMTL_>Z*RUEs)8qW!MeBh!<{57=3j{_V&g2HqZ3-qS$dz-x1#+
    zSNWI|CHNeZCqH33f6*-IzEnipwHEH@`>~MCM&BmS?I+AvQj~VGeT}o(2fdVMI74Ye
    zDf~A;+G<PJbu)Uf<(7@k^gG0%AkBKqBR0u#ltoQ3??W%L?rFw{u)IkI<^qMaQreXs
    z(w5{(IjW@in|wq3v!#-7sH%y1v9$+Y&I!%Omk*1u;&UAki%6~2a6V=FL1t+|S@e?L
    z1?Y9neEda#h*JI_PZVri6R};IB{Ox@GP~i&g-%lsFKgj1(nIX{JnzTsryH7fa0buS
    zb?kG=#t+gYz-dn?1G9Hiqw`9aY=qiU@)r8J6I11m0xA;};`dH!=QyJzh*auf+FR!-
    z`CQacY=3tXu%}REq&=H=7#QLr7W&C<F|FNwX=Ul_xt{ZJ`=k80H~f*_la%>QeBYhk
    z%)GUkc7o*h%lh~2DLZsgFnyg;8182VX=Y&>mhpC5d_JD?$Q;{-l`c`FmX=JS_&1ac
    zr<TDjpkwd4vABH5!$DlnBneSKd^`6m&**8wJLhia<zsu-scRz!=5hzRrqh5J#C!Sp
    z%b$+BS!gX(Co`RVzoL^1XAS$;nD+zl-by{&8#jxWaovmM9nu+3%;cEJ6&?yBkIWWh
    zf24ctTB*~Ha6@;)rMd~&V2og_SNK@!0IP{;l@vw?W*o!tpC{uqxvCj1j=O7d6iq6g
    zy*MW+9Mflwzjxb{mOm=bIXnaTv(GAQgp_tAo@n=&fViGYJ76!?VTwv{^32jXs!2Ho
    z50q=2v>(vlDeQ|<`|)eu2(DB34+*dttD59m&HIe{Hz=4;7N8~-(ys~|u+8#uzhGF?
    zK7KWx2#a>(9V=g6DCbt_e|w@7Fwfsvw;SMbhcNJ}3c4K9H6W9_MBg!8X_-xW#8+{D
    zH%B7gKqa+4Uw#mCD$l^=bke^bn(Rbcbn78Xc1y=L6xO&?4IfHic>YpFZq*{)BK59c
    z&06e6G=MJQ6!*O-qc1Z&!!0YA=xokFDXHr%ffawHZowRF9MHU0p|19P#WQYc&2;^_
    zl)V;OeEkV~G)T^3KBGRk{J;+OF0)Sr@hpqdTsnsrM%1O?<w$S&;V4rj)Oa=LQ1Gr5
    z`_oirD8)>-7|DCY`WE_!sEm3Tes}K32h&Zw&2E2na_lwF{-;#|YtK(b-UlC&dW0X$
    zBA>17{u<Fy!>nISY1qRR=#wZhti9xkEa64CPaLvYG@gc5I$&eR=BL?WubVjP1XZzA
    z>w95-`I>?+?7Q*Gi#9iJJ@k`jwQjH@2|t&Y^k4^l-Qp5KSLEr<_xbxMubRd2DF-d~
    z>Q09u&%iSLY5vBBq@RN?lI<{9l1d%0q)AX6vE=rE#*QQwP1`-mcV2uA+8hTBEI5G9
    z5maxdPUhoLd$eJJ{U6rXsa>EYyYgRjm;6_1GbObvICHvtq%%=9#{_!wd($(iWtC`G
    zWnpe6Zcg;+PnOx)NO&xxJHLczVTCgDRwm|{cNI(;u}oZy67{%~)`w+v5BVzlPR}HJ
    zt9nGJ!V9Kcdz)i$is_s6tbel6M|+a43^%)lZhE1k4LDI@H(l;%h5L@-D`6>_E%;8H
    z>o+`<CHeH~6rI9Yd0)Z}VTtV`GbzxnW$Z~dc9x*QY@3SRq<90P2$Iq=wIhcE5ocyE
    z@_vR<dE;tn?6*Bq^5ff)OdB96znq^*{<L>mQGOyjVdc*d{*d+Z!ClEtGTyx3SPwKJ
    zb9RY{o_j@`hpnTsnmX=Kp|XhR)d~`Byz<j4R*Qhp>v4VZGAcSXDBFK?H<t;Kb=C~H
    z`qiFM6EOWj&?C>vX^&rRr7t-df3-!sDl1WswK4(oV9~cg8e5`6lPS`oaVbFJBs16Y
    z#q-&3XK$}pEE-hWRL)+|cJW2{vPehR`yOFC4=MO{k1=8tFX=<H6F^zs_RGIAUE6UH
    zCTmLzR(h`9`5As5F3<gxFd_y~IQ!0xxcZ<^o_{aqF%H8#ylp19eA{_Qw2}4RWE!#J
    z7`hDlQBq@1^ro=|e$mQDBI);SsT*noOva)Un`c|k*t9^A#X(|Cy{I5*DXuH9L@bH8
    zhUrQ2&9vLcOj3Plui3eOk*j>NspU@}v6kHH_fn&CqK!po!It010ILq^*~8+Y=;xbr
    zpg2&GLoGK!uGk;m{$j&Wa;G<|%6uoJAT0Djp@X2ct7Ky<?n_0=4?L-20SSg5KLO=Z
    zsq85Q2q*Z<dz>Pjo`bw&UAL8Egp0;F-ETn9hntvgr_q9W&yBt|?7;7x3}G7%7!lu4
    zoEpv4f&AxXpgs>Z6<3s4&myg?+M~*KpF*}$-jz^)rc50ptPnp|#bmxis$^P)f)rqb
    zH0Qgh5H*=0{aROW7JF9qbP?EA;bH&Rz2fk3G`}`SghoWS)|Q+^L2ci+%7xqa#?l_F
    z)wNAWw=F52fLJyzLgjZi`@N*1A%!x2C{6qs-^8Ew@D=(HkbUdo?t_ueV!UuLCccgc
    zAt`&`xb|~Wun0V^u)n)9b*7AWZ>ZquKDVpKm+iRDcP4#sIuJz*qfV~IIy-vh4|6|_
    zGcxqppzz<86803G1muobB;A-Iy*}1HyML$i;;FbNs)oyCBd1PYNW|V@&e8Lzq~CzR
    z(LE!!COo13uM1w^D;u*$+SNjrlBr&o0GV_;v2g_yeZR?-A)b!bzlWdSv7O7p*AkQX
    z>!aApRFq8ps*^r1;L-7XT*YqArOk$3e;C%z1@+lCBP`nV>HRZ?o6~BSm!e?~6rK($
    zUghO*Na!Ah9w%yxsdy6SEfXcfXBsenJr0SuG-`?h%#=B`?UX%~t=w2&`I$9;rFb=j
    zqN7TS-^fXq8at3!xcK8lFj6Zy47J@$TL$UXk7fCZy67cQ@tlp7UyT>X2cvgN3-_;X
    z%2$UEEqs35I9L}B+pbe#NRZF_l|E)(!Nu9>g6P9wz?%5|!(j&=8{i#(l01nA`vIiB
    zXFM>AfcI?}hv)ffa(5QxE+xqXFFVPNcCVhI9sH)c%eGL007TH}Lh=3NvCzIWBY}yt
    zm;Fekkoii`h~mLW6{a1&)12Vb7JUt`Ja5cnn&<^PC42x;r^RTK=r14VPCi8MOFhK5
    zTdYKg5=?*~C(S*}f;WB+C3<A7%nJ2C>@Q#0<q;^rP7$ltW^Jgs8Ek~>AIpfja%Yk~
    zuOBg%HsHorNk-H+)~A^B%iH>Q`pPfn_S#;yDuf+sEpO6aWjXOSrpnTV6VU0=R;ad#
    zCAHc#NUO)mbAC(E+A%tV8|mIF978)#u?q+LineRgy^>;NTI&LsseaG>UI$b`RKWkk
    zvqi1=m$=Hw!BO$Q7(IX|6;J+U7y;xfkpG4y5pWor3Y332K7ScQ2sREPm5ns{FE|E4
    zQWF0|H$oD*0<bdvzqwEU&}IG)|9@|zbo@iGeIu<U1wcXuzR{9<qlkdfAOs=3(Ncmy
    zaREp&b`GW{cGT+TuCCTFOBaBaw3@mkf^371fDM5m+G<cF0PcUYYZc{`z=-tc|IPE1
    zLZl;(eg5CXPALg>r9X21n{Ea9M-JuxA4wOY`B!iB{15Mk9)9)=cE799(&5Lr`|l2s
    zf+)X1)Q|KB_yp0=RrwSCgRNColm1gbk|rYjMo$^?C#?H7tgi8&{s5>$H2$;|=?^<g
    zMNQ*x`9Eb;G_)lV>F5%g8h>e}DjMpti1<^8^czI(pL{9BKlw@$8k%YniU4UH6%9lj
    zONm|LZ~oshauA(=>cM`eA_r0WPn`oQ8q!*SWO1<l1?&OTBs8QEaSX1voa)k2{~RtW
    zrSV5TnzDt(-vkvkY4yKl_#rB9{{p=bI>^5HqdU4X_#d#SikgHrBL256vS3A-KmG7m
    z4=v5Vy8crN08mF9@NfH7)Z{h5h_NAqKp{&hD*PD(f2l??Y7$x&*Z?F@VW);zz#af(
    z9ylzgtR`pi9DpSD?jIfh{YSvB{=d`TNNC8T|D%HiCx@K6lm_H~>Q$2XQ`aAumx`K_
    z=D)hCOG&8w+XksO|5N^rruv`$#ZXdyt0)JN`->UV&{I}&d4iDd2>>8;m-<)s8$F1Q
    z1)@#j2-#8^YJb{}`Y+L0MFXPp=NN+MKjY)g-@5*eiNA9N3FXgR`O_Z=-&n=Ja|Hu{
    zq-f%7X-<tuLCBMkRG0cA8xaKPA(H-trO1OWv=HGxdjB~!5FIHn6wz)^&OfCPaS4^b
    z`uyoPi0(gmUjLJ)`$xu~Je7a)(47An769k}ltJwH{QWuqg|Ptu&OHB#|L2O~%=b@R
    zMOjhTg8U!d#Gi)wh~ha$wFfa{iP6aK7P%KBG94dY>OoW#|JKKsZy}+ksr@$`FGnw-
    zrY-Sr_@C<r!1Ld}&{S80{OyZph`Hoq?o16acQr9HaWz3ytFEC2LDZ-y4N>{eoFZ6M
    z*Ohx?_n$se*VXtl?*Qt$O8=f8B*bZx{!hLqVq8o8C;S9a&!6iQ06{a80{mf;{l$_Y
    zqaY(_Vd#JOV5pdAXiw3xF|o0+FtM<n;ov`ihJ%NLh4uX9b3B3<goK3HxJ1M+Ul8NJ
    zAbjyh2of^l7!*_tR8))?&#<1o_&@&r{!27FK_Wm#qWxb)GX#q6e@8U?-2<rozmRH%
    zP@C$1OE&w{uK(-lW=kiJ@%${MT{QdQhbF9EcBW&`$rHzwoiu8MHL03?q4lly`AOOg
    zyaVG(>uv3XRMxD@Vpw996hcWgZg<ixbuo;`1ly5;2wT(3VlmYQb1bsgK`m<m5zF~!
    zkn{+?`zJ2Px_ZfJSN#d@tBXOe9dEo`mVc3&GRw@JEvRACiY0HoeXNBV6dTGK{OAMw
    zCT%C$k2ysPYqu(eM;8PNMg25?_+<8|u~rb;2e#GFJu=x>e@K9F;cgkq2y-SYQ_O@{
    zq?$TvtX_}W_FItE%lqggWWF*xECi2D5LU_iF-yA+%YA;XsqA~IN}mKzB77}%j*}a{
    z^9cgw(SdL+LEUse)fc>-F7gW=js;|Rdwerltbdjemzij7;P9z}evI!NK40NzH7AE>
    zs23WUZSH&VnnT+3?Hoy|Zx!D_iiptk=8=}P5kEA9XHbt(1*lfyO(m}y5vDCbQo0~O
    zhb*$kUTwBP73G>5l2h_oqQMoUrIB=qJ-l_++F2G-8C7y*4k3x=)iw?wEeBe<PlUHS
    z=d8;C=}-k0R7!j_GE2NwO$te>CDEpf2O=V&w6&yEoFAz?HPrM=jLnRx<k4Jk^pzCY
    z<#RXhSNI9rfsFAVm{+C?<&a>U+AqyUtf6NM%nCrKfp2fOZY!6|6dW3(kuF!&`TY_V
    z8oQ$9egnipq&@pI=9AYo`KD?(VI+s+SugCJo}x38;!6xt_{8K{Di`T{87o4XLl!5w
    z*?wI1;THDV*iza`)+fP_65SDI=F-R_juLbNbcB*;{mSh%ajQPE<_?!}?4#IUaMqk<
    zwz78Kl90}RD{N9zQn+n)C<th|Y_#5Wv%TV)@!Raums9i676t+VxM*?F8DYdbgfR~#
    zb#P+RC8)|9n@N7nfuYL%OthIYZ@5D;1ny2tSl3|M3pygaw#QuISTfiyFX2fok@Kh%
    zG!A&hY?362LC%R5%Xcw;SspvWnP@w8<z@<hE7oxFty9{Ng2iFmE7@5J+dYK^ij@%S
    z=(LF0G0nD~dbm}K|EA3{nG(ME?y2_C#kjc8-L%hJNWn0R2Y}?OI;j_=eVBT!s{h@J
    z#IADvo%E+=Rj10aflV{iEHqH`BGWx{QpT2*tBeHg(x9tqL~;t4U|5(q-YLH4bj*87
    zOQ?6WXmR1z-;VWe5LByWz7q#8MDeecyQJdQ|G4zBE-Gdz$^&NdeXRl632>)%VNV$h
    zO0MLIvUiND0K<@G<reu?&tnBvuS=)A2uoBUZu=nL_`&L&+az6M;U^L~<u#cUjUJz@
    z)#NF{p6w8hi>BlhD{lq7)6H_%U7-53Q3{*w2iYgmH*6{E6<CrnqsH1_ln%Uj9i$I%
    z6;1qQmobh<Cwi+Gk3#+GxT*RWTUES^XV_KnG11~&1)M6`@8ZYICgX*^LHT12oCcSQ
    zuQ{A535T<bh$iy5o2Maf5&BOr;^gucJj-!(G76p`CyUkDEwpN0JaYMNkD^tgXXk+z
    zia|=bL0rh1D-9T|`L;pKTl(x1KF!&J=bpr&K|+G!PuXo4Psilbv#x~xA@9llwOcc-
    z*DfoBwXB7fn31_uy^B9SyXJ8*Wp2KXi}{qvRFwLB!zJl)_;nVC@J!I_yxyjA%oLHq
    zvL<_;usUFyX=XTQ!etS@by))gC<~To<FPo}oBJsVRco1uO8))Ak*TBCD(PZFwOwRz
    z2x!tT$~Tr{WNy8*3UAa9gY~SV<OAp_1w!g<oTCDGcGJCdNfPe7iVdxWF6w49_G+kE
    zk*8Wf4NPPi&%SNP0lTAdr@v-?FD7@<$a~+vLixkso54r{&^l#6bz5M)IybKLv^{S2
    z$1N>O&qiussbWOUukEbYj44&XIZoE^-jkyap9huGy{FXKlNL05BNVOcf;7&j^SUcb
    zEbWIT^;<mMgb`Qe%ZLG<48Fa`4Vzwj0{Q!mcifNuwWV%;1Z*+^tH%cA5pI4Awq&jN
    zx*I;l3v!hNS)c3;);@wLokdfAGA9u5gCh%kgst}+l;H8<peE%Q#2yU#-RFxuq2Yl-
    zobP%>T7#D;agqMC5$$1miQ^F$`wD_AaK)w!6Za%W=AojBp-fR&`lU%i_<58zq_X9d
    zKIyakY+%hx^FjNb=c4M17ig55ILf+2A6U!S+3eUe;z#sf61}N#k2BQwgbiWP0ajNj
    zd$PtE)3u}M)mj$9>oRnYN?3Ug2@7{iopSd`?N%(OfP5u4UWq3q5(6DD4rR$)Gzy#0
    zQd=?}^ayMFEx*1fP-$^JGE4yYRP-JB#zJ(6a_EJy2$ygij}}~Ei|w!<&HY6>bZZ=8
    zh(Vam*GgaF!)AaRCof8dy+LAbTp&2UCtK29S7=Jd_=?@f_N~#(gEToy&X3HcL5D3S
    z#snZ=X{{<&i&@Z9zyMm11SlN`&AVrs2STav!O0TE<rt+1>h6s%OHJv1;{D?)ejOrm
    zlw>aF^{Q%woN(-^_SS{~ooH*Xvj?SLTfq)6qBdp}6Fej_*vH{HfcEA7DMmS^!DZ<v
    zRl@~sqdQF#JZYUI>Jy~{ecv?p#<M|ca=f@(fTJgGZo#6Q8xQ|5b<FU*7GyMduuoZ{
    zld%9PW-Z2{%s{8?IxstM?p?e;0qU9K2KBSK)mSgFkWwo3%DNU?PdYQr=D3!YNXU8R
    zEv}>}Y^fQVx|^+p7r*UZsiriKzb|Cal(*P7Z_fIx8_3EDkc)%~uHwA&KC#iQNwE6X
    z6d@~nt8G^NP7v_2PnK#Y)j!Q)Yj&YmxFwklG!w1noHw@ar^FpIl-<-uq*n6$S*2_T
    z9{ZR$g})t)<lZ)yfEY|Z;av&7uAcQZqGH8IxZH(d2H_>3`Zea%mdwAbgCN|4*!It=
    z^$k!XV;yCQFJEZK9e%Zi9`AeLb#b4w=RGwXTGkKQII8zH^Yn2?My=E&Q9|$My+dJQ
    zxD++g)MG9xiRZiQ_75$2_=Hc4=}KMf+*mB1{jp<JJC|_CQC=IJ2F00Kp3I*+sw*dy
    zV1`^Pq{I(>bxKy1!odbdoX`fHlv3igH@U{iLXurHv;1<;U`#UbvW#hzf)nzow;>Ms
    z5Vd-yRnc|28KbcR&1Ny?jcGdWWV|1nEWChs_z<t0jtf>ZP@0YA$v&r{B@wM*s_|*3
    z6=op0PyH-1I<N^d_jQZS=Cgj@79f-!O1i!Tcc~Z{XVw7H5c6@a5LN<rOmI@rCJPND
    zfd&`EyM|WV91b1^1M&rolhtf2Jzm3>F-^?gt7i7<HLfMMtC%>BwpJ<pE>HCXlB1DI
    znj`QUuFIljAJv09b~PAd1WVtz!1anQid<f)h?f>_kbwLXB|Xn@P&o~{Qz?ZsF|rBk
    zfs7*D8rxx{4vgLyb;o!++Y9Ni{(RZ(l7#2|VMxrWr?}CiR-`vgHBBjuYSG5hofg6X
    zO#gS@m!g-)lTvrB`{(&w=;gqh?zPLN)2A5%3*=Segc%z{C;fnpGA{3nfy?qw>-sF@
    zmh%c{mc>c+hpA@>Ti}61y0+HSz6_r$sm+eaCme$bri4x%V_@a2eM7!>H~RERqhbEo
    zq*e4><0pf}8&=4Hu`>1QoxB3@HJ`N4thrg&KhId#jd{43eCv+p9-Lj4ix+LF=JE7c
    z;ZHe7>dt9%Dmd@MWiQa${WC)Rg+G?QPMENM2<WC#GP8YIc{@UrNrOx-<Aenu`AF|_
    zDM(psH~LKmiXOD45Nj%F%0wGNGUhA`qU4<Y1&8Ga%S^N1HyrkU{Z`HOY0xlywqp|q
    zSK#=-=EH}Lh8B1xkyTh+SX9FkQ7Pj>lEWBd?OnqcYKfwjIflJHTuWuGelN3aq$_cs
    z5V8aZrw2JI$pLW|H~odY%kpepkKl8x3&kd&0bM8y=fm1;x>M$d>FSo=>ZtLQw3c_1
    zlFf-SB1EDc4-oB^RDB))3FH(u>{79$4jM`QGK^cWEQR;ZeCvoPEuMUuH;#gS65XU_
    z+OWPt9OfW?O&AU-E=Rne0mYZdtYkD~#?+1(5KZ0sEb*Zp6Ul}}kS^Zlz@A3HbD&GS
    zU_V7&X{{i=Zpc(9Q<8KnZ9FJr7KHEZeY$7Es;rZGb~21Ya@IM!kjY7BZ?Hji{H5d2
    z5SI5k*0<OwkkSnPN;*%$f&0z5ynX77t6Ed+o<LU{4)PP94Kb#^XFBod_9h`D_?kL>
    z?sUwsK9?Cr@{4<0<0XC1V2LLDd0Do@!S1eXFRjgl#m!fAOD?+Z*U9UH1*uDrfv2q5
    zm>;K%tDn2XEjR+*cEZnW<j$SjLW?G+atmyP3|muWoChpOmg)>V1U7cVUp1XZm)pOJ
    zo+R-f!*lmC2|xRCdNUz|*mFomke1$Q73|_-QIU*SevQf^9<z3*913`yir%>UwoaM~
    zR}eSG$pCs<9Q(#wtsjRoP%9^LhtPSpS47SvszPp8yL1DMqKG(Ggjs(2dBb3tTr*t`
    z{XV)kPVKCfo=oQ6lQX~KjLK+{ZpNprSY7tyE@1sBix$0eYhlW$R|So5>o!c~>cUS4
    zcCNjuGcA;?8(h@OZMn4`Hl3fIJLB@|tD5VRC<M5-ISZtwU)x=pgF$s5M(xQSyA@U!
    zZN8r+2m`Ns*^YtB-j!F2w!1&&YCJDZ<HDSX4FXfg8`Oq_Al8dG1*MjXN=IPk>CpZ(
    zZZ6qz+HZ;l`OJFeXrS#P$jiOGuwX7A@laf^YJ=(+Jbb$0{rRZnC{?YsDVv#M0b_VB
    z3w@KLi{81Ul1<`>DY2P*qA?(##Mi_RV^q+m=s-Wc{9}s*bgkE+b`c1JvK1|+UkzLU
    z9}`!VH||E!G^c7k+O2CbLOYv3hX-G4%5{*?1+MT;`YP7&lYSv0IxYf5{swS4;j`fm
    zp$Q&%iFF@2ZT`ASt(3->9Bmz&Bl8%#5!?{y_y5qwd`3Pa&|}J}l$y+9dQrbOUfN~C
    z1TSzen_IC$*hzfV7cTAHbaT!ZSVPiw8XN?kc@b0Ae%QzW-)Y$jM}g_AoqPaJFYF}F
    zPSByv6#8geV`%M!BVPQ;NmQ00$kKv~CLdo{%!`Ad1!yyZN78nl^GmhtI97bvkkP{u
    z{4Z|J=h0Y>CAwe^i4Jn(%S+7ywKk%;F%Q++_VSb(>pntu!eYUO7F8i5uMy}u@kb4s
    zTlaYXT}J8);B|ki7DP{21bnC`(zp8QsS?5PIPmoj=uGfu0BPsrc$zW;-HsGT0%o_<
    zl~ZRY2{OFYsH##S`=Qva-6LJY9&KAiuup<wzcYi_O-4HE<E8M6se(AzhHalp3>H`x
    zTL-7vQ36}w`Z!oGm3h%%d-}{RBYOz;-hN!%*$GZg)f2u0qm2NwJF%8b>JLbYZqFkv
    zq7M@HVI1z>Xw)0=G!b;1NKOhr{nSas8hzl5-bvYbc(Z6-Sf*K-A7;>AY$zB>pA%-L
    z`*cp@h!Gi?ZZ<?TJweBVwDzI2GAwuYkb|m4T#jNKw9#r~^@!hi3NFU3E}uOdD(x;E
    zd3P*G{oY@0?@M97l$@8>*99wW_0La2MD<>;(OYVEQsWDZEz6t}u3$4q==p8KzAAa|
    zsM_;iR}YoxY)gZle7U$B7fpN_gV_51%m!*;b*MUY;;~RC4V)>HtYmC2$g_g1MHCPF
    z7)l=Cw}Dac1sN3S_@tl(Lqg}V0oV&_uC-y1(yqdhSKZsFpO26OX9|WFHwxtQ8`&vR
    za%}V|4!i}M+D5bwStHLZ9b+;YIs{OixyJ`&1$Oe(b^PZ%Sf<sxCc3u=ShwOL2SZm2
    zO)-QKdyG5fiZe2Kx(m8<b3Mt`MNKk5_qw0i6cnC|opNY^>pjl>Xk5L?4SQLUype=n
    zp6r%yJAgyhn8fqw+1*IIi;^Eq>T=`D5HEi*>i6Xg$x_-<Ga#-eNJVSY)ZtOk<C(C}
    z&mYKU7ie+Ps%c$KA?f2tEn)8D0OM#$G`o72@Yexu4}7jZ{kv1{<qP4NWQ5Q#&KuI1
    zn(y0blm^cj?~0VqN^g8|eS&`j@Na?r*AgTT%$IYfG!%2EqJ3&}<mp1NBP6owNQ)Yi
    z@8)O2TN)e9*nWa#pKplN4ZOU4S3Ja+#&3DcT?&?s^mZr49UP;u>^v3GV8Y~<@P*W!
    zBrYN6s*TWO4X3L%s<<oj4dIxRiuD@WlY_K2@l6j9)xN&ahtS7y+QkSh+4R#8I!l3_
    ziqb<Wsiwx-Kv0N=_R2^AH?C6p>G+z?R8N_aZ7MjToi*Bd{0&~!@p1spC49HeaiM|i
    z=()#G_^urfda4E5$T{A}NFGSN2XH5vi*vcg^_DGf>OGN=r|j9Bo4DkvBSyc7GVi)(
    zPoYtN0VbPpywwa5JI(+b%9_^#&^$no_nDaX)L5TYcErQpN+@BV<d?1u!~$&IIgyt*
    z_=G#g=FG3Wb@YY%9j`5)*TZpl@$ko`s2=IlvvSZ()}APN1}qgdR3?ED*?NcK)acNy
    zkiIaIQcn)?b}qo;s00x4R)<E<IRH~=Xji{Iqmkqi;qolo6S#%9b2W~GSS+h6&-1g_
    z(u(<ddt2fN1{S~iOw;!zumf?JcMY9w8EaGZzXgA_&6uP?yq|O5?+=#q%sO21*QwDi
    z--d%f9>uzMP_|tjnz&F|W%1ZUM|+HK-8~w&xpkVN6D6&5Jwe(v%T{^}5wv*&wp&a?
    z;^2p+F{DwaWbbEnIitLFrZJ3W&e+w?qHoKMG82EQ_rsI3`$6Ozbvv1(IQQ${Ab3lY
    zt$Ge2*P%b-Ctg1%3%xuL*%wwhF;LmdGObPHmOD#~#mR8VQet+C`LgsWvaLXG#_DW0
    zM{rk%lD(y3@<MlWD%<*S$akXXaua$wj_`-jpWVKJdRsV;yrdr^j`WAt`Uaog^=b2G
    zzDkpTGu~Nb*$Q@}haMDmAN_PB=IFdYd)l%H=ga-jHKgK^FhRC<!fUSSQcO2`FJWm>
    zPoQkh5-XWIqPfh~tW5K6lN`El;C5At+S_txW&k6RBfANsNG29UJ}(Q_tDmBcT$FR7
    z{TlZ=0ndO5#U(P~7I`6c{diWtFtKzV95ocRX_6+|>H?>*J$o%Tg@6v)R~+)fq?e}T
    zxO!;?UlBi*QO}eS>pg@M+N^2l&nt+`cd*e}&JzZ1y&Ffq`;xi&ON%%2>(gSCWPazl
    z*=&tWbrMm%O3;0KcRJ2*fM>J~)~Fg;e)JG(&s)n6^kK#%>f7_O1G@)Y%68iI0}lw-
    zgF7wiQw@G0*7+qV4+oEP!EmdJxz%DA+CrR$M66CX@cNxFK<l~AbH{v9;T^F&^=StE
    zediM}G|GHdBko7Df5x7^pZxwEMa#_jmmnzfK$-&2`_qO?fzJNBzMKi0D?Ip(kIYo<
    zM^pG_ji3Btp;dcG9#W^P7P;}JS^7r3Uf(oLjNHqsJ;qQULHD-Z7YE=~Y@EHh0l~w}
    zMD|cOL$yAILY6WZv=LKH!7MiV>lU`9aGxwA^45O(jK}&)=&y{d_ZOwGQJ6zceV_x|
    z*S9Ie%uHv4$SCQqCh2#9hAP=M{*!n<`&$p(Ss8_A)lUZuJe&Kd=CHo8Oea;)N!+z&
    zm}}a%FA0vn-8U3%KI!ERpp^LfRZ2BnpuVypR)&n`wjQQBvjZ?uR-MA(YN3Jh&P4LI
    zYKy-;WY!m04_{hW0nMg2gN4W|kjW&l-J?5ZG}kit_PdRMkMm6}N$W>GJhIv<b<RK*
    z1*BN6yeCgLfJel8w)5Rn*~2y!&28m_<mUydLnrQ9_ey$XpD!aG_!Y&PtLh_JHsx{C
    zI9}_!e%RKz3i^VLuYtIydB!ipWir=?Zq>FKVlZQ?+TL35N2c$feS6&PFI8-+OZ>9S
    zr=8jt_=1Q(U%1+-Hvdz3>OwMbGiU_YV0AUtR{mV8^7P4)0dGoUo!K0g(jMB-i;8m+
    z1^(}Ti*cjA+d^M$^qjP0kMoF7hy9d{C{U!Z`SFHfN!L%oI_9y52EM)T)Ppmf27iKu
    zv_<GgUe;aHQn7BoGJ7h3Vvm2)-h(4S0>YI#G%QKt+qW#Jaq6`MS!F{}m1O+ps19+{
    zj$%tV`$3i;*--00vO!blWiKugQ9>yZ4>1*h4FQq1W8(D&I(mQ2!ote@(c*_tstN^~
    zn*O6<n`6AbfF@#S`e``RusLORT3nH#9tH+2u}amKalrGNU1`_X7ry}szwSPiLoX*S
    zrWpd01XH;uK>6r^OOPf->IF_9q!+=Ng!8+p-a;7=)a<leJgzE5p^|r}aFOcYfW1;5
    zKRLRZbGl{p+XnE?NqP3pCs*z&=!M1%bb%#-Fw*&@97pa+S$x07l$?|6iI<t*M39om
    zgKgr6-uXpt|Lwse>Z^tO=x6r2`#UNfZQqZez_VSd8v>Q%=4V1=hwE#$TPeAsZ^0_<
    zZcgc0kCoq&`}M4&CQK3>kgXKir!4a%s+a{TZF6}}<%VB=3jGb}kAhH)R+r7W3a%>#
    zAU_owE8@!g!Od6XYJP|CQ~MaVAYS!KvxB)l#vfu^@bLghueHrmZa4z?75sfh46!`6
    zoLnRUOsX6wyF8mcq&wjy&%~>&t+2L8TIX5)^!&)b?H$3bX04=N{=mcDMD903i!m<3
    zG84^ylGrCMhpDBat+qTC=nkwSCGHdl8Zo{&%4^~4n-vpfxlb1K!{(GG$h)o4;PZL)
    zCvKx1T+JmP!Fajmg4Y9wH!6jbwYMSTYFAsr3e5x4%S0hBPqoCGtH@wNl_Hh<nqPk$
    zwhUsiCuv*`jIBCl;@*y$*-%n8=&PbneF-!G(7k$&FZ8S%u`c}s#ORV&o{HHdlz7hF
    z4!lMt_w$}CkWa7Key#pVD^BP^UB@DbJB38S&h`B(yI&do`M3xUOk)aN?E)vlF$tNu
    zN;n6sT+Q^DA?%UA8^6c@vTi!I*Dh!AIlYDZ)sFSfW!{JGYfLzXTikZxK$K46WuNS*
    z%_nO^L7lXwBzJ}>UdS`UubI=c7crwlNz~X+kajML2=+-~TOp=vV`ivh?v{d-H_-3b
    zg}e%3Yh*{;RiPcIQIMVpa-T2?EX#P$lG_X7OWD<ooytqDKIA8jt7T|;7~Li9cy~*9
    z+13<aovNiMX(BlZ__nFf7o)p*&)cKLY{#G7s2j!O9>g5dz{bLy$cNjvj2ad?M8w9J
    zWv-%c-nBow$7kg!?tPp}`4KN{s@YPZB#TzABty}xy?q_VOEzlr>&R0zQ}!(vsu;&$
    z$36FNz|5hM5^YPac6`Dme|U?V=+5cRp_k>l7$612akl&GaM+F}!6T`GEaPo9Xce0M
    z;IG?oioJ2j?@>1?4g3uN9W%#pUULXAqSb@WFUpWRf0fKrtV{8P&tOkFj5LQ+_i4D1
    zHHZ{{ovvllS{5F*Rj!d{*q|FGBN>Kn3({fDu3}&I`c89c+G^BX>67XWZNrMaY%3|s
    zH%F@MbtE^Dq61^TP$rJpd`^YHo4NbGNJhn*^^?GQEQ(maP1nk;oj-QIO7PX{#Sj*o
    zHy5CK>+<Hx#$L*La`Z(-5p&KHiLRX(`1hUgp9Pef4a|dtbL9^667<<kvdaS3Jf7WX
    znvy-{E@Oy0%B73e<aR>u46{RCSwi7_4|Quzr<p<`P}U3j4h-)TTE*d#<z+skQ_kYJ
    zm2(1w<t7edbKx8bQFMduroqX@wxTqrlzHaX?|)cW>u$xJe7XM`+b?+c{yg(?7YuV<
    ze7R|5>Y1vAy?!<?`_)S`+s7HRj)+WrKb`Pm&wE;H?&YiHH)geCo00d8uo5iq^OdQ~
    zeZ3|#k6#JO^p`c6nkA)Z^J-#T)C=IX2J)AED@FxnHr8d-2~yPrAv)9U(m?F0bbBhL
    zf-KcuYch7XV3C#1XOsy!^Lt_^(F;5BHJoMn^sh(GdNUfC8jLx}k%sw2p7Bvp87(57
    zkeZ6i5?BXE#4N+0n(wnNed8Jk7jd}L&nFv3xqF)m0=FN>C&>H`oWqW^rfFF0gD{(p
    z?!r4_H3F|)`fO#^`D57p>8+uvLQbI+JTJqIbI}}cC5%S9$)h<$P<=E{Up&NAt5?^S
    zPAs1PU~*ZW_h7nF?h;B4btv1JfGyb_9U-$8bKnh<+s$<o1Yub8A$5|^7_MU#`{C~^
    zNVn8Oq1e-K@#gNKISVbduduFx0VN4w<f%bch{MbEKDbJABM~b$tQeo-Yr5ID<G4)m
    zF@8Ur-XCpwS3HTAXh95Gp>A>Y9IwC}<WDr>$-X;{dHa5K(g}Btk19dj7eeo%=cKJh
    zGW3-Vz5DDf$1>i?(%@6y(%vSgS8u%2SanJ&yytiFb0SI$@O1mUV}CHg#8UblfEgJr
    zLtf_LwyO)t-vh7e&4>AqAu}`d%%?c4cbmweI}4?|QqQpatfxg08jZ=|>x1WXR#i}D
    z`y}XM(M2Rg#F$k-B{OO7c~gC4Ba;t-z<YVB7Qoo(!6PByMKjA8tb`yrx&rM0%Ui8;
    z-nDrBFsU#j9u#Jj#o<|euI}{0ext;lHoPAQyLPL1jF!etO`VCgb8S>PtjLqwS)_h7
    zWz}(Wul1zH2EL>dStF4cU?IaUNucp0KxPDkDRUJEjUj!Pl{S1^x;W}>Pui6Awfm{=
    z>(@OADA+Gv5etaL9dSzA$P_AMgOhN-c^X*;N|FHUk}P0C$!E7Y6<4mm0f~gJep}Ex
    zJI~kKliLn;5W0nN!uF)APQ&3!747Xl0BW>TV2s{T3*McAr+PopjF!7>y#vL(Zr7~p
    zRkGFlEb2XRvcPdj?JeWl!Y-R#KCV;Mi>JnEjg~{-#NIKM2|q9|h~nL(iV*0eX+zd(
    zcFW3J1~w5Uc=zdtoz&+SXs5r}0_b?=ekmX}<|!#pMYGH1^ONj}<XZW-<+#>#yMi1g
    zeqzv_T$k0sbxsaOu!&APb{@S(3T|hHs4A7isDHGOts5fNn=~7yhRRc$>j{l?oW>-K
    z{3Ob=El!C(&_yk8Z%+fIK3J<ztNGw-hu6E8TZph1%jA_cK1<BMbBn*DsQ*IzYnt;W
    zB|Bm?+1vMOlBv_xRp4Ht%)8%U0)>e+ccI08s6~qU0bQ@@@iiP)$C9Z4&3?6V4>5l+
    zeQFV3J(uwi6X6PxU%=ao>%&F%T_g9em9D*`5VerK^hj73(bYa%GPtLttJXZ57LfPp
    zQ}PeCmru=%T}&f$3}~tA7Au-^5x8KF6D^RjW{dKp&NQ)jE8G)^$rnH=rWbv7%FyHj
    zEz-{iW2?%eovmuI&zG>#aRxsBa2HA*@VXo)C0oB)?32>*nmU7+{Ku*4h9%~Mv1kW8
    zFO?E)0=*QXqZCUmF$0{-v85&bai^3^*=x%1$^isSU!vF2cKVSBesP!92dDom8AbQe
    z*xeT?Au<}`J6!O%w1RWr+>m!NR&5nGFo7@_hWeAC6_P0l_SzM{D()p7%97(gQ1A(3
    zv7gc_ZHcX4w(8IlOL^LW8~K*JhW)(|d1`36P+F3VkFB`#IS0TE1#ycIz1++MJ{0;B
    zS$V%`%FJ>REp((N=r^lsVC$!1bC2a5lO5w+ZhL#Pbf@O&vtb^cLQxQ61lOpPO9|=O
    z2l~kNaXse;yu28P>I@ad+jH&`<_fk)u8^?%Ga4cM+27i%I79`5D_SD)X`01ejd8d{
    z;tXN*y1lLX4LD>EI62m?J=xC2XgN7N?0Q#a`9S4CV&oJp*XvvA2lO^*hLI@RcbC{H
    z!K2Z~1$Y}zR(kMseC19amNt&zlV?;yuhPqIu7kR%Pj#izQslUBi^OcoKdzi)-Af7a
    zW|Tw}%yz}=FTF|A1NSMp43hM5?yiL2X7#<~*LxGiTZLv}xObFdff6J|j>DN{w9)E~
    z&<S<@qg{X%)^IudLDKX7XK_JbtNU8K$P>=k<p-C3n=^LGn?Sc>eJ@rE-NSsXP+}>2
    zbGjsAlpT^W&pqa#uLYOMvo_NL5&F3fw%lk?o|xt>@{nulUzy9bKZOFKQAlxeDN`sq
    z;wSmEGPRb@k<wQ^R<8su%R#k1`nq_Qtk1fF+d$-n2SVqUB1pw;1XBf5G(TKbg%vD$
    zYvRkMHE7=;K7cjm6eNrwQ<X!MK%;iR1MS-A^}X9#SIxO?sLKZN9=iH><lFO$;=?ip
    z%1VJdPCkz`hWFXbHLt(TBaLCeTGCyQ$4*Wj+C%TD@@63{!#*yEw^l9=Whx)yi`$N4
    z1pBeHe!k|;Rl<4T1GibO?}7rqz(pV6NaWR3ocDs{x^(H!s_xt|!|CmlcO|iPC&jVi
    z4w4cMW&X8YzM?<qPQ=1lFYo%JY3!0k4G7pyw?8hJQVMF_73}#*l!b0+*$Q4ZJ5kL}
    z>MZ+y=tLE#W(N`MABhx??Z7wF<de+m<CQs(&iS_KD_m~k^w%157A#;hLeCh^RjMjm
    zgZ3Pl7~<Gv<tQ{*2IIZYI76s+0;4aLHKNBA>Zxp*iCMaqm&HD9I%T?q4D83el^_3p
    ziM5|pCwIRs{;jy-(6Bfz_(1TdL<cWpI4?0f*>x4y0`2oK+Q8PP<W0-W#dMc_eckgt
    zS~-w7sUTan7R$kxwgdbptM9mQ02gKL9scY=)4L_?k$iOtcr1}pY_e#t#eozHAApxs
    zfvYuwhtzG_Ni)9mn5fzvx3Q5sC@xu(Rcs7e4cH++xNtBwv9ZtJ<m9K~SojeYEVO<K
    zqvn4KcUC;ZPkX}%ldNQsqxt2&8PIHcWJ|VWDnQ1qms7=%NuD#X6{`8kS%y05OJ@3%
    z7#43~Z59AaR>g?Sxla~GGjE0_9Nr*E=B%_HkZ0&w>JMiqCdVf!-875B7(Dw-?k48b
    zrcCZ1!27zYfUhYhB9r`7@fR$sc-K8hV47Naek*y<eng4b*sKEJCfYz@J90S=sJaQF
    z^D1MR9C(9jENc6v89YVj0YlmWXbf)%@az|FIJww609BcLxAh)!<oH4<wXl%V>sd3y
    zZnVYx%56kBrjHKSwd*kt7GM}*(5UvXHfXf20CK@(s}N>24Dz$<T6PcChH&QI{Z+z;
    z*nqLKZnv&abfCeBubY%mSqO?eek}v!Cyz;OD2`&-wl`HUBwbKR751CpiCZ^)7#{(s
    zeA$>m%aZ?@wI1>qocrvhCVZ<dO3}IK4f3(zAPX9Z^IrUdaBzIX_TvFND~s#T8A$VO
    zk8{SB{0Gdt4wS@Yr%WMor~YgztAss93sZz=`o!{3reXJCS1{|4LkDshnjUjzlC67e
    zirTua5Rn`QE<BvMAnqFZFz@??9NO_kGZE~-IObI3wdP4*6gHO>MZ6SjuX7S#RfnT<
    zfHJ2rL-N&zIp1#p2OSmTCE@G6h>Uee<qJAnrd$0Oc&Wc*v6jBv!!{A$g>a!Se?eYo
    zoS%n@H3>^aw_~Tok@r+-@hHP}YCtVF-}ZU3m-{6!Q;C_-?a2sX{@Ib)<0&%vBh`ii
    z{bdRE1Sq^9yPMqYMO&q2t3Kh}tj<lpO4Wyf`nEGuBR=U*s+rzT`va!Sq&5!0(}0Pk
    zTc>g{ma6ifz%tb11lK&mJl431I_Fsst`g_%k9Mx5M8#8}!ptx*7@jS--jw~Z@P0%v
    zWu^a=Q9TCj4y+V>w5%w0m0h@bQ(oNPzW~z-sQxzm$h)H=hdogfL1xTh*od|bj77T6
    zdl)H+rgIq>HbV;H?EI+&akvaGKIA+XrVJNlx5YcZir9EO`f?zqVq@jcZ_AwC`tvuS
    z{!74>{5i3PAuIv<>_F3fpqmb*;$jR<eyl!oa*yMv97Xy*pIctC*gMl}g9l_pW0U`Q
    z1zecDV!tt*@9~84eZzHRH1N-9IlA|xQfo@}X~$YLFf@>zY)KhUp(C%x%ajAna5Sx1
    zS?UXWF~+`HpyQltCVvp;6}IKD^7bAtwNd$CzHc>W?kLzfCVqSq-fkP7*m`w5c`$iM
    zS_)mQXhsX!it!k3GF`gE=D!^^H}E9tOeT`Fs!1_P{?<G7arpS%233X&X@CDX_HMnJ
    z1UCL284+~gFylY~Xg0&h-?!wU|KmO7%ZVhbAv;;qI_!a-n9s2S0t5Fhh8HI{a|iPX
    zZ~jiUpS!-6Cu$HiLHY?soyGji+9norYNP>wZFu3Rm)fpS)_XTwfuj*MgI?;-=u4Kw
    zY8bdRv7FHo_r(hb{scD*eQ^s0Hwy*{lJ!yU5ejOgUP#yE?8KMlN+&Uau34D#y@(Hp
    zT74LEN-}BF4}H9dT4}E+&*_F2qvvVnTNT!q^^XSNx4d=Rh%!CbY+rPf%f+JO7)Zd1
    z?sUH}Fm@L#DgTh<Bv(2=?&rXwzQ8?H%=Mf^R^`2qv+~+ogPZABPTQYzlCQIox%}25
    z34(<1xx=^JUdov~v5MaVaNtV}1vL2tyykqK%<TH(2$RxsV1Bxk$9t-tTfc>~(p;C6
    zySf}zxsAEvMj?M;xNK?fj2WF~fv~BfZ`-Y#e=Ac2;$!xR_MO*@(q0Jtu&kvP94d__
    zEP>=rx%^{b(dzU@`ZvH2bGLG9Ne!=NpxX+Kcz{a&j4@1;b~n$m;#N-(H>|!%!=C}Z
    z*4`LbDvms+qDeD$5L2-9Q=rrq_h8u21|9EuWeGeHf}5x_CWjL15;!LM{?tG@rp6H3
    zZ^WdZ=89$hgkc&PQ&gpA^HT8m;;O7|%dCEym92cVNWRYQ$s5i4`mUl_T)__VodWX9
    zQZGSkF#k_HcP8B%hr!xR{mlq|F6c;H2bri^ZrltV@$T7WiT=LNGhEf0YWR9IH%|z~
    zY|`us`v_V-v43tk?|09p6Wvbxg3+y8wKk=dPc0ph-)=*n5|iUN``1Q%^UE1Ky`%uY
    zWu-I0i!$x18BE0CoS`lVF!E6=@F@04N$3AqvR$beDwyii8pV77?O;60<dQ|f1_&}v
    zRxnhyPw}Fru1{;~2R8|9gf*%NA+5Z7%59Xz+ryOK=|h=E?tzya3vtcMHYNk4mnjiN
    zr)*FLHDo>aEV}*LU~4<~wZ?<mi#`X%9@CTfRu(xJo_hQ~uj)-0&Nr5|_<8-$ht8Y5
    z_71fJbngtx{WAiPbnP3znub$&Z2CN+;J%%1JcxfZvqTF%N6I?y6+28;S0aAcG%WHq
    z)0Ql-f8(Btljmg{X20`J*|HWIz4gP)j6MRD^}D5r2*f+Ol;T`h!mGqu>nee(AC_e6
    zT;w~5S2uW9=iY%r(2R1?Pq?myK#gCx$FKK2+UlneJ8;%bx_|3emP*%Lpdzv1GE(4K
    zH<a8qklT^Rj!Ntlk=OuidTfBOwdoc4c^XT&4R*y$e~_+gCcYGqvkKyF90RX%1|jzK
    z8m~%1YctQ5nT}vd0-Fx3Br&74Xp-DfjU9s*13#T6Zvy&eSfMY(OdsJ52zM|ln^eUb
    z9rC~d0QnhiJ+&ES>dM+fekorXNNeAP@ZyGTZ=aOORN*;}Eb$fEltmas&FC@kr&s2u
    zai?u-zUfic(e99F=~xEhXs#Y-0W_4~0Nm;7T3g#Y;qmH4wmvcLuev1T-&QSmZ_bTU
    z%yR;|*w>L>ka~}qysm7!H*ed8)<z&DEjpzev${O;OdPTEwt^3H-j_Jv($0P`;OL5K
    z*vx3w`L)xkDYU0{?OqYhARJ-*j)%J5ud`ZO|1Hf6mN?0>c4o8`#6#79$g8q8W6?qc
    z=ozc`sqTG+Z@!oyGBQ>9t{O9zH3>~BVif`F;}{xo-$ArznZ<a|=^$`IKdHVYn&Amp
    zYC>sw7GTMkO|IcjFtc^~G|LMQla1Dad)q#HmXKJ<J6>nizqJj+<h6ckAE;zESbi<#
    z1;&$O;jg=W-kBMGO{6Y{^xLG=W+~!aPY((h_rFv5GXdkD@vDXk58z|aOKJ%MfKGw2
    zxS}Z=D*|PfT1{`!R-Us%?_|vfI%%HAF35tM8Z7r0@(dhzhnD2GDkmTTK&?ti)=g_q
    zq);NS&T;=%|Ax{;i;TMd7amAyo4QnF5{$$<3&i*Pxbmm&VMV}c{Eu5)N^6*>k@n2+
    z8%JD;XdG@GQLdXp>jE3?q{w3Is|~ywY7YBuMyDer2bX@m$wUmu9AaNuCV;PiA$)v$
    zq>7|}81tQYSMcr5ZFq+m0gL|+Yl;z*L(9*-Z7=7C<fzl1cy^F`XW5)e-(+|I;~sL9
    zxzH1b3Lj5*NcPYaXE+_oWl3j2F`Lr-pZPLf(ocLyud<U2{ZERi6mEvrXyXm__8zjU
    ztHa-Lito724!x5gcSG5@C>Mf1^2)fa|C~fkxFgRIhcc!kUpH=uaQEAcoFvt*i&5D^
    z3TAg_NXybd*^HrJ23~D}tqQ&~Wicgz@~^&hcT_h}B_E#*P2E&3GU#BelJLc-t7_+~
    z{-mOIs3+WI>SUbESFK*Fp=wf>1%>s!O4MEFwVZ*U@J9IdMSF-CUK@=8ZuQUlFznnB
    zd;{<V*t1W<&W&oQ?YRD=JP^dNtpK4?=BIH_iK;rHqih(p+%w_{U_ZE$rHKL)YS<HB
    z^m<I>DuvgW+8=0Z`R=c_&pn+UBlGzUcxjX3=)#bNd49p3bz1H)K?C>Yw0y`~i{hi;
    zntj*~16Xbpiu?wUCJ8W1k&E<{IJ?xgzCLT=^MQt8$2Z=p3NoIQ6{q&$MfZXuY>!js
    zb;pt8mHdj`=aF)9ffI2>Lr4iuzX36x0|Rf1J+6wUuPSbgCSWjC%jr!-vXy}u<J}u?
    z+>whCg_sMqNeDB<MVN%%uaOPH6T<j?yT!@tv;79BGJ)EU8EphRge(?}KFS_;oVua@
    zhL{t~(Xf$usWRd+9^j2V(#Vk1x7AXIc|-jY5K0+ZyERqQ>fT3Y8zINera5*qd-$s0
    zi&}pIJX5qbrLcdS4eTuVv<f@OYDt1~JQ5#+WJheQ!AU@8=DH~?)#%kpS&HksxuJ=u
    zJNdfxhJ~dMzX9KN2s`%uk;3;LF)~l|hGG0b&N5HxwP5n#h4S)-vc0iitY5s;m?ghh
    z$OsPXd?ODENapFYUD)q$Zce0u|B(Es6LjDbD|^5pmgswzVdK21)Ti|G+}?X^8GHxQ
    zKaTIc_8L>_PYfk?+a25~ppf<&55L#+T&9i|5vYmpChZw%L}m;Q8CUUhcA1%MojxN|
    z2#qJRh}NMC06hg1H3E!dUEYaPX88oCu7jvfhBd!mI@G*f)~#(h;~L<52_4542V|uc
    zA-+1|K2eIEzt#RZF-BmDA$%C}INlb1du)HTP(e+ssgNnECP-~L8#p6KlsYu2z-j4$
    zHmmqPO$)4x{gI2SzXJGZ{7c)Wjj-YW2_h!l*<C{6XO`Lr{pS0NnAb8Y;3|Lv9F}G0
    zJ$NKw)IKKgZH}v`Pd&6V+g~(s#EBz$hzj6?jAZ0wkOxwIE2G;-O+<P0e-3VMEtW{x
    zRrYQ%mtEuQmKZf0Uk>%pIc0*;uOk2w(3P1^2<fo3bQ+h2b!)A5<{duv@hzre{$yWq
    zh6V=GM<i#H*Y&J@Qhi&))4=w&_b&`(VSBrX*k)V|s2vv{-N`-ApxQMhCuqqsS1Hcx
    z`g_^lT5OJAw1|t(6CqNKyzzs`;~lF*!+sgnJYT3=MQla7MH;ef#sJ(Dp1d5Ma%-*d
    z1X_lrtW6^<pKjHg4J*b9NggtJZ$ro8I@eg*#h-`o4Y7P)%+aJpj!{=Qedj-L4ZI%w
    z^rb9Aza&<np_^ynQ{nwxk`WEXoJR_`C4q_55T#iTKZ&!S?wr=d-ZYXMf9%n0uul!C
    zB~{0iHwFc`1cQ(lZhDWXtUXHVcr`!l`<q`rC1jdpD%+3GmK(U_HbKF`CmiCr>#My}
    zP+LT^h^@@>8@GipQDR~jf(tf5&U1zXo_llqKWx8ws138yPOmNH-P1?ptkR=N=4@!P
    zGOSem$G%41*{mHd;w?*9g>EmF8*6)&GR(2>k6|D*n9A=bJvbx`WLIlv;qk6(7V})*
    zT|_M1B)a=7uOJ6=oDSm{2cS6IdV^K`Rix<JJfAE{W&mk?n}tH;FjZCG&m)uSGwe-t
    zMoB(YD;|5Tn_Ep@@+(G;YgLuJsW!*TXDgpWjAVMzRz`)TUFL7LHKy`Ngux_*26!8S
    z;L%-H6^~x=F1VI<vj`bvxhHG4@T#yN;Pu_X$@*76qIkaRO^O&6MO$Q?y8#e9@$(bk
    zr{R-WSDL5WFEZyR037!|wOVU|5;-cQ6SP+}DEqdNt&XPi#+Q1PgmX=Hs}e}j#Tg{J
    z-~w5PJ4RSw`h#3vzv7FVtyFyD%TbKT6!DA^*9W2RRIa?qp)iIDfwv3}d)8B0O?5g!
    z3~e-{fT{`nD|&c%Mc%?_)6%?0b*J1s@v@!3khmuYp#49UWyyHYCh|8H=N_Kb>OK|y
    znfzzsi7b-d&jxnE0fr+4XDr<Z9jog90EK_BK8<64YkA^J!F3E^7Pmw9*c|-C@CUHZ
    zA6n{BjCCAoxomtbr1)1?*Z%<H&c=C^w%^^xTh!wKdIMi$e$RjKPQ6>l8ZMWw_@d<L
    z8hz1Oyomta<sLyJ)b+u~2EM9;R`_x7drSV!(sX-ktB8ggD_GT=J<G^?bLs0|ZR1al
    zekSo$av{8zPSe!w7PiDjpGIMVXy(4g@us5nJ*WN=kAZ&ybd#pr=(hT{tZ>Y>s8EyB
    z=4Bubdva^dHE)a>M3T!iJ`>Vog^nYJSuS0bN4S+iuOgEAYl)UWIi{GAyn1u!J$SAE
    z01SLT)jUCQxh<r-3O6#j`6C?j$?5+9*QM%je#Yc_^nN70^5F2*j40dz9Ft?WBQA6F
    zuKxhTUkYtCXkJ^JE1M^boy`MbaqquB=QY`QAK?wInPCO>>>p%~-2{zt*~<4SpdVbF
    z-t|+)9wOA;QXMYP+UY15h|)H9mmS|>$UpB9+@DGYahlhJ^o=$t7~I?~vbl|9d81v&
    z)j<O^-9FF5eh|{GHP^md#zkB@y1NHqks58_wQ`!yvvuPQS>u{m;*BFRU0p0-ED?<E
    z+DjgwW1dO&sycmv*Dhl=ii@IIvHLZ=(J*Jv{Ixmbb9&P=Y|@6}=Twq?Ui~B8Ir%nc
    z2!o=7fuHf_u(fNJR(W&V_=iqE`t@&H*Baf{(8~xa*l6QM+<h^V^Kr*o=Jowi8r+1q
    zDUv_~IVbtm+=g-&jMH8*SMxz{y@CETlQ~&A9P||waw|)M+1d_rKMpCk3|aRxsRPoG
    zf^0gFNgKQS)(zHG!=c6pKJ~J$8<rij_p2)Leo#RNjN?5&Ite9Kc9|xL!pxkGy!YgO
    zUzJHFtiRcQRj}K0)=8w?#oHMprWYM^(~dpzE$$3bc}<XeVDvtfVrWT>v}^aW3TKBM
    zaDUJBt~^BrQC)5}u2ufYlIqXwR+5h@H1nmNFhG&=2-<t#WM}d3QCivR?We=`HjemA
    z!Zn?w1&9RY&N=8a+o#hvEcHnuLmbkqR$H;S{LWtka6W)?KMI=jRJem-SpLs!m<kn0
    zZsd&m;Qs(R`HGU9A1u+%*C~&0bhrAgm96gWI#!5B9JdHU<$r`{7&*t&jGB>?%eZ^E
    zhuUu~Sou;rt0Eo4IO9Jr&OaPyoYij^TiI$;MRxH@*G?H@`SBl^+2fv=z|K2*5m~x?
    zm!2DG<%&4la1LYI8A5d%fB>GE?V6_a(UPQdx`cNLHPzjWmvISSY_eO%5#x0^1Y?{r
    z1CHF*&DFKl)v30=^DZqd<aq=E58nB6w+Eq8e^J`5UHFpP`b{%Wy^qOgc6sX*J0{$@
    z00G?f86@@NxiLqlUustias!A2(s^NkDufot;x>-J<d4H%c9*@Nn!d&_h&~}(={mFN
    z8cmw)SlSaVSSk)lY=z+f!61Kh`&J#@sTzf?+_BtkUoIW-wj6~7vE&oDzH^cQ$okho
    zr+8hYy}g=m-ECHh$`XXeQz7{ykU<zD2PZs%Rkcl8(l(aMYP8C&5{P0XZUFjVasa^V
    zz&^FrPEbuo3tpt$lTd?o&9%XnMg^s5_LO7_O9k4ioVGXw6UisFWO#2+zmHzHI&PSd
    z!*44Dnt4Qc01|Sf<gqOtFhR#0@r>rby3};GwfiyqTzl>0l*fReXOeM@@_6bq)Q*}S
    z6S}%vX;xeLFIvnNhY{^a*Ngz91Dx+XH*T28u9fb=+_dg*eY)DuO*2a+xx2TuEq1X+
    zs$+0?-MnGsJQIVFw2X`bc~25}vcts|cM&{t$qYsnD-c$b2bmc`#tuLO2Y^WA4mIyI
    zySsU#y)o~$k_)7|^C6F5jD>BW_1(wb^&<lr&Ub?R6&01m#;bF3jcQwR#}F#c0^|+Y
    zJ3-C}9Go89g&$`hdA&t*k+blF>K3tFjauFzD(?w2up<omhL<DpqPyL&TjnUdEpIex
    zNLPKVNY8Rd;YD<Nc4?vKaoi=s^}~O9wX~fVQ`f(9XKf^-A%0PwO?ST!{t3;itQXpz
    z>nj#e$eI1){XraiSH5^}!#Zw@XA~B&F385>SYw{#`kM6lQBhhR58)4hI;Ocae`p0%
    zkYvFCDC4=~IQsUlqJA9w2hlt=e{Xv<QQgk~9#ldxwGVur{{T}~&x|z9B~&Z<?i-Z1
    z83UhQmCtHEHq^C7Ws*rPZ3y{dwIpZQw;c8M`p{~UN3&miN6`EmB3eZVo^@RLHedxz
    z`ksQgUlM#)*8EV?KrUL&&4%G?Zg4S<L2PBKgA$}MvH8dQEWn>%!~X!SL~=axNE4MO
    z13d>Gr~Ko!L02@|_<3PtEYcsnoOArWs%v;IFC&Q`e$o!bj31N%*V>IILNj6Hx0&T9
    z83ZWbj+>8D>@oEjuT=1#fOS}J?e}Sy5(ZG-d@JXLZ16Z?la3EPsTH|j;r{@``L(4U
    zMw=v1u~loSBiy^Qjk(*8;>XmF!|LA-{6Eq>BcL12YT`R~SizPU9bn$WkPg-Z*pt`3
    zE0L2<kHb1_H`nmPE}-)w+S%JLluLCiSO#OpGD+$_mCEWG?6+PljB0Y+B+YJO-6rLN
    zHp^!$!#jR#5^zb+(xUe_O2=8_4;Vw@C~fX;@2qUCWKx#aZFE=%Q^_Sx+=2f9>aQH}
    zcZ?#uNW4FxPYX=!RorJ|F+VX-SnOP9sOi$H_~*qMW`wipwl`)eISl(+Pd_{aR>lCv
    zc|t}p->056@KyT`pCZ{^%9}2(Bq0?BK`IG8z+(gRHE~8w^g5KVlTz~hIiW`swwBU)
    znvLLXt8*YcjP(E>gWIPS)?VHX8p#Z>#}gB=BaPKT^*y-bjyqHy1JM5fvvhsNC2i&1
    zE<ngCKZtYl5;`8g)}FJdEX#=lHvA4wU3olkydDNQ_4KPG=C-xdJ;7MpV`V&*Cm)Zf
    z{5n?;uUv1s0XJ;}1+&I~FUPM+>~A#&nih&SkcCpDN~d-*Ks|jwoq4{YDEmWTor%w<
    zPB=f8>q&^OB-<HSAyJ;E>w!r$f=*au<2c4U)N;tVA$Iv^Im-Lv)}xYEWl@it-v=kH
    z2tu*>y8GmFRW5e2k;YHAH7v|Q%YnB&F;Fa_a;FEJ^~d?^Oq-dK1IvS!3yfzNr`z2h
    zCo-rl#(fCM=h~fg4*YNdApZbA>-_4Y{I8LX{l+*w=>a{|f4q)Wi_AgE9Wp%+Pg<h4
    zQZLIa&cLSP6>z18JAaV>06D9xF%8LL802>A^#1@nQeQ#mLb1*ylGL}H@T(EEv5mvj
    z<Bxuo!-&Jiuf5b#k1?BdXC|TM=HfLn4Y6$}3UE%}u0N$nx;tHop<K;!k}Cl2KyaWD
    z$mziR&*M|9KWBgl1TmySNxY<&a}$81b;u=KBe%<e=~a^4#}TrI=V_DgKi*J;emvs7
    zYP2N@9FmVNrVogfHLWl0w>B?ta}4EDTQ<a_2Lo`&&Fk-zjAFF3`E{EcOK7c%>Y98w
    z3kt*|V#FR8_2BS&bg89<E*WjDhWkXzNYVqGkOG{Jn9Cob&(o{ZWx26kh9zm+h}HAk
    z10&}3shvqiHfST<;4iP^Yw3ih^4&l)l>jjLkM~brnFBpAG0ju)ABRS{tw%C6Fi4gl
    z%egiU><A%&=r--`k&*ST)5mtUlWA_!+ZMQp5MD8o!LgI{!5w`^OmoTKL#k+Yu}3Ts
    zGzd4e#Fhm^5UYYfB!tdAvO3pAK9YuldNOO<eLmAow~19JjRA-<;Dd<p#{)P7o`W1<
    zcB;Nyx6(u{=Z?xqV<DRav0wpF@{di&AH$z|lSs9R?Qg9T)<`Zk2+<@?ySEISj_3Ib
    zyQkRM&9XCbFrAAl%DKv*Jc2u9Wk*BD(w{nw@3^-)PY|`ln*37frIT<>6+jvApuR9M
    zoDTm0t#%rHlIeaF(;@pr*66B}9n48)2aVXqJMrm`d($lLbe$Rqt|f&Xq)DWUe=YNz
    zXWQ4`w@Q-E=JMjsN#y;=1gufW6~icOZN?8%(>|FsQkOGHY>jFBTA24=78g#qlHD!X
    zq92s0I3V&55`oTh(u(D^uM3-vQsPTQy12I_1(F0}hX4bD69=5pUA(ly$od(zduuiz
    z$kQ+wDIN|hGHrT&LCk(@MF``|Pn0+N-n|WFr<vw6B6(wWa!BWnnEa}5-N*6~<~DZl
    zGk{Hd?cCiW=b5_PWbIt%3(vRscB_Bu0*KfFj;shBJw0nW7}<gY`?cGgsmDK`JwHm<
    zx3{;oVfL41k8ljlk~5Co`6T*(T1ANst54;VxgR_b-0|FV>0L&Lqy4Bcja$x9v~S7I
    zNB|6V=cohTqtk5{Qn<IZMT~AB7jE40r{?~jLyGjz4EQxI<#^*vJ8;(qauK~qMHm?L
    z&VN%#Xm5dj7;2O3d&vuVGO0*57`NbZ3y<OL+pp*C+8XKq019;0pU9p|{XhvNTNs$C
    zHh9MFfDH0_<E3$yzBaJZ?4h;NuLb0)+gonp#Al+a@%1MFW9eMiivBEka>ZQg9w0}%
    zU<{V<lW@e2LWNSna4<gb%@%CFWfQcx@pa|h)NpHdmofdhHZPwUB^>7tcCI>;g&yM>
    z#(6&)YnR>+np<mqdP(ED0#{WyCm?*AcAP#taComh@dxcGJeFy1qiIq)D{oti`3iH4
    zXJT{H{j66jKgDkmc!73LrA>cu`3<&LjyxXQQ2}B8@oIdex~SE@)&`|{<cQGfvq`cz
    zqY3-9#sNRX2qPHn{Q5r#=*{7qh;OeUw~k2n2`#U7vyq?S<Q#Q8j{g8!<Fx+(5PU+?
    zVUTE=66m(%W!flRGCkOlkzBp!#Lpb+ay(z#Gf5yjSX;*=fF7NR%|2el^CRiotz$<v
    zP^@?Mcxe9se6hEb<&=V>->Y$)bM4O?tll3jV};e^-22S3jF0Z0W6Aw%!Zk>KCV4VQ
    zA=U1r*|{V8DNX)^B}pG#S3Ry@c%w@4q}E$dw3<}jR^d<+>-;(E+=_gn=JGwSRlRMp
    zF}aEvNx%%*9dn*Kll=v97qL#N*pW{e1OeB#72=nkDAbvWONmB5iKTDpikre0cm5*r
    zHI=32qkA2bKIxhg%yy1X0R9=L?8)Xm7Hvsv)lyTmlar7~PfjW+HM>AS^P|G|*m`<&
    zuQ2fx&vD`nPCIMa#gtRJLf&f}sB9_ZlhV2CjaKU1vMtP~o(nl7eGN^Ja~{?;YZ2yu
    zrpBR<9chr>+PKVDQ4$CD#z*z*ULd|A)Gg!B+BbS`P-^sg<;2kL4v8ygYDpO9AB87$
    zEA~B_b@Q1@mD`+>2q1C)0P3n2TbCyrTRlL>Z{?mV$!z>zc3GBKqEfsKq>g_&ziZ<=
    zo<?0Zv0SUL2OV*mo3ZmAg>1#L3iF&C^{qG^nRZC$3-gshv=BYMp#Gw~!M-3j%!Ek6
    zP7V&%Bl`X{)%bw}u-koiZ28CxyRd$ZkyCfre8;7{Xt$AEi2UD}G>H932QAO%*Vd`u
    z?bFfP1=uFyCt{$u11zHh-==?^arXZJ5H&aykk(p?ZN}GA8>sq&fNJfJ#H}%|Vmg(i
    z){Fht`H&JZkl4s0u5nzo=qk$b+)mO~J8dQ#MbUSb&EJ@aJi~-2Pu?B#k_J5wrApVA
    zS`@I}LZ4|dfpZj$PInBJ;BrPXxgE#jRK~3irDE5}E%gLa0b6~9k&N#rJw^c_jE;H^
    zYAr)vo_`C@(0OpEn8wJYg+*XI&s-_ac<G;BzBjRVw2WFvip50M`i0&7+aQiD*5Ocv
    zDy#ra-F-pr$o8!3pB6pdt!*()n@o|jsmh4*0Bixs1<3^e07{2Y)n#iGxSX}Qnblr7
    zRZ5NsA$a2%#ygRpO7q=X>g!Ki=_5EWM9LL_`=&%09s8auc+<P(W-RUXi`Ld7)n>P~
    zTf2y+bVx%So=+HD9C8O3;PxiDjXzhqv6#m_yF+-}f=4MJ6(<9BM{=!$pHbGSXxG<D
    zMdAI;wak-D{{S|4#C&5p>i+<b6|<)55MKnozTXrv%l2hfZ!sCb927rz0iL}H6>g6z
    zX)A$mM!eIl*^^XMkVZm-a#(qcK`N<^*kQ@+Gv23tYCS=36U~>-0C_J7Tps;<^3B^A
    zTz0CE_-4}kT%K5AmEd>t6`o#00m)@vzypj707i4gX<6uN9i6<FK~*A=RRjFyJfFHw
    zIUJq_I`qbCm8eDPHKE4bN37l4Nj0n83wB~qBr3oR_Bo=vajt1r06`tyqzHrkS&l}*
    z>UkV>%@wb;f65%kT#_`(!M2Ej=OFX?{{Z#4t4j{iw4J+`ARc)qrE?JcK=9No(Wap`
    zqS!wzg|x2O^}~f9tyzB@%jNl&9vIdx!8sD%Y_vi>%Wnts74*EU>$*C4Lo8*Sa)Srv
    z!5p5Ry=$q_G!)d!G;>JhhS?b7c<4w2u<h&9*1W<W5qQT?Z}^w#9v`u6k|MmmSq^^&
    z13y~0z8v_ot+Nm9%UgS0+l+P!tPf5JUQgm^<?bBF_S=11!`>6Hx{BuJ$|bjjrJv4~
    z6L1~3F&RAL9c#xtW&3DBYYok=j*=?{A`@ys#GbC~K4tb%>CYVTTK@p-<>K91c9QPq
    z>hV>HSd|@BbLhTa(fC(AdHX!y*ynbUs7ry8T4>ikxX#*QbGd`4e$t*TzGn0O&2X;U
    zm`rAU$JFLdX_|lS9pW87?r9nvjs5AZq;LJHig)}fxrTT6YgHXa;etO#sF&e%*%|P@
    ztbg5H{{ZaNjLzS}-?i_FblD8DX`T%WhEWyPl`m2_ZPA?Z*R5+u`)>ID*+`2~@TN2O
    zQo`kN`2k*W55e6=_Bjk56~9j{a^7Q=C$|G9J$qJ{g}x4WuE$x5=Fh{LmAS!RGCMJH
    z>yeHUQMA<+%=SAs{kpt-yLXQa-GkY!?=krP@KGP_&*Mf1I&X!PcUbN2SN{M)b4;7y
    zZ^Vn1mR0b!zjMhYzu^|MWA!+0D~#7X0piU*K@Hc2Z<WF1OW|mf^-)5TTM7D|`hMMh
    zGF3RyJUZ>r5X`^Ls~=MRusm_%Ndz}K7lWop`G3-*1Y_NrZYzYF!Mf87$>E!J<3B!?
    zG)Mmc9U`es;O$3aH~b>DTR_P^P164W?MioI=JPxIUyC}1pt8v~g?v8JCoLtui*+@>
    z{r0ne?6|E9f7)kX@Y?w{mYxu@Ok>N2!tU7p4p4tO@f}Z3zte4trjr$lw+3r-C?oS@
    zRIRlgZ&n7w&%4x7#}=|q><&5MbAya$@T%q!-T1TORsR5rH5a}64vz%V51;JU`&GU1
    zBXL~Bz9GMEw)SNI0I_`$KciG@;M>6(`JNoSFSHU;;19<ePAV4gt;!Rh4(dbcCV%!y
    zf!vY2O?V%jF`pgL%MZ*7Sv*m7D<d5)U{|II{{RZ6-XD>3od;6`+5Z6hT|p+4k^Su_
    zP>=oQ{{ZYNMr{88!cgqMn===*mkXcQ6q0z7*_5eAOq_*T5wZUOk5&sqXAcc)c88`I
    zulQBC>@?WnmSzn(z}p*3unu`S0)gDnlf@CNc=D$wxoF2fj%qmkRc9Vf$v1Y&jw>~7
    z?07$CaD7t#G5pq?QrED&U!}rGKP(CWwtP!($s55wgCC_T_^5epADr7s{w2*+n^1}y
    zU$aF7ZO-k}+DE_s`W)2>iri!ONfGspF`wy74f{V98##ow_YzymKI=qE{{Wu#=)M^E
    z-7Ge8*<QxC2|g91g|<249HIG7>0UrI$XAnitN!vh{*`}6z5c`0tuHOE;qz_fSroE4
    z<Q)G1c8_NYpAstF&!aT`YRkvB5-f`c+B~clY{tZakXcCRNjN>SdkW3*SA-zGv$MCh
    z3%#vD^O$GM`2&z~jGox)4Nu{}9BBSLjJBbtiTsuTTt=oj5aT~79R5bSzxYQ`#?$GV
    zjMpr`>aDJTz{oiv`i2~J2RT15@8(p(PBP}E_9juj$DjWI!ZsU6u!>i8hSoI$<lB`K
    z7JsS07&OQ{F}F@L+#^W055MGYW08Pj0PXWg7w|Q|rQTTEy~U(c$7rtvkvtN}0!Jp$
    z04<(=Z%p+32C>t``h&p%j!_wTqn;K8SQE64IuVe5fb+$2H#jz>p__?|Z=&4k_Ofc5
    z))>~=WoW|h^HcyyAAvo&$Gtm8nr&jvY3%I<#iS0eJTayMs|>LYImq<SeBkq42B?C^
    zPqU4@p%<2^Ge-O}h64+p#15XM=CN;lNq?rpEu=Gsw7iZb5pE}OIV7Gsf_W8#Qj)w@
    zrfYM6yzpE%mr~5Sq!9;f`EshODeedyb@ZaT+dXpTJ9Jcz9iu<%Hi8a$9G_Y%era|i
    z<T78k+sI;qXSaj<vB|YZ;G2JyGF<>%VWhi~X>;>nU^hH+dHm|ep?w9Ki6mgL@Ha2a
    zGJ6s;&lLINp56qSTT6)Hbq<m=Oam$89OI@d(sd`*6L}e0hlXTZb(eHXOKni6bAC07
    zo*&fKL~D6uS0i@q<wzYXqB?{#aIETm1yr2vM&~21JxxNdBrQr;7v3t+BT20_okPrH
    zw`><~4t}8Gx(^NfV)3tpq}_SqEoyn#g3%kcWlwHNti2st>r;e$s)_S$?Z;2Xx|sCU
    zzji?cdzfQ#iq<uyC8+OXy3>DZO?KAa+V1+|;$-tNvg+C?QLq6Zf;k+D^q&uY*B%7a
    zzF!k~V!}v>$D3gjTR1+vaDOpgS2x3heWB?Px}<4|9~ku@f9$oVr~Dhx;k5~IBbhpr
    zvx8e*0-Ie=WM3S7B=JxuMb~farXxO0+D5=<x876y>YK@@K&9+u)NZlf-rZOqr|&Ds
    zwJ(BNHPVxBsWc6Yh{|#LR(xLubuBhGHX7S2w?ho9v=`30p3xu0YtIhK8e4r_DsaAK
    zzXSgOALH<;rv0Nd8`#x#8+nbu{{WWH2G;z>e_Hwe^G*1P;qVL^z2vc{IA`+#{KaEj
    z{7CVoo>u)Mxd8HBEvNMV01B&lm(3I2H4lh>7}bvDxY~N1s>%NV>NN!ZCh*pvtK@1Y
    zPP|?gNo3rvJs9`;_4?O_NAXGvi8kJ8rr7@g&qa+TACX$nhs9ccpa6<(K4QcDT#zhB
    z`3hsvo~x*8))#RIFJ!fB{oTYeKe*3Y;`NUX_<nY2BeJlHU_aJEs)zGh$Ntgs<MN)$
    z-QjQfX8VdCafajSDr*f!+Riyz+QQp&E*UNq$XDoCo;}CoRJG8CtWbj1#@(7Y<AwR>
    za|S=HOxk^uV4G-j{@|=Fb5=iYa4c=24nA)!0MGb-6_GxnZm{irJ|{WOPhaIq#OieS
    zRyI+o0^Sf0qKd`1v$Z6F41IkCb2Hf6UrW4MykPc8h+0`ZKV+;CgZhT^z#Tdq)^14;
    z=50-7Xwf`o2;~^$GJ-!ErE{RzTfl+z`$SLSUnz0*&1T7GrrNMa+^R-$vlIO)o4%VV
    zcx9NB&kD}K{y&Xk=P7K8xwjsXqpCLhOJ9?MzDkB)PTbTpXgW;HrEGNk9zpVyes#`X
    zL2u;%AzXq&d9oAJu^`q&I*ePIE0`lf4+EaQy=xz5DQj?7XH}?pe@B!gG1^<oqbwFU
    z&VGR7pU$eUhV*wOMxPX9pZRFpPrrX!#Dl~ur`$;KBuLvfWqJO2$m>(h;%TCj<YsVJ
    zA$I(y)~VB%aI@}Qx$xGD959iD+;HFN50Ui%f%#RfJ4VuUySV3DX$<i<3?(kT@NmSA
    zea98fOMLR&WV*6+IcyQ%j8mUbkz-*Tu*bOMKK3j-W8aF_FqYS2_m7}{5O~piS>mgI
    z4_!xR1Z}v6p4}PW13N*$$@=@(wCf%f@Q3!JX?HhP`gPQ5yp7Rr+%gDmpN4x^#9Dud
    zrn0=Zl)P?XA(G-SQI&^G9u9cxUg2}`0uPH;7WWZxtXsk5UPcsvLXuWN>_H&<W87C|
    zG~SHksR_mNLD=-oL-t$JZWc$<yg_wqGo8RNNfVLB99KPk@B_u#Al+#ejje?k&Ke1b
    z{{VZ6y?^38LSKh^9;2pDBv*QG`W?!~#Be_8KD=%1-n}cp9}ptfd^Kq-{%_h7_fs;k
    zW4wBU`Sq>Te9YBakz1Yts%g6Chq9Wlg>~B)@(W99-)e|p<Q%YI1!8IuXkH+&wODoa
    zv6kJkziPEA<nAqoZU=HYcdxT;^(#xD40k9={uzOckI&`(YL2<!eG^)QolixzyjTAK
    zmX8Xb#4xCIqW;SMy$vIZ{$zZ0ZQy&z;6;m9(j-oy#nBn}#@Z|BdH(=se*j!e@_%GV
    zyN+;|QUE@mH57Xaf6Mrq{bmOh;*WtEhKb?(3vcYH?yqLlm^_fgu8SK06y$OM3zOR?
    z^Yw`S!)0S_Wj={;ZSA5+7EiL48&yJ%M?JkOQu|)lblE?11;x}o*;}vRMky>jYj-r>
    zT$Yo><n)%)KdR=AOBR{N>R%500k3QlS*?tqVSJ}(4_x%`_==;ce$Kuh)UCj>kjB`;
    zxMELE`6oY+uVuHm`$V2y)DVJsE6O+P{uN(P@Xwq}S?aNAs(NmS5)b>zLHz3pK{u*3
    zvTEmrSpLm3T1g|Nz{7l?mFhndJ-b%LkHH;I-GGWX%CDh0{#EN*Uxjt$nOJzL+TlOi
    zi~j(vU4$Bbqi+Zedt);W?F%pd%~0vZbTj2l@@f17sM=WAMlDG(#ezWu{p0UVZwTrE
    zK6`1fJE~^Asym%cR^4RNW0^vKthZ+8<~@r409sp#rd8ke33vYhbfErqEfLC(1ijFv
    zgdf~V{{Vb9_*Mnvaz@x#*>UW^e=7Isyg@3+Qhg@r7uRM#E<pWj6I1aPkh_xR-tOGv
    zhGc*KxTng><Xz7Tn(F2jEFD0{MtoN@uWKa6QcHVz2_14P(fn25n?Dz7rsX_AJP_jx
    z9QMVw`t2Z&KMLb-J__g$4bohAncn4__hvhHMFZc1%`QeIO!F&?3tOO1+2T&Q!NIJF
    zyfbIU=-E|yCv|mtr@%d0#`5ao$5yeMON|;qB+@#*&@;;I>(}zGIaO9Q$mbt<TOj+2
    z6k6&HSgzR8@+~zPS10{g1(WH6Pmje{O6L~oAypvbBk{-ds`rvxS;Azwnm7gt4I=@Z
    zf1lF29|`DEUTB^m)MJ7hm?5^=mLP>E_=mV3&b5dfwa17il5+CEJut1v6pg73o@ut>
    zdhcx0qtn_|EC&Gf#WqNUYU%gGbU3McfkJJ1X<3HNiT=Xz`Sqy0ueJdroxt)79)6W8
    z+w9LDK}DbL<6ztc=s@S5)f}m81~>0uoWfi>mcZS}sU^2hv^i+PvVr$TbI&z?XhPl+
    zP!f5_=qffuX&HdSg(Qz!pt+ZM8CfIRHpaYk>z==rP`ZgHlXz7R)G0l?AFXS)BvOxz
    z20T?usL`K#Hs%e_;YnyanWoXrxKP6Y;O8E>{3@#}BDi!Ly6|#83elOs+)#8mZ1GS@
    za2Z&<AQSg?@C8uJzb%;Lo@wiB-zmo))pyBPV8j*U>IF+2dvU<6BKi$(MIF3`Eu{Rd
    zj`g$QUl3pTLtM7iH0gYss1D%WkEkQKBZ52C5HLp>#b2~rd884Wc6_h=vnL?>^ICIQ
    zt7&7|d{Oc1Mj9$yX?lBF>UxAmJwnW>@;7wcqt`r)W8S$R3H)Z)JTrOwyCiFQfGr%s
    zfMNaNgIq<;(1vMzlxIa%3}hTB{=Mo5Zl#%>Vs;JFfq{=<Lz9-o#*7lN^)H710B9{E
    zQDuWrf=xC|WIy`I&$dS(`(nLE!~QU|)FTq<dYFFX+sXP7pXXl+Ze@@Ootj>qdV5yZ
    zi>qGgvn9kcyl)xGl_gK-R~IB_JTzazeck^62`$m<<gxBd3H}sU$C3P3@rtNb);>|w
    Sd`idhJ9(nE<}muSkN?>xYR41+
  • src/org/openstreetmap/josm/data/ImageData.java

    literal 0
    HcmV?d00001
    
    diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java
    index 232ecc6588..7a0ae4a3f4 100644
    a b public class ImageData implements Data {  
    365365        afterImageUpdated(img);
    366366    }
    367367
     368    /**
     369     * Update the GPS track direction of the image and trigger update.
     370     * @param img the image to update
     371     * @param trackDirection the new GPS track direction
     372     * @since xxx
     373     */
     374    public void updateImageGpsTrack(ImageEntry img, double trackDirection) {
     375        img.setExifGpsTrack(trackDirection);
     376        afterImageUpdated(img);
     377    }
     378
     379    /**
     380     * Update the image horizontal positioning error and trigger update.
     381     * @param img the image to update
     382     * @param hposerr the new horizontal positionning error
     383     * @since xxx
     384     */
     385    public void updateImageHPosErr(ImageEntry img, double hposerr) {
     386        img.setExifHPosErr(hposerr);
     387        afterImageUpdated(img);
     388    }
     389
     390    /**
     391     * Update the image GPS differential mode and trigger update.
     392     * @param img the image to update
     393     * @param gpsDiffMode the new GPS differential mode
     394     * @since xxx
     395     */
     396    public void updateImageGpsDiffMode(ImageEntry img, Integer gpsDiffMode) {
     397        img.setGpsDiffMode(gpsDiffMode);
     398        afterImageUpdated(img);
     399    }
     400
     401    /**
     402     * Update the image GPS 2d/3d mode value and trigger update.
     403     * @param img the image to update
     404     * @param gps2d3dMode the new 2d/3d GPS mode
     405     * @since xxx
     406     */
     407    public void updateImageGps2d3dMode(ImageEntry img, Integer gps2d3dMode) {
     408        img.setGps2d3dMode(gps2d3dMode);
     409        afterImageUpdated(img);
     410    }
     411
     412    /**
     413     * Update the image GPS DOP value and trigger update.
     414     * @param img the image to update
     415     * @param exifGpsDop the new GPS DOP value
     416     * @since xxx
     417     */
     418    public void updateImageExifGpsDop(ImageEntry img, Double exifGpsDop) {
     419        img.setExifGpsDop(exifGpsDop);
     420        afterImageUpdated(img);
     421    }
     422
     423    /**
     424     * Update the image GPS datum and trigger update.
     425     * @param img the image to update
     426     * @param exifGpsDatum the new datum string value
     427     * @since xxx
     428     */
     429    public void updateImageExifGpsDatum(ImageEntry img, String exifGpsDatum) {
     430        img.setExifGpsDatum(exifGpsDatum);
     431        afterImageUpdated(img);
     432    }
     433
     434    /**
     435     * Update the image GPS processing method and trigger update.
     436     * @param img the image to update
     437     * @param exifGpsProcMethod the new GPS processing method
     438     * @since xxx
     439     */
     440    public void updateImageExifGpsProcMethod(ImageEntry img, String exifGpsProcMethod) {
     441        img.setExifGpsProcMethod(exifGpsProcMethod);
     442        afterImageUpdated(img);
     443    }
     444
    368445    /**
    369446     * Manually trigger the {@link ImageDataUpdateListener#imageDataUpdated(ImageData)}
    370447     */
  • src/org/openstreetmap/josm/data/gpx/GpxConstants.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxConstants.java b/src/org/openstreetmap/josm/data/gpx/GpxConstants.java
    index 543cdfebce..bfaff9f7e2 100644
    a b public interface GpxConstants {  
    145145     *  Fractional seconds are allowed for millisecond timing in tracklogs. */
    146146    String PT_TIME = "time";
    147147
     148    /** True Course/Bearing angle over ground.
     149     * @since xxx
     150     */
     151    String PT_COURSE = "course";
     152
    148153    /** Magnetic variation (in degrees) at the point. 0.0 &lt;= value &lt; 360.0 */
    149154    String PT_MAGVAR = "magvar";
    150155
    public interface GpxConstants {  
    188193     * Ordered list of all possible waypoint keys.
    189194     */
    190195    List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT,
    191             GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE,
    192             PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, PT_STD_HDEV, PT_STD_VDEV));
     196            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE, PT_FIX, PT_SAT, PT_COURSE,
     197            PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, PT_STD_HDEV, PT_STD_VDEV));
    193198
    194199    /**
    195200     * Ordered list of all possible route and track keys.
  • src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java b/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java
    index 023c495be3..be3f29beaf 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.util.Arrays;
    45import java.util.ArrayList;
    56import java.util.Collection;
    67import java.util.List;
    public final class GpxImageCorrelation {  
    7273        }
    7374
    7475        final GpxImageDirectionPositionSettings dirpos = settings.getDirectionPositionSettings();
     76        final GpxImageExtendedSettings extSettings = settings.getExtendedSettings();
    7577        final long offset = settings.getOffset();
    7678
    7779        boolean isFirst = true;
    public final class GpxImageCorrelation {  
    141143                        }
    142144                    }
    143145                    WayPoint nextWp = i < size - 1 ? wps.get(i + 1) : null;
    144                     ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, nextWp, dirpos);
     146                    ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, nextWp, dirpos, extSettings);
    145147                    prevWp = curWp;
    146148                    prevWpTime = curWpTime;
    147149                }
    148150            }
    149151        }
    150152        if (trkTag && prevWp != null) {
    151             ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, null, dirpos);
     153            ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, null, dirpos, extSettings);
    152154        }
    153155        Logging.debug("Correlated {0} total points", ret);
    154156        return ret;
    public final class GpxImageCorrelation {  
    209211        return null;
    210212    }
    211213
    212     private static int matchPoints(List<? extends GpxImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime,
    213             long offset, boolean interpolate, int tagTime, WayPoint nextWp, GpxImageDirectionPositionSettings dirpos) {
     214    static Double getHPosErr(WayPoint wp) {
     215        if (wp != null) {
     216            if (wp.attr.get(GpxConstants.PT_STD_HDEV) instanceof Float) {
     217                Float hposerr = (Float) wp.attr.get(GpxConstants.PT_STD_HDEV);
     218                if (hposerr != null) {
     219                    return hposerr.doubleValue();
     220                }
     221            } else if (wp.attr.get(GpxConstants.PT_STD_HDEV) instanceof Double) {               
     222                Double hposerr = (Double) wp.attr.get(GpxConstants.PT_STD_HDEV);
     223                if (hposerr != null) {
     224                    return hposerr;
     225                }
     226            }
     227        }
     228        return null;
     229    }
     230
     231    static Double getGpsDop(WayPoint wp) {
     232        if (wp != null) {
     233            if (wp.attr.get(GpxConstants.PT_PDOP) != null) {
     234                if (wp.attr.get(GpxConstants.PT_PDOP) instanceof Float) {
     235                    Float pdopvalue = (Float) wp.attr.get(GpxConstants.PT_PDOP);
     236                    return pdopvalue.doubleValue();
     237                } else if (wp.attr.get(GpxConstants.PT_PDOP) instanceof Double) {
     238                    Double pdopvalue = (Double) wp.attr.get(GpxConstants.PT_PDOP);
     239                    return pdopvalue.doubleValue();
     240                }
     241            } else if (wp.attr.get(GpxConstants.PT_HDOP) != null) {
     242                if (wp.attr.get(GpxConstants.PT_HDOP) instanceof Float) {
     243                    Float hdopvalue = (Float) wp.attr.get(GpxConstants.PT_HDOP);
     244                    return hdopvalue.doubleValue();
     245                } else if (wp.attr.get(GpxConstants.PT_HDOP) instanceof Double) {
     246                    Double hdopvalue = (Double) wp.attr.get(GpxConstants.PT_HDOP);
     247                    return hdopvalue.doubleValue();
     248                }
     249            }
     250        }
     251        return null;
     252    }
     253
     254    static Double getGpsTrack(WayPoint wp) {
     255        if (wp != null) {
     256            String trackvalue = wp.getString(GpxConstants.PT_COURSE);
     257            Logging.debug("track angle value: {0}", trackvalue);
     258            if (!Utils.isEmpty(trackvalue)) {
     259                try {
     260                    return Double.valueOf(trackvalue);
     261                } catch (NumberFormatException e) {
     262                    Logging.warn(e);
     263                }
     264            }
     265        }
     266        return null;
     267    }
     268
     269    static String getGpsProcMethod(String prevGpsFixMode, final String curGpsFixMode,
     270                                    final List<String> positioningModes) {
     271        String gpsProcMethod = null;
     272        Integer lowestProcIndex = null;
     273        int lowestGnssModeIdx = 3; // 2d or higher index in positioningModes list are Gnss methods
     274        try {
     275            lowestProcIndex = Math.min(positioningModes.indexOf(prevGpsFixMode), positioningModes.indexOf(curGpsFixMode));
     276            if (lowestProcIndex < 0) {
     277                return null;
     278            }
     279            gpsProcMethod = "GNSS" + " " + positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION";
     280            if (lowestProcIndex < lowestGnssModeIdx) {
     281                gpsProcMethod = positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION";
     282            } else {
     283                gpsProcMethod = "GNSS" + " " + positioningModes.get(lowestProcIndex).toUpperCase() + " " + "CORRELATION";
     284            }
     285            gpsProcMethod = gpsProcMethod.replace("FLOAT RTK", "RTK_FLOAT");
     286            gpsProcMethod = gpsProcMethod.replace(" RTK ", " RTK_FIX ");
     287        } catch (ArrayIndexOutOfBoundsException ex) {
     288            Logging.warn(ex);
     289        }
     290        return gpsProcMethod;
     291    }
     292
     293    static Integer getGps2d3dMode(String prevGpsFixMode, final String curGpsFixMode,
     294                                final List<String> positioningModes) {
     295        Integer lowestMode = null;
     296        lowestMode = Math.min(positioningModes.indexOf(prevGpsFixMode), positioningModes.indexOf(curGpsFixMode));
     297        if (lowestMode > 3) {
     298            return 3;
     299        }
     300        if (lowestMode > 2) {
     301            return 2;
     302        }
     303        return null;
     304    }
     305
     306    private static int matchPoints(List<? extends GpxImageEntry>
     307                                        images,
     308                                        WayPoint prevWp,
     309                                        long prevWpTime,
     310                                        WayPoint curWp,
     311                                        long curWpTime,
     312                                        long offset,
     313                                        boolean interpolate,
     314                                        int tagTime,
     315                                        WayPoint nextWp,
     316                                        GpxImageDirectionPositionSettings dirpos,
     317                                        GpxImageExtendedSettings extSetting) {
    214318
    215319        final boolean isLast = nextWp == null;
    216320
    public final class GpxImageCorrelation {  
    236340        int ret = 0;
    237341        Double speed = null;
    238342        Double prevElevation = null;
     343        Double prevHPosErr = null;
     344        Double prevGpsDop = null;
     345        Double prevGpsTrack = null;
     346        String prevGpsFixMode = null;
     347        //list of differential GPS mode
     348        //TODO move these lists in Gpx.Constants?
     349        final List<String> diffMode = Arrays.asList("dgps", "float rtk", "rtk");
     350        final List<String> positioningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk");
     351
    239352
    240353        if (prevWp != null && interpolate) {
    241354            double distance = prevWp.greatCircleDistance(curWp);
    public final class GpxImageCorrelation {  
    244357                speed = 3600 * distance / (curWpTime - prevWpTime);
    245358            }
    246359            prevElevation = getElevation(prevWp);
     360            prevHPosErr = getHPosErr(prevWp);
     361            prevGpsDop = getGpsDop(prevWp);
     362            prevGpsTrack = getGpsTrack(prevWp);
     363            prevGpsFixMode = prevWp.getString(GpxConstants.PT_FIX);
    247364        }
    248365
    249366        final Double curElevation = getElevation(curWp);
     367        final Double curHPosErr = getHPosErr(curWp);
     368        final Double curGpsDop = getGpsDop(curWp);
     369        final Double curGpsTrack = getGpsTrack(curWp);
     370        final String curGpsFixMode = curWp.getString(GpxConstants.PT_FIX);
    250371
    251372        if (!interpolate || isLast) {
    252373            final long half = Math.abs(curWpTime - prevWpTime) / 2;
    public final class GpxImageCorrelation {  
    266387                    } else {
    267388                        curTmp.setPos(curWp.getCoor());
    268389                    }
     390                    //TODO fix this, nextWp doesn't exist here
    269391                    if (nextWp != null && dirpos.isSetImageDirection()) {
    270392                        double direction = curWp.bearing(nextWp);
    271393                        curTmp.setExifImgDir(computeDirection(direction, dirpos.getImageDirectionAngleOffset()));
    public final class GpxImageCorrelation {  
    317439                    if (curElevation != null && prevElevation != null) {
    318440                        curTmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff + dirpos.getElevationShift());
    319441                    }
     442
     443                    // Add exif GpsHPositioningerror interpolated value
     444                    if (curHPosErr != null && prevHPosErr != null) {
     445                        Double interpolatedValue = prevHPosErr + (curHPosErr - prevHPosErr) * timeDiff;
     446                        curTmp.setExifHPosErr(Math.round(interpolatedValue*10000)/10000.0);
     447                    }
     448
     449                    // Add exif GpsDifferentialMode
     450                    // Get previous and current waypoint differential. As no interpolation is possible,
     451                    // set differential mode to 0 if any waypoint isn't in differential mode.
     452                    if (prevGpsFixMode != null) {
     453                        if (diffMode.contains(prevGpsFixMode) && diffMode.contains(curGpsFixMode)) {
     454                            curTmp.setGpsDiffMode(1);
     455                        } else {
     456                            curTmp.setGpsDiffMode(0);
     457                        }
     458                    }
     459
     460                    // Add exif GpsMeasureMode
     461                    if (prevGpsFixMode != null && curGpsFixMode != null) {
     462                        Integer gps2d3dMode = getGps2d3dMode(prevGpsFixMode, curGpsFixMode, positioningModes);
     463                        if (gps2d3dMode != null) {
     464                            curTmp.setGps2d3dMode(gps2d3dMode);
     465                        }
     466                    }
     467                   
     468                    // Add exif GpsProcessingMethod. As no interpolation is possible,
     469                    // set processing method to the "lowest" previous and current processing method value.
     470                    if (prevGpsFixMode != null && curGpsFixMode != null) {
     471                        String gpsProcMethod = getGpsProcMethod(prevGpsFixMode, curGpsFixMode, positioningModes);                       
     472                        if (gpsProcMethod != null) {
     473                            curTmp.setExifGpsProcMethod(gpsProcMethod);
     474                        }
     475                    }
     476
     477                    // Add Exif GpsDop with interpolated GPS DOP value
     478                    if (curGpsDop != null && prevGpsDop != null) {
     479                        Double interpolatedValue = prevGpsDop + (curGpsDop - prevGpsDop) * timeDiff;
     480                        curTmp.setExifGpsDop(Math.round(interpolatedValue*100)/100.0);
     481                    }
     482
     483                    // Add Exif GpsTrack tag
     484                    if (dirpos.isSetGpxTrackDirection()) {
     485                        if (curGpsTrack != null && prevGpsTrack != null) {
     486                            curTmp.setExifGpsTrack(prevGpsTrack + (curGpsTrack - prevGpsTrack) * timeDiff);
     487                        }
     488                    }
     489                   
     490                    // Add GpsDatum tag
     491                    if (extSetting.isSetImageGpsDatum()) {
     492                        if (diffMode.contains(prevGpsFixMode) && diffMode.contains(curGpsFixMode)) {
     493                            curTmp.setExifGpsDatum(extSetting.getImageGpsDatum());
     494                        } else //without differential mode, datum is WGS-84
     495                            curTmp.setExifGpsDatum("WGS-84");
     496                    }
     497
    320498                    curTmp.setGpsTime(curImg.getExifInstant().minusMillis(offset));
    321499                    curTmp.flagNewGpsData();
    322500                    curImg.tmpUpdated();
  • src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelationSettings.java
    index 079d69f99c..aa08ec4f7e 100644
    a b public class GpxImageCorrelationSettings {  
    1212    private final long offset;
    1313    private final boolean forceTags;
    1414    private final GpxImageDirectionPositionSettings directionPositionSettings;
     15    private final GpxImageExtendedSettings extendedSettings;
    1516
    1617    /**
    1718     * Constructs a new {@code GpxImageCorrelationSettings}.
    public class GpxImageCorrelationSettings {  
    1920     * @param forceTags force tagging of all photos, otherwise prefs are used
    2021     */
    2122    public GpxImageCorrelationSettings(long offset, boolean forceTags) {
    22         this(offset, forceTags, new GpxImageDirectionPositionSettings(false, 0, 0, 0, 0));
     23        this(offset, forceTags,
     24        new GpxImageDirectionPositionSettings(false, 0, false, 0, 0, 0),
     25        new GpxImageExtendedSettings(false, null)
     26        );
    2327    }
    2428
    2529    /**
    public class GpxImageCorrelationSettings {  
    3034     */
    3135    public GpxImageCorrelationSettings(long offset, boolean forceTags,
    3236            GpxImageDirectionPositionSettings directionPositionSettings) {
     37        this(offset, forceTags, directionPositionSettings,
     38        new GpxImageExtendedSettings(false, null));
     39    }
     40
     41    /**
     42     * Constructs a new {@code GpxImageCorrelationSettings}.
     43     * @param offset offset in milliseconds
     44     * @param forceTags force tagging of all photos, otherwise prefs are used
     45     * @param directionPositionSettings direction/position settings
     46     * @param extendedSettings GPS datum settings
     47     * @since xxx @extendedSettings was added
     48     */
     49    public GpxImageCorrelationSettings(long offset, boolean forceTags,
     50            GpxImageDirectionPositionSettings directionPositionSettings,
     51            GpxImageExtendedSettings extendedSettings) {
    3352        this.offset = offset;
    3453        this.forceTags = forceTags;
    3554        this.directionPositionSettings = Objects.requireNonNull(directionPositionSettings);
     55        this.extendedSettings = Objects.requireNonNull(extendedSettings);
    3656    }
    37 
     57   
    3858    /**
    3959     * Returns the offset in milliseconds.
    4060     * @return the offset in milliseconds
    public class GpxImageCorrelationSettings {  
    5979        return directionPositionSettings;
    6080    }
    6181
     82    /**
     83     * Returns the extended exif metadata settings.
     84     * @return the extended exif metadata settings
     85     * @since xxx
     86     */
     87    public GpxImageExtendedSettings getExtendedSettings() {
     88        return extendedSettings;
     89    }
     90
    6291    @Override
    6392    public String toString() {
    6493        return "[offset=" + offset + ", forceTags=" + forceTags
    65                 + ", directionPositionSettings=" + directionPositionSettings + ']';
     94                + ", directionPositionSettings=" + directionPositionSettings
     95                + ", extendedSettings=" + extendedSettings + ']';
    6696    }
    6797}
  • src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageDirectionPositionSettings.java
    index 20dd5e0f73..53483c2e4f 100644
    a b public class GpxImageDirectionPositionSettings {  
    99
    1010    private final boolean setImageDirection;
    1111    private final double imageDirectionAngleOffset;
     12    private final boolean setGpxTrackDirection;
    1213    private final double shiftImageX;
    1314    private final double shiftImageY;
    1415    private final double elevationShift;
    public class GpxImageDirectionPositionSettings {  
    1718     * Constructs a new {@code GpxImageDirectionPositionSettings}.
    1819     * @param setImageDirection determines if image direction must be set towards the next GPX waypoint
    1920     * @param imageDirectionAngleOffset direction angle offset in degrees
     21     * @param setGpxTrackDirection determines if image course direction must be set
    2022     * @param shiftImageX image shift on X axis relative to the direction in meters
    2123     * @param shiftImageY image shift on Y axis relative to the direction in meters
    2224     * @param elevationShift image elevation shift in meters
     25     * @since xxx @setGpsTrackDirection was added
    2326     */
    2427    public GpxImageDirectionPositionSettings(
    25             boolean setImageDirection, double imageDirectionAngleOffset, double shiftImageX, double shiftImageY, double elevationShift) {
     28            boolean setImageDirection, double imageDirectionAngleOffset, boolean setGpxTrackDirection,
     29            double shiftImageX, double shiftImageY, double elevationShift) {
    2630        this.setImageDirection = setImageDirection;
    2731        this.imageDirectionAngleOffset = imageDirectionAngleOffset;
     32        this.setGpxTrackDirection = setGpxTrackDirection;
    2833        this.shiftImageX = shiftImageX;
    2934        this.shiftImageY = shiftImageY;
    3035        this.elevationShift = elevationShift;
    public class GpxImageDirectionPositionSettings {  
    4651        return imageDirectionAngleOffset;
    4752    }
    4853
     54    /**
     55     * Determines if GPS course direction must be set. Angle value is from the gpx trace.
     56     * @return {@code true} if image GPS course must be set
     57     * @since xxx
     58     */
     59    public boolean isSetGpxTrackDirection() {
     60        return setGpxTrackDirection;
     61    }
     62
    4963    /**
    5064     * Returns image shift on X axis relative to the direction in meters
    5165     * @return image shift on X axis relative to the direction in meters
    public class GpxImageDirectionPositionSettings {  
    7387    @Override
    7488    public String toString() {
    7589        return "[setImageDirection=" + setImageDirection
    76                 + ", imageDirectionAngleOffset=" + imageDirectionAngleOffset + ", shiftImageX=" + shiftImageX
    77                 + ", shiftImageY=" + shiftImageY + ", elevationShift=" + elevationShift + ']';
     90                + ", imageDirectionAngleOffset=" + imageDirectionAngleOffset
     91                + ", setImageGpxTrackDirection=" + setGpxTrackDirection
     92                + ", shiftImageX=" + shiftImageX
     93                + ", shiftImageY=" + shiftImageY
     94                + ", elevationShift=" + elevationShift + ']';
    7895    }
    7996}
  • src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java b/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
    index f4161cd78f..4886980cdd 100644
    a b public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    3232    private Integer exifOrientation;
    3333    private LatLon exifCoor;
    3434    private Double exifImgDir;
     35    private Double exifGpsTrack;
     36    private Double exifHPosErr;
     37    private Double exifGpsDop;
    3538    private Instant exifTime;
    3639    private Projections cameraProjection = Projections.UNKNOWN;
    3740    /**
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    5760    private Double speed;
    5861    /** Elevation (altitude) in meters */
    5962    private Double elevation;
     63    /** GPS Differential mode */
     64    private Integer gpsDiffMode;
     65    /** GPS Measure mode */
     66    private Integer gps2d3dMode;
     67    /** GPS Datum */
     68    private String exifGpsDatum;
     69    /** GPS processing method */
     70    private String exifGpsProcMethod;
    6071    /** The time after correlation with a gpx track */
    6172    private Instant gpsTime;
    6273
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    8899        exifOrientation = other.exifOrientation;
    89100        exifCoor = other.exifCoor;
    90101        exifImgDir = other.exifImgDir;
     102        exifGpsTrack = other.exifGpsTrack;
     103        exifHPosErr = other.exifHPosErr;
     104        gpsDiffMode = other.gpsDiffMode;
     105        gps2d3dMode = other.gps2d3dMode;
     106        exifGpsDop = other.exifGpsDop;
     107        exifGpsDatum = other.exifGpsDatum;
     108        exifGpsProcMethod = other.exifGpsProcMethod;
    91109        exifTime = other.exifTime;
    92110        isNewGpsData = other.isNewGpsData;
    93111        exifGpsTime = other.exifGpsTime;
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    169187        return elevation;
    170188    }
    171189
     190    /**
     191     * Return the GPS Differential mode value. The GPS Differential mode value from the temporary
     192     * copy is returned if that copy exists.
     193     * @return the differential mode value
     194     * @since xxx
     195     */
     196    @Override
     197    public Integer getGpsDiffMode() {
     198        if (tmp != null)
     199            return tmp.gpsDiffMode;
     200        return gpsDiffMode;
     201    }
     202
     203    /**
     204     * Return the GPS 2d or 3d mode value. The GPS mode value form the temporary
     205     * copy is returned if that copy exists.
     206     * @return the GPS 2d/3d mode value
     207     * @since xxx
     208     */
     209    @Override
     210    public Integer getGps2d3dMode() {
     211        if (tmp != null)
     212            return tmp.gps2d3dMode;
     213        return gps2d3dMode;
     214    }
     215
     216    /**
     217     * Return the GPS DOP value. The GPS DOP value from the temporary
     218     * copy is returned if that copy exists.
     219     * @return the DOP value
     220     * @since xxx
     221     */
     222    @Override
     223    public Double getExifGpsDop() {
     224        if (tmp != null)
     225            return tmp.exifGpsDop;
     226        return exifGpsDop;
     227    }
     228
     229    /**
     230     * Return the exif GPS coordinates datum value.
     231     * @return The datum value
     232     * @since xxx
     233     */
     234    @Override
     235    public String getExifGpsDatum() {
     236        if (tmp != null)
     237            return tmp.exifGpsDatum;
     238        return exifGpsDatum;
     239    }
     240
     241    /**
     242     * Return the exif GPS processing method string
     243     * @return the processing method string
     244     * @since xxx
     245     */
     246    @Override
     247    public String getExifGpsProcMethod() {
     248        if (tmp != null)
     249            return tmp.exifGpsProcMethod;
     250        return exifGpsProcMethod;
     251    }
     252
    172253    /**
    173254     * Returns the GPS time value. The GPS time value from the temporary copy
    174255     * is returned if that copy exists.
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    272353            return tmp.exifImgDir;
    273354        return exifImgDir;
    274355    }
     356   
     357    /**
     358     * Convenient way to determine if this entry has a EXIF GPS track angle value,
     359     * without the cost of building a defensive copy.
     360     * @return {@code true} if this entry has a EXIF track angle value
     361     * @since xxx
     362     */
     363    @Override
     364    public Double getExifGpsTrack() {
     365        if (tmp != null)
     366            return tmp.exifGpsTrack;
     367        return exifGpsTrack;
     368    }
     369
     370    /**
     371     * Convenient way to determine if this entry has a EXIF GPS horizontal positionning error value,
     372     * without the cost of building a defensive copy.
     373     * @return {@code true} if this entry has a EXIF GPS horizontal positionning error value
     374     * @since xxx
     375     */
     376    @Override
     377    public Double getExifHPosErr() {
     378        if (tmp != null)
     379            return tmp.exifHPosErr;
     380        return exifHPosErr;
     381    }
    275382
    276383    @Override
    277384    public Instant getLastModified() {
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    345452        this.elevation = elevation;
    346453    }
    347454
     455    /**
     456     * Sets GPS Differential mode.
     457     * @param gpsDiffMode GPS Differential mode
     458     * @since xxx
     459     */
     460    @Override
     461    public void setGpsDiffMode(Integer gpsDiffMode) {
     462        this.gpsDiffMode = gpsDiffMode;
     463    }
     464
     465    /**
     466     * Sets GPS 2d/3d mode.
     467     * @param gps2d3dMode GPS 2d/3d mode value
     468     * @since xxx
     469     */
     470    @Override
     471    public void setGps2d3dMode(Integer gps2d3dMode) {
     472        this.gps2d3dMode = gps2d3dMode;
     473    }
     474
     475    /**
     476     * Sets GPS DOP value.
     477     * @param exifGpsDop GPS DOP value
     478     * @since xxx
     479     */
     480    @Override
     481    public void setExifGpsDop(Double exifGpsDop) {
     482        this.exifGpsDop = exifGpsDop;
     483    }
     484
     485    /**
     486     * Sets the GPS Datum.
     487     * @param exifGpsDatum GPS Datum
     488     * @since xxx
     489     */
     490    @Override
     491    public void setExifGpsDatum(String exifGpsDatum) {
     492        this.exifGpsDatum = exifGpsDatum;
     493    }
     494
     495    /**
     496     * Sets the GPS Processing Method.
     497     * @param exifGpsProcMethod GPS Processing Method
     498     * @since xxx
     499     */
     500    @Override
     501    public void setExifGpsProcMethod(String exifGpsProcMethod) {
     502        this.exifGpsProcMethod = exifGpsProcMethod;
     503    }
     504
    348505    /**
    349506     * Sets associated file.
    350507     * @param file associated file
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    412569        this.exifImgDir = exifDir;
    413570    }
    414571
     572    /**
     573     * Sets the exif GPS track (move direction angle)
     574     * @param exifGpsTrack the exif GPS track angle
     575     * @since xxx
     576     */
     577    @Override
     578    public void setExifGpsTrack(Double exifGpsTrack) {
     579        this.exifGpsTrack = exifGpsTrack;
     580    }
     581
     582    /**
     583     * Sets the exif horizontal positioning error
     584     * @param exifHposErr the Exif horizontal positionning error
     585     * @since xxx
     586     */
     587    @Override
     588    public void setExifHPosErr(Double exifHPosErr) {
     589        this.exifHPosErr = exifHPosErr;
     590    }
     591
    415592    /**
    416593     * Sets the IPTC caption.
    417594     * @param iptcCaption the IPTC caption
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    507684    @Override
    508685    public int hashCode() {
    509686        return Objects.hash(height, width, isNewGpsData,
    510             elevation, exifCoor, exifGpsTime, exifImgDir, exifOrientation, exifTime,
    511             iptcCaption, iptcHeadline, iptcKeywords, iptcObjectName,
    512             file, gpsTime, pos, speed, tmp, cameraProjection);
     687            elevation, exifCoor, exifGpsTime, exifImgDir, exifGpsTrack, exifHPosErr,
     688            exifGpsDop, gpsDiffMode, gps2d3dMode, exifGpsDatum, exifGpsProcMethod,
     689            exifOrientation, exifTime, iptcCaption, iptcHeadline, iptcKeywords,
     690            iptcObjectName, file, gpsTime, pos, speed, tmp, cameraProjection);
    513691    }
    514692
    515693    @Override
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    526704            && Objects.equals(exifCoor, other.exifCoor)
    527705            && Objects.equals(exifGpsTime, other.exifGpsTime)
    528706            && Objects.equals(exifImgDir, other.exifImgDir)
     707            && Objects.equals(exifGpsTrack, other.exifGpsTrack)
     708            && Objects.equals(exifHPosErr, other.exifHPosErr)
     709            && Objects.equals(gpsDiffMode, other.gpsDiffMode)
     710            && Objects.equals(gps2d3dMode, other.gps2d3dMode)
     711            && Objects.equals(exifGpsDop, other.exifGpsDop)
     712            && Objects.equals(exifGpsDatum, other.exifGpsDatum)
     713            && Objects.equals(exifGpsProcMethod, other.exifGpsProcMethod)
    529714            && Objects.equals(exifOrientation, other.exifOrientation)
    530715            && Objects.equals(exifTime, other.exifTime)
    531716            && Objects.equals(iptcCaption, other.iptcCaption)
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    570755     * Copy the values from the temporary variable to the main instance. The
    571756     * temporary variable is deleted.
    572757     * @see #discardTmp()
     758     * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod added
    573759     */
    574760    public void applyTmp() {
    575761        if (tmp != null) {
    public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType  
    578764            elevation = tmp.elevation;
    579765            gpsTime = tmp.gpsTime;
    580766            exifImgDir = tmp.exifImgDir;
     767            exifGpsTrack = tmp.exifGpsTrack;
     768            exifHPosErr = tmp.exifHPosErr;
     769            gpsDiffMode = tmp.gpsDiffMode;
     770            gps2d3dMode = tmp.gps2d3dMode;
     771            exifGpsDop = tmp.exifGpsDop;
     772            exifGpsDatum = tmp.exifGpsDatum;
     773            exifGpsProcMethod = tmp.exifGpsProcMethod;
    581774            isNewGpsData = isNewGpsData || tmp.isNewGpsData;
    582775            tmp = null;
    583776        }
  • new file src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java

    diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java b/src/org/openstreetmap/josm/data/gpx/GpxImageExtendedSettings.java
    new file mode 100644
    index 0000000000..b7e88ec695
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.gpx;
     3
     4/**
     5 * Image extended exif metadata settings used by {@link GpxImageCorrelationSettings}.
     6 * @since xxx
     7 */
     8public class GpxImageExtendedSettings {
     9
     10    private final boolean setImageGpsDatum;
     11    private final String imageGpsDatum;
     12   
     13    /**
     14     * Construcs a new {@code GpxImageExtendedSettings}.
     15     * @param setImageGpsDatum determines if images GPS datum must be set
     16     * @param imageGpsDatum determines the GPS coordinates datum value to be set
     17     */
     18    public GpxImageExtendedSettings(
     19        boolean setImageGpsDatum, String imageGpsDatum) {
     20            this.setImageGpsDatum = setImageGpsDatum;
     21            this.imageGpsDatum = imageGpsDatum;
     22        }
     23   
     24    /**
     25     * Determines if image GPS datum must be set
     26     * @return if the GPS datum must be set
     27     */
     28    public boolean isSetImageGpsDatum() {
     29        return setImageGpsDatum;
     30    }
     31   
     32    /**
     33     * Return the GPS coordinates datum code.
     34     * @return the datum code
     35     */
     36    public String getImageGpsDatum() {
     37        return imageGpsDatum;
     38    }
     39}
  • src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java

    diff --git a/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java b/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java
    index 3dcd79705d..04a9504e95 100644
    a b public interface IImageEntry<I extends IImageEntry<I>> {  
    218218     */
    219219    Double getElevation();
    220220
     221    /**
     222     * Return the GPS Differential mode value. The differential mode value from the temporary
     223     * copy is returned if that copy exists.
     224     * @return the fix mode value
     225     * @since xxx
     226     */
     227    Integer getGpsDiffMode();
     228
     229    /**
     230     * Return the GPS 2d/3d mode value. The 2d/3d mode value from the temporary
     231     * copy is returned if that copy exists.
     232     * @return the 2d/3d mode value
     233     * @since xxx
     234     */
     235    Integer getGps2d3dMode();
     236
     237    /**
     238     * Return the GPS DOP value. The GPS DOP value from the temporary
     239     * copy is return if that copy exists.
     240     * @return the GPS DOP value
     241     * @since xxx
     242     */
     243    Double getExifGpsDop();
     244
     245    /**
     246     * Return the GPS datum value. The GPS datum value from the temporary
     247     * copy is return if that copy exists.
     248     * @return the GPS datum value
     249     * @since xxx
     250     */
     251    String getExifGpsDatum();
     252
     253    /**
     254     * Return the GPS processing method. The processing method value from the temporary
     255     * copy is return if that copy exists.
     256     * @return the GPS processing method
     257     * @since xxx
     258     */
     259    String getExifGpsProcMethod();
     260
    221261    /**
    222262     * Returns the image direction. The image direction from the temporary
    223263     * copy is returned if that copy exists.
    224264     * @return The image camera angle
    225265     */
    226266    Double getExifImgDir();
     267   
     268    /**
     269     * Returns the image GPS track direction. The GPS track direction from the temporary
     270     * copy is returned if that copy exists.
     271     * @return the image GPS track direction angle
     272     * @since xxx
     273     */
     274    Double getExifGpsTrack();
    227275
     276    /**
     277     * Returns the image horizontal positionning error. The image positionning error
     278     * from the temporary copy is returned if that copy exists.
     279     * @return the image horizontal positionning error
     280     * @since xxx
     281     */
     282    Double getExifHPosErr();
     283   
    228284    /**
    229285     * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
    230286     * @return {@code true} if this entry has a EXIF time
    public interface IImageEntry<I extends IImageEntry<I>> {  
    251307     */
    252308    Instant getGpsInstant();
    253309
     310    /**
     311     * Returns the Exif GPS Time value. The Exif GPS time value from the temporary copy
     312     * is returned if that copy exists.
     313     * @return the Exif GPS time value
     314     * @since xxx
     315     */
     316    Instant getExifGpsInstant();
     317
    254318    /**
    255319     * Returns the IPTC caption.
    256320     * @return the IPTC caption
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
    index 92d16c1ed1..faaaf50268 100644
    a b public class OsmDataLayer extends AbstractOsmDataLayer  
    978978        addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_SYM, "wpt_symbol", null);
    979979        addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_TYPE, null, null);
    980980
     981        // Angle info
     982        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_COURSE, "gps:course");
     983
    981984        // Accuracy info
    982985        addStringIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_FIX, "gps:fix", null);
    983986        addIntegerIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_SAT, "gps:sat");
    984987        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_HDOP, "gps:hdop");
    985988        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_VDOP, "gps:vdop");
    986989        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_PDOP, "gps:pdop");
     990        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_STD_HDEV, "gps:stdhdev");
     991        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_STD_VDEV, "gps:stdvdev");
    987992        addDoubleIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_AGEOFDGPSDATA, "gps:ageofdgpsdata");
    988993        addIntegerIfPresent(wpt, n, gpxPrefix, GpxConstants.PT_DGPSID, "gps:dgpsid");
    989994
  • src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java b/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
    index a47b4bb734..a3478fbea2 100644
    a b import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;  
    5353import org.openstreetmap.josm.data.gpx.GpxDataContainer;
    5454import org.openstreetmap.josm.data.gpx.GpxImageCorrelation;
    5555import org.openstreetmap.josm.data.gpx.GpxImageCorrelationSettings;
     56import org.openstreetmap.josm.data.gpx.GpxImageExtendedSettings;
    5657import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
    5758import org.openstreetmap.josm.data.gpx.GpxTimezone;
    5859import org.openstreetmap.josm.data.gpx.WayPoint;
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    168169                Config.getPref().put("geoimage.timezone", timezone.formatTimezone());
    169170                Config.getPref().put("geoimage.delta", delta.formatOffset());
    170171                Config.getPref().putBoolean("geoimage.showThumbs", yLayer.useThumbs);
     172                Config.getPref().put("geoimage.datum", tfDatum.getText());
    171173
    172174                yLayer.useThumbs = cbShowThumbs.isSelected();
    173175                yLayer.startLoadThumbs();
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    246248
    247249    private ExtendedDialog syncDialog;
    248250    private JPanel outerPanel;
     251    private JPanel expertPanel;
    249252    private JosmComboBox<GpxDataWrapper> cbGpx;
    250253    private JButton buttonSupport;
    251254    private JosmTextField tfTimezone;
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    253256    private JCheckBox cbExifImg;
    254257    private JCheckBox cbTaggedImg;
    255258    private JCheckBox cbShowThumbs;
     259    private JSeparator sepExtendedTags;
     260    private JLabel labelExtTags;
     261    private JLabel labelDatum;
    256262    private JLabel statusBarText;
    257263    private JSeparator sepDirectionPosition;
    258264    private ImageDirectionPositionPanel pDirectionPosition;
     265    private JCheckBox cbAddGpsDatum;
     266    private JosmTextField tfDatum;
    259267
    260268    // remember the last number of matched photos
    261269    private int lastNumMatched;
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    472480        }
    473481    }
    474482
     483    static String loadGpsDatum() {
     484        String gpsDatum = Config.getPref().get("geoimage.datum", "WGS-84");
     485        return gpsDatum;
     486    }
     487
    475488    @Override
    476489    public void actionPerformed(ActionEvent ae) {
    477490        NoGpxDataWrapper nogdw = new NoGpxDataWrapper();
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    619632        gbc.gridy = y++;
    620633        panelTf.add(cbShowThumbs, gbc);
    621634
     635        //Image direction and position offset GUI
    622636        gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0);
    623637        sepDirectionPosition = new JSeparator(SwingConstants.HORIZONTAL);
    624638        gbc.gridy = y++;
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    630644        pDirectionPosition = ImageDirectionPositionPanel.forGpxTrace();
    631645        panelTf.add(pDirectionPosition, gbc);
    632646
     647        //Extended tags GUI panel
     648        expertPanel = new JPanel(new GridBagLayout());
     649        gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0);
     650        sepExtendedTags = new JSeparator(SwingConstants.HORIZONTAL);
     651        gbc.gridx = 0;
     652        gbc.gridy = 0;
     653        expertPanel.add(sepExtendedTags, gbc);
     654
     655        labelExtTags = new JLabel(tr("Extended tags"));
     656        cbAddGpsDatum = new JCheckBox(tr("Set datum for images coordinates"));
     657        cbAddGpsDatum.addActionListener(e -> tfDatum.setEnabled(!tfDatum.isEnabled()));
     658
     659        labelDatum = new JLabel(tr("Datum: "));
     660        //TODO An AutoCompComboBox would be nice to list the recent datum values. I don't have the skill to add it.
     661        tfDatum = new JosmTextField(loadGpsDatum(), 8);
     662        tfDatum.setToolTipText(tr("<html>Enter the datum for your images coordinates. Default value is WGS-84.<br>" +
     663                                "For RTK it could be your local CRS epsg code.<br>(e.g. EPSG:9782 for France mainland.)</html>"));
     664        tfDatum.setEnabled(false);
     665
     666        gbc = GBC.eol();
     667        gbc.gridx = 0;
     668        gbc.gridy = 1;
     669        expertPanel.add(labelExtTags, gbc);
     670
     671        gbc = GBC.eol();
     672        gbc.gridx = 0;
     673        gbc.gridy = 2;
     674        expertPanel.add(cbAddGpsDatum, gbc);
     675
     676        //TODO move this line a little more to the right
     677        gbc = GBC.std();
     678        gbc.gridx = 1;
     679        gbc.gridy = 3;
     680        expertPanel.add(labelDatum);
     681
     682        gbc = GBC.eol();
     683        gbc.gridx = 2;
     684        gbc.gridy = 3;
     685        expertPanel.add(tfDatum, gbc);
     686
     687        //Add expertPanel to panelTf
     688        gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0);
     689        gbc.gridy = y++;
     690        panelTf.add(expertPanel, gbc);
     691       
    633692        final JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
    634693        statusPanel.setBorder(BorderFactory.createLoweredBevelBorder());
    635694        statusBarText = new JLabel(" ");
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    651710        tfOffset.getDocument().addDocumentListener(statusBarUpdater);
    652711        cbExifImg.addItemListener(statusBarUpdaterWithRepaint);
    653712        cbTaggedImg.addItemListener(statusBarUpdaterWithRepaint);
     713        cbAddGpsDatum.addItemListener(statusBarUpdaterWithRepaint);
     714        tfDatum.getDocument().addDocumentListener(statusBarUpdater);
    654715        pDirectionPosition.addChangeListenerOnComponents(statusBarUpdaterWithRepaint);
    655716        pDirectionPosition.addItemListenerOnComponents(statusBarUpdaterWithRepaint);
    656717
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    678739        }
    679740    }
    680741
     742    public GpxImageExtendedSettings getSettings() {
     743        return new GpxImageExtendedSettings(
     744            cbAddGpsDatum.isSelected(),
     745            tfDatum.getText());
     746    }
     747
    681748    @Override
    682749    public void expertChanged(boolean isExpert) {
    683750        if (buttonSupport != null) {
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    689756        if (pDirectionPosition != null) {
    690757            pDirectionPosition.setVisible(isExpert);
    691758        }
     759        if (expertPanel != null) {
     760            expertPanel.setVisible(isExpert);
     761        }
    692762        if (syncDialog != null) {
    693763            syncDialog.pack();
    694764        }
    public class CorrelateGpxWithImages extends AbstractAction implements ExpertMode  
    786856            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds
    787857            lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data,
    788858                    pDirectionPosition.isVisible() ?
    789                             new GpxImageCorrelationSettings(offsetMs, forceTags, pDirectionPosition.getSettings()) :
     859                            new GpxImageCorrelationSettings(offsetMs, forceTags, pDirectionPosition.getSettings(),
     860                                                            new GpxImageExtendedSettings(cbAddGpsDatum.isSelected(), tfDatum.getText())) :
    790861                            new GpxImageCorrelationSettings(offsetMs, forceTags));
    791862
    792863            return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>",
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDirectionPositionPanel.java
    index 1d998b0795..9057240185 100644
    a b public class ImageDirectionPositionPanel extends JPanel {  
    2828
    2929    private final JCheckBox cChangeImageDirection = new JCheckBox();
    3030    private final JSpinner sOffsetDegrees = new JSpinner(new SpinnerNumberModel(0, -360, 360, 1));
     31    private final JCheckBox cSetGpxTrackTag = new JCheckBox();
    3132
    3233    private final JSpinner sX = new JSpinner(new SpinnerNumberModel(0.0, -50.0, 50.0, 0.1));
    3334    private final JSpinner sY = new JSpinner(new SpinnerNumberModel(0.0, -50.0, 50.0, 0.1));
    public class ImageDirectionPositionPanel extends JPanel {  
    3738     * Constructs a new {@code ImageMetadataModificationPanel}
    3839     * @param changeDirectionText the text displayed for the change image direction combobox
    3940     */
    40     protected ImageDirectionPositionPanel(String changeDirectionText) {
     41    protected ImageDirectionPositionPanel(String changeDirectionText, boolean hideGpxTrack) {
    4142        super(new GridBagLayout());
    4243
    4344        cChangeImageDirection.setText(changeDirectionText);
    public class ImageDirectionPositionPanel extends JPanel {  
    4546        cChangeImageDirection.addActionListener(e -> sOffsetDegrees.setEnabled(!sOffsetDegrees.isEnabled()));
    4647        addSetting(tr("Offset angle in degrees:"), sOffsetDegrees);
    4748        sOffsetDegrees.setEnabled(false);
     49        if (!hideGpxTrack) {
     50            cChangeImageDirection.addActionListener(e -> cSetGpxTrackTag.setEnabled(!cSetGpxTrackTag.isEnabled()));
     51            cSetGpxTrackTag.setText(tr("Set image course direction (from gpx track)"));
     52            add(cSetGpxTrackTag, GBC.eol().insets(0, 0, 0, 5));
     53            cSetGpxTrackTag.setEnabled(false);
     54        }
    4855
    4956        add(new JSeparator(SwingConstants.HORIZONTAL),
    5057                GBC.eol().fill(GBC.HORIZONTAL).insets(0, 12, 0, 12));
    public class ImageDirectionPositionPanel extends JPanel {  
    6168     * @return a new {@code ImageMetadataModificationPanel} in a GPX trace context
    6269     */
    6370    public static ImageDirectionPositionPanel forGpxTrace() {
    64         return new ImageDirectionPositionPanel(tr("Set image direction towards the next GPX waypoint"));
     71        return new ImageDirectionPositionPanel(tr("Set image direction towards the next GPX waypoint"), false);
    6572    }
    6673
    6774    /**
    public class ImageDirectionPositionPanel extends JPanel {  
    6976     * @return a new {@code ImageMetadataModificationPanel} in an image sequence context
    7077     */
    7178    public static ImageDirectionPositionPanel forImageSequence() {
    72         return new ImageDirectionPositionPanel(tr("Set image direction towards the next one"));
     79        return new ImageDirectionPositionPanel(tr("Set image direction towards the next one"), true);
    7380    }
    7481
    7582    protected void addSetting(String text, JComponent component) {
    public class ImageDirectionPositionPanel extends JPanel {  
    8693        return new GpxImageDirectionPositionSettings(
    8794                cChangeImageDirection.isSelected(),
    8895                (Integer) sOffsetDegrees.getValue(),
     96                cSetGpxTrackTag.isSelected(),
    8997                (Double) sX.getValue(),
    9098                (Double) sY.getValue(),
    9199                (Double) sZ.getValue());
    public class ImageDirectionPositionPanel extends JPanel {  
    109117     */
    110118    public void addItemListenerOnComponents(ItemListener listener) {
    111119        cChangeImageDirection.addItemListener(listener);
     120        cSetGpxTrackTag.addItemListener(listener);
    112121    }
    113122
    114123    /**
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java
    index 473e8e5752..c7eb8d3b77 100644
    a b public interface ImageMetadata {  
    128128    Double getExifImgDir();
    129129
    130130    /**
    131      * Get the last time the source was modified
     131     * Get the exif GPS track direction.
     132     * @return The GPS track direction
     133     * @since xxx
     134     */
     135    Double getExifGpsTrack();
     136
     137    /**
     138     * Get the exif Horizontal positioning error.
     139     * @return The image horizontal positioning error
     140     * @since xxx
     141     */
     142    Double getExifHPosErr();
     143
     144    /**
     145     * Get the GPS Differential mode.
     146     * @return The image gnss fix mode
     147     * @since xxx
     148     */
     149    Integer getGpsDiffMode();
     150   
     151    /**
     152     * Get the GPS 2d/3d mode.
     153     * @return The image gnss 2d/3d mode
     154     * @since xxx
     155     */
     156    Integer getGps2d3dMode();
     157
     158    /**
     159     * Get the exif GPS DOP value.
     160     * @return The image GPS DOP value
     161     * @since xxx
     162     */
     163    Double getExifGpsDop();
     164
     165    /**
     166     * Get the GPS datum value.
     167     * @return The image GPS datum value
     168     * @since xxx
     169     */
     170    String getExifGpsDatum();
     171
     172    /**
     173     * Get the exif GPS processing method.
     174     * @return The image GPS processing method
     175     * @since xxx
     176     */
     177    String getExifGpsProcMethod();
     178
     179    /**
     180     * Get the last time the source was modified.
    132181     * @return The last time the source was modified
    133182     */
    134183    Instant getLastModified();
    public interface ImageMetadata {  
    193242    void setGpsTime(Instant gpsTime);
    194243
    195244    /**
    196      * Set the exif coordinates
     245     * Sets the exif coordinates
    197246     * @param exifCoor The exif coordinates
    198247     */
    199248    void setExifCoor(ILatLon exifCoor);
    200249
    201250    /**
    202      * Set the exif direction
     251     * Sets the exif direction
    203252     * @param exifDir The direction
    204253     */
    205254    void setExifImgDir(Double exifDir);
    206255
     256    /**
     257     * Sets the exif GPS track direction.
     258     * @param exifGpsTrack The GPS track direction
     259     * @since xxx
     260     */
     261    void setExifGpsTrack(Double exifGpsTrack);
     262
     263    /**
     264     * Sets the exif horizontal positioning error.
     265     * @param exifHposErr the exif horizontal positionning error
     266     * @since xxx
     267     */
     268    void setExifHPosErr(Double exifHPosErr);
     269
     270    /**
     271     * Sets the exif GPS DOP value.
     272     * @param exifGpsDop the exif GPS DOP value
     273     * @since xxx
     274     */
     275    void setExifGpsDop(Double exifGpsDop);
     276
     277    /**
     278     * Sets the GPS Differential mode.
     279     * @param gpsDiffMode GPS Differential mode
     280     * @since xxx
     281     */
     282    void setGpsDiffMode(Integer gpsDiffMode);
     283
     284    /**
     285     * Sets the GPS 2d/3d mode.
     286     * @param gps2d3dMode GPS 2d/3d mode
     287     * @since xxx
     288     */
     289    void setGps2d3dMode(Integer gps2d3dMode);
     290
     291    /**
     292     * Sets the GPS datum value.
     293     * @param exifGpsDatum GPS datum
     294     * @since xxx
     295     */
     296    void setExifGpsDatum(String exifGpsDatum);
     297
     298    /**
     299     * Sets the GPS processing method.
     300     * @param exifGpsProcMethod GPS processing method
     301     * @since xxx
     302     */
     303    void setExifGpsProcMethod(String exifGpsProcMethod);
     304
    207305    /**
    208306     * Sets the IPTC caption.
    209307     * @param iptcCaption the IPTC caption
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java
    index abe9e1f12c..19f0390c12 100644
    a b public final class ImageUtils {  
    191191            Logging.debug(ex);
    192192        }
    193193
     194        try {
     195            ifNotNull(ExifReader.readGpsTrackDirection(dirGps), image::setExifGpsTrack);
     196        } catch (IndexOutOfBoundsException ex) {
     197            Logging.debug(ex);
     198        }
     199
     200        try {
     201            ifNotNull(ExifReader.readHpositioningError(dirGps), image::setExifHPosErr);
     202        } catch (IndexOutOfBoundsException ex) {
     203            Logging.debug(ex);
     204        }
     205       
     206        try {
     207            ifNotNull(ExifReader.readGpsDiffMode(dirGps), image::setGpsDiffMode);
     208        } catch (IndexOutOfBoundsException ex) {
     209            Logging.debug(ex);
     210        }
     211       
     212        try {
     213            ifNotNull(ExifReader.readGpsMeasureMode(dirGps), image::setGps2d3dMode);
     214        } catch (IndexOutOfBoundsException ex) {
     215            Logging.debug(ex);
     216        }
     217
     218        try {
     219            ifNotNull(ExifReader.readGpsDop(dirGps), image::setExifGpsDop);
     220        } catch (IndexOutOfBoundsException ex) {
     221            Logging.debug(ex);
     222        }
     223
     224        try {
     225            ifNotNull(ExifReader.readGpsDatum(dirGps), image::setExifGpsDatum);
     226        } catch (IndexOutOfBoundsException ex) {
     227            Logging.debug(ex);
     228        }
     229
     230        try {
     231            ifNotNull(ExifReader.readGpsProcessingMethod(dirGps), image::setExifGpsProcMethod);
     232        } catch (IndexOutOfBoundsException ex) {
     233            Logging.debug(ex);
     234        }
     235       
    194236        ifNotNull(dirGps.getGpsDate(), d -> image.setExifGpsTime(d.toInstant()));
    195237    }
    196238
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
    index d4b402296b..1ce375f9a4 100644
    a b public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    8888
    8989    private final ImageZoomAction imageZoomAction = new ImageZoomAction();
    9090    private final ImageCenterViewAction imageCenterViewAction = new ImageCenterViewAction();
     91    private final ImageExtendedInfoAction imageExtendedInfoAction = new ImageExtendedInfoAction();
    9192    private final ImageNextAction imageNextAction = new ImageNextAction();
    9293    private final ImageRemoveAction imageRemoveAction = new ImageRemoveAction();
    9394    private final ImageRemoveFromDiskAction imageRemoveFromDiskAction = new ImageRemoveFromDiskAction();
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    103104    private final ImageDisplay imgDisplay = new ImageDisplay(imageryFilterSettings);
    104105    private Future<?> imgLoadingFuture;
    105106    private boolean centerView;
     107    private boolean extendedImgInfo;
    106108
    107109    // Only one instance of that class is present at one time
    108110    private static volatile ImageViewerDialog dialog;
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    163165    private JButton btnOpenExternal;
    164166    private JButton btnDeleteFromDisk;
    165167    private JToggleButton tbCentre;
     168    private JToggleButton tbImgExtInfo;
    166169    /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */
    167170    private final HideableTabbedPane layers = new HideableTabbedPane();
    168171
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    234237        tbCentre.setSelected(centerView);
    235238        tbCentre.setPreferredSize(buttonDim);
    236239
     240        extendedImgInfo = Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false);
     241        tbImgExtInfo = new JToggleButton(imageExtendedInfoAction);
     242        tbImgExtInfo.setSelected(extendedImgInfo);
     243        tbImgExtInfo.setPreferredSize(buttonDim);
     244
    237245        JButton btnZoomBestFit = new JButton(imageZoomAction);
    238246        btnZoomBestFit.setPreferredSize(buttonDim);
    239247
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    242250
    243251        JPanel buttons = new JPanel();
    244252        addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast);
    245         addButtonGroup(buttons, this.tbCentre, btnZoomBestFit);
     253        addButtonGroup(buttons, this.tbCentre, btnZoomBestFit, this.tbImgExtInfo);
    246254        addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk);
    247255        addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal);
    248256        addButtonGroup(buttons, createButton(visibilityAction, buttonDim));
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    583591        }
    584592    }
    585593
     594    private class ImageExtendedInfoAction extends JosmAction {
     595        ImageExtendedInfoAction() {
     596            super(null, new ImageProvider("info"), tr("Display image extended metadata"), Shortcut.registerShortcut(
     597                    "geoimage:extendedinfos", tr(GEOIMAGE_FILLER, tr("Toggle Osd extended informations")),
     598                    KeyEvent.CHAR_UNDEFINED, Shortcut.NONE),
     599            false, null, false);
     600        }
     601
     602        @Override
     603        public void actionPerformed(ActionEvent e) {
     604            final JToggleButton button = (JToggleButton) e.getSource();
     605            extendedImgInfo = button.isEnabled() && button.isSelected();
     606            Config.getPref().putBoolean("geoimage.viewer.extendedinfo", extendedImgInfo);
     607            refresh(false);
     608        }
     609    }
     610   
    586611    private class ImageRemoveAction extends JosmAction {
    587612        ImageRemoveAction() {
    588613            super(null, new ImageProvider(DIALOG_FOLDER, "delete"), tr("Remove photo from layer"), Shortcut.registerShortcut(
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    9991024        if (entry.getSpeed() != null) {
    10001025            osd.append(tr("\nSpeed: {0} km/h", Math.round(entry.getSpeed())));
    10011026        }
    1002         if (entry.getExifImgDir() != null) {
    1003             osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir())));
    1004         }
    1005 
    10061027        DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM)
    1007                 // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp,
    1008                 // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata)
    1009                 .withZone(ZoneOffset.UTC);
    1010 
     1028                    // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp,
     1029                    // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata)
     1030                    .withZone(ZoneOffset.UTC);
    10111031        if (entry.hasExifTime()) {
    1012             osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant())));
     1032            if (Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false)) {
     1033                osd.append(tr("\nEXIF DTO time: {0}", dtf.format(entry.getExifInstant())));
     1034            } else {
     1035                osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant())));       
     1036            }
    10131037        }
    1014         if (entry.hasGpsTime()) {
    1015             osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsInstant())));
     1038
     1039        if (Config.getPref().getBoolean("geoimage.viewer.extendedinfo", false)) {
     1040            if (entry.getExifGpsInstant() != null) {
     1041                osd.append(tr("\nEXIF GPS time: {0}", dtf.format(entry.getExifGpsInstant())));
     1042            }
     1043            if (entry.hasGpsTime()) {
     1044                osd.append(tr("\nCorr GPS time: {0}", dtf.format(entry.getGpsInstant())));
     1045            }           
     1046            if (entry.getExifImgDir() != null) {
     1047                osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir())));
     1048            }
     1049            if (entry.getExifGpsTrack() != null) {
     1050                osd.append(tr("\nGPS direction: {0}\u00b0", Math.round(entry.getExifGpsTrack())));
     1051            }
     1052            if (entry.getExifHPosErr() != null) {
     1053                osd.append(tr("\nHpos errror: {0}m", entry.getExifHPosErr()));
     1054            }
     1055            if (entry.getGps2d3dMode() != null) {
     1056                osd.append(tr("\n2d/3d mode: {0}d", entry.getGps2d3dMode()));
     1057            }
     1058            if (entry.getGpsDiffMode() != null) {
     1059                osd.append(tr("\nDifferential: {0}", entry.getGpsDiffMode()));
     1060            }
     1061            if (entry.getExifGpsDop() != null) {
     1062                osd.append(tr("\nDOP: {0}", entry.getExifGpsDop()));
     1063            }
     1064            if (entry.getExifGpsDatum() != null) {
     1065                osd.append(tr("\nDatum: {0}", entry.getExifGpsDatum().toString()));
     1066            }
     1067            if (entry.getExifGpsProcMethod() != null) {
     1068                osd.append(tr("\nProc. method: {0}", entry.getExifGpsProcMethod().toString()));
     1069            }
    10161070        }
    10171071        Optional.ofNullable(entry.getIptcCaption()).map(s -> tr("\nCaption: {0}", s)).ifPresent(osd::append);
    10181072        Optional.ofNullable(entry.getIptcHeadline()).map(s -> tr("\nHeadline: {0}", s)).ifPresent(osd::append);
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    11451199        }
    11461200    }
    11471201
     1202    /**
     1203     * Reload the image or reload only the image info. Call this if want to update the OSD.
     1204     * @param imageChanged reload the image if true. Reload only the OSD if false.
     1205     * @since xxx
     1206     */
     1207    public void refresh(boolean imageChanged) {
     1208        if (SwingUtilities.isEventDispatchThread()) {
     1209            this.updateButtonsNonNullEntry(currentEntry, imageChanged);
     1210        } else {
     1211            GuiHelper.runInEDT(this::refresh);
     1212        }
     1213    }
     1214   
    11481215    private void registerOnLayer(Layer layer) {
    11491216        if (layer instanceof IGeoImageLayer) {
    11501217            layer.addPropertyChangeListener(l -> {
  • src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java
    index ab87cd8c05..e2b5cd3c4c 100644
    a b public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    3737    private ILatLon pos;
    3838    private Integer exifOrientation;
    3939    private Double elevation;
     40    private Integer gpsDiffMode;
     41    private Integer gps2d3dMode;
     42    private Double exifHPosErr;
     43    private Double exifGpsDop;
     44    private String exifGpsDatum;
     45    private String exifGpsProcMethod;
    4046    private Double speed;
    4147    private Double exifImgDir;
     48    private Double exifGpsTrack;
    4249    private ILatLon exifCoor;
    4350    private Instant exifTime;
    4451    private Instant exifGpsTime;
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    117124        this.speed = speed;
    118125    }
    119126
     127    /**
     128     * @since xxx
     129     */
     130    @Override
     131    public void setGpsDiffMode(Integer gpsDiffMode) {
     132        this.gpsDiffMode = gpsDiffMode;
     133    }
     134
     135    /**
     136     * @since xxx
     137     */
     138    @Override
     139    public void setGps2d3dMode(Integer gps2d3dMode) {
     140        this.gps2d3dMode = gps2d3dMode;
     141    }
     142
     143    /**
     144     * @since xxx
     145     */
     146    @Override
     147    public void setExifGpsDatum(String exifGpsDatum) {
     148        this.exifGpsDatum = exifGpsDatum;
     149    }
     150
     151    /**
     152     * @since xxx
     153     */
     154    @Override
     155    public void setExifGpsProcMethod(String exifGpsProcMethod) {
     156        this.exifGpsProcMethod = exifGpsProcMethod;
     157    }
     158
    120159    @Override
    121160    public void setElevation(Double elevation) {
    122161        this.elevation = elevation;
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    152191        this.exifImgDir = exifDir;
    153192    }
    154193
     194    /**
     195     * @since xxx
     196     */
     197    @Override
     198    public void setExifGpsTrack(Double exifGpsTrack) {
     199        this.exifGpsTrack = exifGpsTrack;
     200    }
     201
     202    /**
     203     * @since xxx
     204     */
     205    @Override
     206    public void setExifHPosErr(Double exifHPosError) {
     207        this.exifHPosErr = exifHPosError;
     208    }
     209
     210    /**
     211     * @since xxx
     212     */
     213    @Override
     214    public void setExifGpsDop(Double exifGpsDop) {
     215        this.exifGpsDop = exifGpsDop;
     216    }
     217
    155218    @Override
    156219    public void setIptcCaption(String iptcCaption) {
    157220        this.iptcCaption = iptcCaption;
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    212275        return this.elevation;
    213276    }
    214277
     278    /**
     279     * @since xxx
     280     */
     281    @Override
     282    public Integer getGpsDiffMode() {
     283        return this.gpsDiffMode;
     284    }
     285   
     286    /**
     287     * @since xxx
     288     */
     289    @Override
     290    public Integer getGps2d3dMode() {
     291        return this.gps2d3dMode;
     292    }
     293
     294    /**
     295     * @since xxx
     296     */
     297    @Override
     298    public String getExifGpsDatum() {
     299        return this.exifGpsDatum;
     300    }
     301
     302    /**
     303     * @since xxx
     304     */
     305    @Override
     306    public String getExifGpsProcMethod() {
     307        return this.exifGpsProcMethod;
     308    }
     309
     310    /**
     311     * @since xxx
     312     */
    215313    @Override
    216314    public Double getExifImgDir() {
    217315        return this.exifImgDir;
    218316    }
    219317
     318    /**
     319     * @since xxx
     320     */
     321    @Override
     322    public Double getExifGpsTrack() {
     323        return this.exifGpsTrack;
     324    }
     325
     326    /**
     327     * @since xxx
     328     */
     329    @Override
     330    public Double getExifHPosErr() {
     331        return this.exifHPosErr;
     332    }
     333
     334    /**
     335     * @since xxx
     336     */
     337    @Override
     338    public Double getExifGpsDop() {
     339        return this.exifGpsDop;
     340    }
     341
    220342    @Override
    221343    public Instant getLastModified() {
    222344        if ("file".equals(this.getImageURI().getScheme())) {
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    335457    public int hashCode() {
    336458        return Objects.hash(this.uri, this.pos,
    337459                this.exifOrientation, this.elevation, this.speed, this.exifImgDir,
    338                 this.exifCoor, this.exifTime, this.exifGpsTime, this.gpsTime,
    339                 this.iptcObjectName, this.iptcCaption, this.iptcHeadline, this.iptcKeywords,
    340                 this.projection, this.title);
     460                this.exifGpsTrack, this.exifCoor, this.exifTime, this.exifGpsTime,
     461                this.gpsTime, this.exifHPosErr, this.exifGpsDop, this.gpsDiffMode,
     462                this.gps2d3dMode, this.exifGpsDatum, this.exifGpsProcMethod,
     463                this.iptcObjectName, this.iptcCaption, this.iptcHeadline,
     464                this.iptcKeywords, this.projection, this.title);
    341465    }
    342466
    343467    @Override
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    352476                    && Objects.equals(this.exifCoor, other.exifCoor)
    353477                    && Objects.equals(this.exifGpsTime, other.exifGpsTime)
    354478                    && Objects.equals(this.exifImgDir, other.exifImgDir)
     479                    && Objects.equals(this.exifGpsTrack, other.exifGpsTrack)
     480                    && Objects.equals(this.exifHPosErr, other.exifHPosErr)
     481                    && Objects.equals(this.exifGpsDop, other.exifGpsDop)
    355482                    && Objects.equals(this.exifOrientation, other.exifOrientation)
    356483                    && Objects.equals(this.exifTime, other.exifTime)
    357484                    && Objects.equals(this.gpsTime, other.gpsTime)
    public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata {  
    362489                    && Objects.equals(this.pos, other.pos)
    363490                    && Objects.equals(this.projection, other.projection)
    364491                    && Objects.equals(this.speed, other.speed)
     492                    && Objects.equals(this.gpsDiffMode, other.gpsDiffMode)
     493                    && Objects.equals(this.gps2d3dMode, other.gps2d3dMode)
     494                    && Objects.equals(this.exifGpsDatum, other.exifGpsDatum)
     495                    && Objects.equals(this.exifGpsProcMethod, other.exifGpsProcMethod)
    365496                    && Objects.equals(this.title, other.title);
    366497        }
    367498        return false;
  • src/org/openstreetmap/josm/io/nmea/NmeaParser.java

    diff --git a/src/org/openstreetmap/josm/io/nmea/NmeaParser.java b/src/org/openstreetmap/josm/io/nmea/NmeaParser.java
    index 3c6b82e12c..9631df402e 100644
    a b public class NmeaParser {  
    466466                    accu = e[VTG.COURSE.position];
    467467                    if (!accu.isEmpty() && currentwp != null) {
    468468                        Double.parseDouble(accu);
    469                         currentwp.put("course", accu);
     469                        currentwp.put(GpxConstants.PT_COURSE, accu);
    470470                    }
    471471                }
    472472                // SPEED
    public class NmeaParser {  
    542542                accu = e[RMC.COURSE.position];
    543543                if (!accu.isEmpty() && !currentwp.attr.containsKey("course")) {
    544544                    Double.parseDouble(accu);
    545                     currentwp.put("course", accu);
     545                    currentwp.put(GpxConstants.PT_COURSE, accu);
    546546                }
    547547
    548548                // TODO fix?
  • src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java

    diff --git a/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java b/src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java
    index fa24a4d66a..47ff8ebb8d 100644
    a b import org.w3c.dom.Element;  
    2222/**
    2323 * Session exporter for {@link GeoImageLayer}.
    2424 * @since 5505
     25 * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod exporter added
    2526 */
    2627public class GeoImageSessionExporter extends AbstractSessionExporter<GeoImageLayer> {
    2728
    public class GeoImageSessionExporter extends AbstractSessionExporter<GeoImageLay  
    105106            if (entry.getExifImgDir() != null) {
    106107                addAttr("exif-image-direction", entry.getExifImgDir().toString(), imgElem, support);
    107108            }
     109            if (entry.getExifGpsTrack() != null) {
     110                addAttr("exif-gps-track", entry.getExifGpsTrack().toString(), imgElem, support);
     111            }
     112            if (entry.getExifHPosErr() != null) {
     113                addAttr("exif-gps-hposerr", entry.getExifHPosErr().toString(), imgElem, support);
     114            }
     115            if (entry.getGpsDiffMode() != null) {
     116                addAttr("exif-gps-diffmode", entry.getGpsDiffMode().toString(), imgElem, support);
     117            }
     118            if (entry.getGps2d3dMode() != null) {
     119                addAttr("exif-gps-2d3dmode", entry.getGps2d3dMode().toString(), imgElem, support);
     120            }
     121            if (entry.getExifGpsDop() != null) {
     122                addAttr("exif-gps-dop", entry.getExifGpsDop().toString(), imgElem, support);
     123            }
     124            if (entry.getExifGpsDatum() != null) {
     125                addAttr("exif-gps-datum", entry.getExifGpsDatum().toString(), imgElem, support);
     126            }
     127            if (entry.getExifGpsProcMethod() != null) {
     128                addAttr("exif-gps-procmethod", entry.getExifGpsProcMethod().toString(), imgElem, support);
     129            }
    108130            if (entry.hasNewGpsData()) {
    109131                addAttr("is-new-gps-data", Boolean.toString(entry.hasNewGpsData()), imgElem, support);
    110132            }
  • src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java

    diff --git a/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java b/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
    index 80f99b68ad..0914fe4c97 100644
    a b import org.w3c.dom.NodeList;  
    2525/**
    2626 * Session importer for {@link GeoImageLayer}.
    2727 * @since 5505
     28 * @since xxx exifGpsTrack, exifHPosErr, gpsDiffMode, gps2d3dMode, exifGpsDop, exifGpsDatum, exifGpsProcMethod importer added
    2829 */
    2930public class GeoImageSessionImporter implements SessionLayerImporter {
    3031
    public class GeoImageSessionImporter implements SessionLayerImporter {  
    108109            case "exif-image-direction":
    109110                entry.setExifImgDir(Double.valueOf(attrElem.getTextContent()));
    110111                break;
     112            case "exif-gps-track":
     113                entry.setExifGpsTrack(Double.valueOf(attrElem.getTextContent()));
     114                break;
     115            case "exif-gps-hposerr":
     116                entry.setExifHPosErr(Double.valueOf(attrElem.getTextContent()));
     117                break;
     118            case "exif-gps-diffmode":
     119                entry.setGpsDiffMode(Integer.valueOf(attrElem.getTextContent()));
     120                break;
     121            case "exif-gps-2d3dmode":
     122                entry.setGps2d3dMode(Integer.valueOf(attrElem.getTextContent()));
     123                break;
     124            case "exif-gps-dop":
     125                entry.setExifGpsDop(Double.valueOf(attrElem.getTextContent()));
     126                break;
     127            case "exif-gps-datum":
     128                entry.setExifGpsDatum(attrElem.getTextContent());
     129                break;
     130            case "exif-gps-procmethod":
     131                entry.setExifGpsProcMethod(attrElem.getTextContent());
     132                break;
    111133            case "is-new-gps-data":
    112134                if (Boolean.parseBoolean(attrElem.getTextContent())) {
    113135                    entry.flagNewGpsData();
  • src/org/openstreetmap/josm/tools/ExifReader.java

    diff --git a/src/org/openstreetmap/josm/tools/ExifReader.java b/src/org/openstreetmap/josm/tools/ExifReader.java
    index ad9e361c4f..404481b3a3 100644
    a b public final class ExifReader {  
    123123        return null;
    124124    }
    125125
     126    /**
     127     * Returns the GPS date/time from the given JPEG file.
     128     * @param filename The JPEG file to read
     129     * @return The GPS date/time read in the EXIF section, or {@code null} if not found
     130     * @since xxx
     131     */
     132    public static Instant readGpsInstant(File filename) {
     133        try {
     134            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     135            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     136            return readGpsInstant(dirGps);
     137        } catch (JpegProcessingException | IOException e) {
     138            Logging.error(e);
     139        }
     140        return null;
     141    }
     142 
     143    /**
     144     * Returns the GPS date/time from the given JPEG file.
     145     * @param dirGps The EXIF GPS directory
     146     * @return The GPS date/time read in the EXIF section, or {@code null} if not found
     147     * @since xxx
     148     */
     149    public static Instant readGpsInstant(GpsDirectory dirGps) {
     150        if (dirGps != null) {
     151            try {
     152                Instant dateTimeStamp = dirGps.getGpsDate().toInstant();
     153                return dateTimeStamp;
     154            } catch (UncheckedParseException | DateTimeException e) {
     155                Logging.error(e);
     156            }
     157        }
     158        return null;
     159    }
     160 
    126161    /**
    127162     * Returns the image orientation of the given JPEG file.
    128163     * @param filename The JPEG file to read
    public final class ExifReader {  
    218253        return null;
    219254    }
    220255
     256    /**
     257     * Returns the GPS track direction of the given JPEG file.
     258     * @param filename The JPEG file to read
     259     * @return The GPS track direction of the image when it was captures (in degrees between 0.0 and 359.99),
     260     * or {@code null} if not found
     261     * @since xxx
     262     */
     263    public static Double readGpsTrackDirection(File filename) {
     264        try {
     265            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     266            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     267            return readGpsTrackDirection(dirGps);
     268        } catch (JpegProcessingException | IOException e) {
     269            Logging.error(e);
     270        }
     271        return null;
     272    }
     273   
     274    /**
     275     * Returns the GPS track direction of the given EXIF GPS directory.
     276     * @param dirGps The EXIF GPS directory
     277     * @return The GPS track direction of the image when it was captured (in degrees between 0.0 and 359.99),
     278     * or {@code null} if missing or if {@code dirGps} is null
     279     * @since xxx
     280     */
     281    public static Double readGpsTrackDirection(GpsDirectory dirGps) {
     282        if (dirGps != null) {
     283            Rational trackDirection = dirGps.getRational(GpsDirectory.TAG_TRACK);
     284            if (trackDirection != null) {
     285                return trackDirection.doubleValue();
     286            }
     287        }
     288        return null;
     289    }
     290
    221291    private static double readAxis(GpsDirectory dirGps, int gpsTag, int gpsTagRef, char cRef) throws MetadataException {
    222292        double value;
    223293        Rational[] components = dirGps.getRationalArray(gpsTag);
    public final class ExifReader {  
    323393        return null;
    324394    }
    325395
     396    /**
     397     * Returns the GPS horizontal positionning error of the given JPEG file.
     398     * @param filename The JPEG file to read
     399     * @return The GPS horizontal positionning error of the camera when the image was captured (in m),
     400     *         or {@code null} if not found
     401     * @since xxx
     402     */
     403    public static Double readHpositioningError(File filename) {
     404        try {
     405            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     406            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     407            return readHpositioningError(dirGps);
     408        } catch (JpegProcessingException | IOException e) {
     409            Logging.error(e);
     410        }
     411        return null;
     412    }
     413
     414    /**
     415     * Returns the GPS horizontal positionning error of the given EXIF GPS directory.
     416     * @param dirGps The EXIF GPS directory
     417     * @return The GPS horizontal positionning error of the camera when the image was captured (in m),
     418     *         or {@code null} if missing or if {@code dirGps} is null
     419     * @since xxx
     420     */
     421    public static Double readHpositioningError(GpsDirectory dirGps) {
     422        if (dirGps != null) {
     423            Double hposerr = dirGps.getDoubleObject(GpsDirectory.TAG_H_POSITIONING_ERROR);
     424            if (hposerr != null) {
     425                return hposerr.doubleValue();
     426            }
     427        }
     428        return null;
     429    }
     430
     431    /**
     432     * Returns the GPS differential mode of the given JPEG file.
     433     * @param filename The JPEG file to read
     434     * @return The GPS differential mode of the camera when the image was captured,
     435     * <ul>
     436     *  <li>0 : no differential correction</li>
     437     *  <li>1 : differential correction</li>
     438     *  <li>or {@code null} if not found</li>
     439     * </ul>
     440     * @since xxx
     441     */
     442    public static Integer readGpsDiffMode(File filename) {
     443        try {
     444            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     445            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     446            return readGpsDiffMode(dirGps);
     447        } catch (JpegProcessingException | IOException e) {
     448            Logging.error(e);
     449        }
     450        return null;
     451    }
     452
     453    /**
     454     * Returns the GPS differential mode of the given EXIF GPS directory.
     455     * @param dirGps The EXIF GPS directory
     456     * @return The GPS differential mode of the camera when the image was captured,
     457     * <ul>
     458     *  <li>0 : no differential correction</li>
     459     *  <li>1 : differential correction</li>
     460     *  <li>or {@code null} if missing or if {@code dirGps} is null</li>
     461     * </ul>
     462     * @since xxx
     463     */   
     464    public static Integer readGpsDiffMode(GpsDirectory dirGps) {
     465        if (dirGps != null) {
     466            Integer gpsDiffMode = dirGps.getInteger(GpsDirectory.TAG_DIFFERENTIAL);
     467            if (gpsDiffMode != null) {
     468                return gpsDiffMode.intValue();
     469            }
     470        }
     471        return null;
     472    }
     473
     474    /**
     475     * Returns the GPS 2d/3d mode of the given JPEG file.
     476     * @param filename The JPEG file to read
     477     * @return The GPS 2d/3d mode of the camera when the image was captured,
     478     * <ul>
     479     *  <li>2 : 2d mode</li>
     480     *  <li>2 : 3d mode</li>
     481     *  <li>or {@code null} if not found</li>
     482     * </ul>
     483     * @since xxx
     484     */
     485    public static Integer readGpsMeasureMode(File filename) {
     486        try {
     487            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     488            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     489            return readGpsMeasureMode(dirGps);
     490        } catch (JpegProcessingException | IOException e) {
     491            Logging.error(e);
     492        }
     493        return null;
     494    }
     495
     496    /**
     497     * Returns the GPS 2d/3d mode of the given EXIF GPS directory.
     498     * @param dirGps The EXIF GPS directory
     499     * @return The 2d/3d mode of the camera when the image was captured,
     500     * <ul>
     501     *  <li>2 : 2d mode</li>
     502     *  <li>3 : 3d mode</li>
     503     *  <li>or {@code null} if missing or if {@code dirGps} is null</li>
     504     * </ul>
     505     * @since xxx
     506     */   
     507    public static Integer readGpsMeasureMode(GpsDirectory dirGps) {
     508        if (dirGps != null) {
     509            Integer gps2d3dMode = dirGps.getInteger(GpsDirectory.TAG_MEASURE_MODE);
     510            if (gps2d3dMode != null) {
     511                return gps2d3dMode.intValue();
     512            }
     513        }
     514        return null;
     515    }
     516
     517    /**
     518     * Returns the GPS DOP value of the given JPEG file.
     519     * @param filename The JPEG file to read
     520     * @return The GPS DOP value of the camera when the image was captured,
     521     *         or {@code null} if not found
     522     * @since xxx
     523     */
     524    public static Double readGpsDop(File filename) {
     525        try {
     526            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     527            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     528            return readGpsDop(dirGps);
     529        } catch (JpegProcessingException | IOException e) {
     530            Logging.error(e);
     531        }
     532        return null;
     533    }
     534
     535    /**
     536     * Returns the GPS DOP value of the given EXIF GPS directory.
     537     * @param dirGps The EXIF GPS directory
     538     * @return The GPS DOP value of the camera when the image was captured,
     539     *         or {@code null} if missing or if {@code dirGps} is null
     540     * @since xxx
     541     */
     542    public static Double readGpsDop(GpsDirectory dirGps) {
     543        if (dirGps != null) {
     544            Double gpsDop = dirGps.getDoubleObject(GpsDirectory.TAG_DOP);
     545            if (gpsDop != null) {
     546                return gpsDop.doubleValue();
     547            }
     548        }
     549        return null;
     550    }
     551
     552    /**
     553     * Returns the GPS datum value of the given JPEG file.
     554     * @param filename The JPEG file to read
     555     * @return The GPS datum value of the camera when the image was captured,
     556     *         or {@code null} if not found
     557     * @since xxx
     558     */
     559    public static String readGpsDatum(File filename) {
     560        try {
     561            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     562            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     563            return readGpsDatum(dirGps);
     564        } catch (JpegProcessingException | IOException e) {
     565            Logging.error(e);
     566        }
     567        return null;
     568    }
     569
     570    /**
     571     * Returns the GPS datum value of the given EXIF GPS directory.
     572     * @param dirGps The EXIF GPS directory
     573     * @return The GPS datum value of the camera when the image was captured,
     574     *         or {@code null} if missing or if {@code dirGps} is null
     575     * @since xxx
     576     */
     577    public static String readGpsDatum(GpsDirectory dirGps) {
     578        if (dirGps != null) {
     579            String gpsDatum = dirGps.getString(GpsDirectory.TAG_MAP_DATUM);
     580            if (gpsDatum != null) {
     581                return gpsDatum.toString();
     582            }
     583        }
     584        return null;
     585    }
     586
     587    /**
     588     * Return the GPS processing method of the given JPEG file.
     589     * @param filename The JPEG file to read
     590     * @return The GPS processing method. Possible values from the EXIF specs are:
     591     * <ul>
     592     * <li>GPS</li>
     593     * <li>QZSS</li>
     594     * <li>GALILEO</li>
     595     * <li>GLONASS</li>
     596     * <li>BEIDOU</li>
     597     * <li>NAVIC</li>
     598     * <li>CELLID</li>
     599     * <li>WLAN</li>
     600     * <li>MANUAL</li>
     601     * </ul>
     602     * Other values, and combined space separated values are possible too.
     603     * or {@code null} if missing
     604     * @since xxx
     605     */
     606    public static String readGpsProcessingMethod(File filename) {
     607        try {
     608            final Metadata metadata = JpegMetadataReader.readMetadata(filename);
     609            final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
     610            return readGpsProcessingMethod(dirGps);
     611        } catch (JpegProcessingException | IOException e) {
     612            Logging.error(e);
     613        }
     614        return null;
     615    }
     616
     617    /**
     618     * Return the GPS processing method of the given EXIF GPS directory.
     619     * @param dirGps The EXIF GPS directory
     620     * @return The GPS processing method. Possible values from the EXIF specs are:
     621     * <ul>
     622     * <li>GPS</li>
     623     * <li>QZSS</li>
     624     * <li>GALILEO</li>
     625     * <li>GLONASS</li>
     626     * <li>BEIDOU</li>
     627     * <li>NAVIC</li>
     628     * <li>CELLID</li>
     629     * <li>WLAN</li>
     630     * <li>MANUAL</li>
     631     * </ul>
     632     * Other values, and combined space separated values are possible too.
     633     * or {@code null} if missing or if {@code dirGps} is null
     634     * @since xxx
     635     */
     636    public static String readGpsProcessingMethod(GpsDirectory dirGps) {
     637        if (dirGps != null) {
     638            String gpsProcessingMethod = dirGps.getDescription(GpsDirectory.TAG_PROCESSING_METHOD);
     639            if (gpsProcessingMethod != null) {
     640                return gpsProcessingMethod.toString();
     641            }
     642        }
     643        return null;
     644    }
     645
    326646    /**
    327647     * Returns the caption of the given IPTC directory.
    328648     * @param dirIptc The IPTC directory
  • test/unit/org/openstreetmap/josm/data/ImageDataTest.java

    diff --git a/test/unit/org/openstreetmap/josm/data/ImageDataTest.java b/test/unit/org/openstreetmap/josm/data/ImageDataTest.java
    index 22dd7eff9f..3618721d8e 100644
    a b class ImageDataTest {  
    444444        data.updateImageDirection(list.get(0), 0);
    445445    }
    446446
     447    @Test
     448    void testUpdateHPosErr() {
     449        List<ImageEntry> list = getOneImage();
     450        ImageData data = new ImageData(list);
     451
     452        new Expectations(list.get(0)) {{
     453            list.get(0).setExifHPosErr(1.23);
     454            list.get(0).flagNewGpsData();
     455        }};
     456        data.updateImageHPosErr(list.get(0), 1.23);
     457    }
     458
     459    @Test
     460    void testUpdateGpsDatum() {
     461        List<ImageEntry> list = getOneImage();
     462        ImageData data = new ImageData(list);
     463
     464        new Expectations(list.get(0)) {{
     465            list.get(0).setExifGpsDatum("WGS-84");
     466            list.get(0).flagNewGpsData();
     467        }};
     468        data.updateImageExifGpsDatum(list.get(0), "WGS-84");
     469    }
     470
     471    @Test
     472    void testUpdateGpsTrack() {
     473        List<ImageEntry> list = getOneImage();
     474        ImageData data = new ImageData(list);
     475
     476        new Expectations(list.get(0)) {{
     477            list.get(0).setExifGpsTrack(180.5);
     478            list.get(0).flagNewGpsData();
     479        }};
     480        data.updateImageGpsTrack(list.get(0), 180.5);
     481    }
     482
     483    @Test
     484    void testUpdateGpsDop() {
     485        List<ImageEntry> list = getOneImage();
     486        ImageData data = new ImageData(list);
     487
     488        new Expectations(list.get(0)) {{
     489            list.get(0).setExifGpsDop(1.9);
     490            list.get(0).flagNewGpsData();
     491        }};
     492        data.updateImageExifGpsDop(list.get(0), 1.9);
     493    }
     494
     495    @Test
     496    void testUpdateGpsProcMethod() {
     497        List<ImageEntry> list = getOneImage();
     498        ImageData data = new ImageData(list);
     499
     500        new Expectations(list.get(0)) {{
     501            list.get(0).setExifGpsProcMethod("GNSS RTK CORRELATION");
     502            list.get(0).flagNewGpsData();
     503        }};
     504        data.updateImageExifGpsProcMethod(list.get(0), "GNSS RTK CORRELATION");
     505    }
     506
     507    @Test
     508    void testUpdateGpsDifferentialMode() {
     509        List<ImageEntry> list = getOneImage();
     510        ImageData data = new ImageData(list);
     511
     512        new Expectations(list.get(0)) {{
     513            list.get(0).setGpsDiffMode(1);
     514            list.get(0).flagNewGpsData();
     515        }};
     516        data.updateImageGpsDiffMode(list.get(0), 1);
     517    }
     518
     519    @Test
     520    void testUpdateGps2d3dMode() {
     521        List<ImageEntry> list = getOneImage();
     522        ImageData data = new ImageData(list);
     523
     524        new Expectations(list.get(0)) {{
     525            list.get(0).setGps2d3dMode(3);
     526            list.get(0).flagNewGpsData();
     527        }};
     528        data.updateImageGps2d3dMode(list.get(0), 3);
     529    }
     530
    447531    @Test
    448532    void testTriggerListenerOnUpdate() {
    449533        List<ImageEntry> list = getOneImage();
  • test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java

    diff --git a/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java b/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java
    index ad5029fba3..74b9316a31 100644
    a b class GpxImageCorrelationTest {  
    350350        wp.put(GpxConstants.PT_ELE, "150.0");
    351351        assertEquals(Double.valueOf(150.0d), GpxImageCorrelation.getElevation(wp));
    352352    }
     353
     354    /**
     355     * Unit test of {@link GpxImageCorrelation#getHPosErr}
     356     */
     357    @Test
     358    void testGetHorizontalPosError() {
     359        assertNull(GpxImageCorrelation.getHPosErr(null));
     360        WayPoint wp = new WayPoint(LatLon.ZERO);
     361        assertNull(GpxImageCorrelation.getHPosErr(wp));
     362        wp.put(GpxConstants.PT_STD_HDEV, 1.5f);
     363        assertEquals(1.5f, GpxImageCorrelation.getHPosErr(wp));
     364    }
     365
     366    /**
     367     * Unit test of {@link GpxImageCorrelation#getGpsDop}
     368     */
     369    @Test
     370    void testGetGpsDop() {
     371        assertNull(GpxImageCorrelation.getGpsDop(null));
     372        WayPoint wp = new WayPoint(LatLon.ZERO);
     373        assertNull(GpxImageCorrelation.getGpsDop(wp));
     374        wp.put(GpxConstants.PT_HDOP, 2.1f);
     375        assertEquals(2.1f, GpxImageCorrelation.getGpsDop(wp));
     376        wp.put(GpxConstants.PT_PDOP, 0.9f);
     377        assertEquals(0.9f, GpxImageCorrelation.getGpsDop(wp));
     378        wp.put(GpxConstants.PT_PDOP, 1.2d);
     379        assertEquals(1.2d, GpxImageCorrelation.getGpsDop(wp));
     380    }
     381
     382    /**
     383     * Unit test of {@link GpxImageCorrelation#getGpsTrack}
     384     */
     385    @Test
     386    void testGetGpsTrack() {
     387        assertNull(GpxImageCorrelation.getHPosErr(null));
     388        WayPoint wp = new WayPoint(LatLon.ZERO);
     389        assertNull(GpxImageCorrelation.getGpsTrack(wp));
     390        wp.put(GpxConstants.PT_COURSE, "");
     391        assertNull(GpxImageCorrelation.getGpsTrack(wp));
     392        wp.put(GpxConstants.PT_COURSE, "not a number");
     393        assertNull(GpxImageCorrelation.getGpsTrack(wp));
     394        wp.put(GpxConstants.PT_COURSE, "167.0");
     395        assertEquals(Double.valueOf(167.0d), GpxImageCorrelation.getGpsTrack(wp), 0.01d);
     396    }
     397
     398    /**
     399     * Unit test of {@link GpxImageCorrelation#getGpsProcMethod}
     400     */
     401    @Test
     402    void testGetGpsProcMethod() {
     403        final List<String> positionningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk");
     404        assertNull(GpxImageCorrelation.getGpsProcMethod(null, null, positionningModes));
     405        assertNull(GpxImageCorrelation.getGpsProcMethod("", "", positionningModes));
     406        assertNull(GpxImageCorrelation.getGpsProcMethod("3d", null, positionningModes));
     407        assertNull(GpxImageCorrelation.getGpsProcMethod("", "dgps", positionningModes));
     408        assertEquals("MANUAL CORRELATION", GpxImageCorrelation.getGpsProcMethod("manual", "rtk", positionningModes));
     409        assertEquals("ESTIMATED CORRELATION", GpxImageCorrelation.getGpsProcMethod("estimated", "2d", positionningModes));
     410        assertEquals("GNSS DGPS CORRELATION", GpxImageCorrelation.getGpsProcMethod("rtk", "dgps", positionningModes));
     411        assertEquals("GNSS RTK_FLOAT CORRELATION", GpxImageCorrelation.getGpsProcMethod("float rtk", "rtk", positionningModes));
     412        assertEquals("GNSS RTK_FIX CORRELATION", GpxImageCorrelation.getGpsProcMethod("rtk", "rtk", positionningModes));
     413    }
     414
     415    /**
     416     * Unit test of {@link GpxImageCorrelation#getGps2d3dMode}
     417     */
     418    @Test
     419    void testGetGps2d3dMode() {
     420        final List<String> positionningModes = Arrays.asList("none", "manual", "estimated", "2d", "3d", "dgps", "float rtk", "rtk");
     421        assertNull(GpxImageCorrelation.getGps2d3dMode(null, null, positionningModes));
     422        assertNull(GpxImageCorrelation.getGps2d3dMode("", "", positionningModes));
     423        assertNull(GpxImageCorrelation.getGps2d3dMode("3d", null, positionningModes));
     424        assertNull(GpxImageCorrelation.getGps2d3dMode("", "dgps", positionningModes));
     425        assertNull(GpxImageCorrelation.getGps2d3dMode("estimated", "rtk", positionningModes));
     426        assertEquals(2, GpxImageCorrelation.getGps2d3dMode("2d", "2d", positionningModes));
     427        assertEquals(2, GpxImageCorrelation.getGps2d3dMode("2d", "3d", positionningModes));
     428        assertEquals(3, GpxImageCorrelation.getGps2d3dMode("3d", "dgps", positionningModes));
     429    }
    353430}
  • test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java b/test/unit/org/openstreetmap/josm/tools/ExifReaderTest.java
    index 285acd9d55..9dcad6ca38 100644
    a b import org.openstreetmap.josm.data.coor.conversion.DMSCoordinateFormat;  
    2121 * @since 6209
    2222 */
    2323class ExifReaderTest {
    24     private File orientationSampleFile, directionSampleFile;
     24    private File orientationSampleFile, directionSampleFile, positionErrorSampleFile;
    2525
    2626    /**
    2727     * Setup test
    class ExifReaderTest {  
    3030    public void setUp() {
    3131        directionSampleFile = new File("nodist/data/exif-example_direction.jpg");
    3232        orientationSampleFile = new File("nodist/data/exif-example_orientation=6.jpg");
     33        positionErrorSampleFile = new File("nodist/data/exif-position-error.jpg");
    3334    }
    3435
    3536    /**
    class ExifReaderTest {  
    5556        assertEquals(Instant.parse(expectedDate), date);
    5657    }
    5758
     59    /**
     60     * Test reading GPS date and time
     61     */
     62    @Test
     63    void testReadGpsDateTime() {
     64        Instant date = ExifReader.readGpsInstant(positionErrorSampleFile);
     65        assertEquals(Instant.parse("2024-04-30T16:36:42Z"), date);
     66    }
     67 
    5868    /**
    5969     * Test orientation extraction
    6070     */
    class ExifReaderTest {  
    100110        assertEquals(Double.valueOf(23.4), ExifReader.readElevation(new File("nodist/data/exif-example_speed_ele.jpg")));
    101111    }
    102112
     113    /**
     114     * Test horizontal position error extraction
     115     */
     116    @Test
     117    void testReadHorPosError() {
     118        assertEquals(Double.valueOf(0.014), ExifReader.readHpositioningError(positionErrorSampleFile));
     119    }
     120
     121    /**
     122     * Test GPS track course extraction
     123     */
     124    @Test
     125    void testReadGpsTrack() {
     126        assertEquals(Double.valueOf(298), ExifReader.readGpsTrackDirection(positionErrorSampleFile));
     127    }
     128
     129    /**
     130     * Test GPS differential mode extraction
     131     */
     132    @Test
     133    void testReadGpsDiffmode() {
     134        assertEquals(Integer.valueOf(1), ExifReader.readGpsDiffMode(positionErrorSampleFile));
     135    }
     136
     137    /**
     138     * Test GPS DOP value extraction
     139     */
     140    @Test
     141    void testReadGpsDop() {
     142        assertEquals(Double.valueOf(0.92), ExifReader.readGpsDop(positionErrorSampleFile));
     143    }
     144
     145    /**
     146     * Test GPS measure mode (2D/3D) extraction
     147     */
     148    @Test
     149    void testReadGps2d3dMode() {
     150        assertEquals(Integer.valueOf(3), ExifReader.readGpsMeasureMode(positionErrorSampleFile));
     151    }
     152
     153    /**
     154     * Test GPS datum extraction
     155     */
     156    @Test
     157    void testReadGpsDatum() {
     158        assertEquals(String.valueOf("EPSG:9782"), ExifReader.readGpsDatum(positionErrorSampleFile));
     159    }
     160
     161    /**
     162     * Test GPS processing method extraction
     163     */
     164    @Test
     165    void testReadGpsProcMethod() {
     166        assertEquals(String.valueOf("GNSS RTK_FIX CORRELATION"), ExifReader.readGpsProcessingMethod(positionErrorSampleFile));
     167    }
     168
    103169    /**
    104170     * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/11685">#11685</a>
    105171     * @throws IOException if an error occurs during reading